feat(pms): add project and knowledge management

This commit is contained in:
YunaiV
2026-09-04 00:37:08 +08:00
parent 6aa235e493
commit 31aded61d8
320 changed files with 31954 additions and 1 deletions
+2 -1
View File
@@ -28,7 +28,8 @@
<!-- <module>yudao-module-wms</module>-->
<!-- <module>yudao-module-hrm</module>-->
<!-- <module>yudao-module-fms</module>-->
<!-- <module>yudao-module-im</module>-->
<module>yudao-module-pms</module>
<!-- <module>yudao-module-im</module>-->
<!-- 请参考 https://doc.iocoder.cn/ai/build/ 文档,完成 AI 模块的启动!!! -->
<!-- <module>yudao-module-ai</module>-->
</modules>
@@ -1,11 +1,14 @@
package cn.iocoder.yudao.framework.excel.core.convert;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.util.StrUtil;
import cn.idev.excel.converters.Converter;
import cn.idev.excel.metadata.GlobalConfiguration;
import cn.idev.excel.metadata.data.ReadCellData;
import cn.idev.excel.metadata.data.WriteCellData;
import cn.idev.excel.metadata.property.ExcelContentProperty;
import java.util.Collections;
import java.util.List;
/**
@@ -15,6 +18,25 @@ import java.util.List;
*/
public class StringListConvert implements Converter<List<String>> {
/**
* 将 Excel 单元格中的分隔字符串解析为字符串列表。
*
* @param value Excel 单元格数据
* @param contentProperty Excel 字段属性
* @param globalConfiguration Excel 全局配置
* @return 字符串列表
*/
@Override
public List<String> convertToJavaData(ReadCellData<?> value, ExcelContentProperty contentProperty,
GlobalConfiguration globalConfiguration) {
String text = value == null ? null : value.getStringValue();
if (StrUtil.isBlank(text)) {
return Collections.emptyList();
}
// 兼容导入模板常用的英文逗号、中文逗号、顿号,以及导出时使用的斜杠。
return StrUtil.splitTrim(text.replace(',', ',').replace('、', ',').replace('/', ','), ',');
}
@Override
public WriteCellData<String> convertToExcelData(List<String> value,
ExcelContentProperty contentProperty, GlobalConfiguration globalConfiguration) {
@@ -50,6 +50,10 @@ public class BannerApplicationRunner implements ApplicationRunner {
if (isNotPresent("cn.iocoder.yudao.module.wms.framework.web.config.WmsWebConfiguration")) {
System.out.println("[WMS 仓库管理系统 yudao-module-wms - 已禁用][参考 https://doc.iocoder.cn/wms/build/ 开启]");
}
// PMS 系统
if (isNotPresent("cn.iocoder.yudao.module.pms.framework.web.config.PmsWebConfiguration")) {
System.out.println("[PMS 项目管理系统 yudao-module-pms - 已禁用][请在 yudao-server 中开启]");
}
// CRM 系统
if (isNotPresent("cn.iocoder.yudao.module.crm.framework.web.config.CrmWebConfiguration")) {
System.out.println("[CRM 系统 yudao-module-crm - 已禁用][参考 https://doc.iocoder.cn/crm/build/ 开启]");
+63
View File
@@ -0,0 +1,63 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao</artifactId>
<version>${revision}</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>yudao-module-pms</artifactId>
<packaging>jar</packaging>
<name>${project.artifactId}</name>
<description>
pms 包下,项目管理(Project Management System)
例如说:项目、工作事项、迭代、看板、工时和知识库等等
</description>
<dependencies>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-system</artifactId>
<version>${revision}</version>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-module-infra</artifactId>
<version>${revision}</version>
</dependency>
<!-- Web 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-security</artifactId>
</dependency>
<!-- DB 相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-mybatis</artifactId>
</dependency>
<!-- Job 定时任务相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-job</artifactId>
</dependency>
<!-- Test 测试相关 -->
<dependency>
<groupId>cn.iocoder.boot</groupId>
<artifactId>yudao-spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>
@@ -0,0 +1,93 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.permission.PmsKnowledgeContentPermissionRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.permission.PmsKnowledgeContentPermissionUpdateReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.content.PmsKnowledgeContentPermissionDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.content.PmsKnowledgeContentPermissionMemberDO;
import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeContentPermissionService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 知识内容协作权限")
@RestController
@RequestMapping("/pms/kb/content-permission")
@Validated
public class PmsKnowledgeContentPermissionController {
@Resource
private PmsKnowledgeContentPermissionService permissionService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@GetMapping("/get")
@Operation(summary = "获得知识内容协作权限")
@Parameter(name = "id", description = "协作权限编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<PmsKnowledgeContentPermissionRespVO> getContentPermission(@RequestParam("id") Long id) {
Long userId = getLoginUserId();
PmsKnowledgeContentPermissionDO permission = permissionService.getContentPermission(id, userId);
List<PmsKnowledgeContentPermissionMemberDO> members = permissionService.getContentPermissionMemberList(id, userId);
Integer currentUserLevel = permissionService.getCurrentUserContentPermissionLevel(
id, permission.getLibraryId(), userId);
return success(buildPermissionRespVO(permission, members, currentUserLevel));
}
@PutMapping("/update")
@Operation(summary = "更新知识内容协作权限")
@PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
public CommonResult<Boolean> updateContentPermission(@Valid @RequestBody PmsKnowledgeContentPermissionUpdateReqVO updateReqVO) {
permissionService.updateContentPermission(updateReqVO, getLoginUserId());
return success(true);
}
// ==================== 拼接 VO ====================
private PmsKnowledgeContentPermissionRespVO buildPermissionRespVO(PmsKnowledgeContentPermissionDO permission,
List<PmsKnowledgeContentPermissionMemberDO> members,
Integer currentUserLevel) {
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSet(members, PmsKnowledgeContentPermissionMemberDO::getUserId));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
convertSet(members, PmsKnowledgeContentPermissionMemberDO::getDeptId));
// 拼接 VO 返回
PmsKnowledgeContentPermissionRespVO permissionVO = BeanUtils.toBean(permission, PmsKnowledgeContentPermissionRespVO.class)
.setCreatorUserId(NumberUtils.parseLong(permission.getCreator()))
.setCurrentUserLevel(currentUserLevel)
.setMembers(BeanUtils.toBean(members, PmsKnowledgeContentPermissionRespVO.Member.class));
permissionVO.getMembers().forEach(member -> {
findAndThen(userMap, member.getUserId(), user -> member.setUserName(user.getNickname()));
findAndThen(deptMap, member.getDeptId(), dept -> member.setDeptName(dept.getName()));
});
return permissionVO;
}
}
@@ -0,0 +1,202 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document.PmsKnowledgeDocumentCreateReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document.PmsKnowledgeDocumentMoveReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document.PmsKnowledgeDocumentRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document.PmsKnowledgeDocumentSearchPageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document.PmsKnowledgeDocumentUpdateReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.content.PmsKnowledgeDocumentDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.interaction.PmsKnowledgeDocumentLikeDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.library.PmsKnowledgeLibraryDO;
import cn.iocoder.yudao.module.pms.enums.kb.content.PmsKnowledgeContentLevelEnum;
import cn.iocoder.yudao.module.pms.enums.kb.content.PmsKnowledgeDocumentTypeEnum;
import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeContentPermissionService;
import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeDocumentService;
import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeDocumentLikeService;
import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeFavoriteService;
import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeViewRecordService;
import cn.iocoder.yudao.module.pms.service.kb.library.PmsKnowledgeLibraryService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 知识库文档")
@RestController
@RequestMapping("/pms/kb/document")
@Validated
public class PmsKnowledgeDocumentController {
@Resource
private PmsKnowledgeDocumentService documentService;
@Resource
private PmsKnowledgeContentPermissionService contentPermissionService;
@Resource
private PmsKnowledgeFavoriteService favoriteService;
@Resource
private PmsKnowledgeDocumentLikeService documentLikeService;
@Resource
private PmsKnowledgeViewRecordService viewRecordService;
@Resource
private PmsKnowledgeLibraryService libraryService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建文档")
@PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
public CommonResult<Long> createDocument(@Valid @RequestBody PmsKnowledgeDocumentCreateReqVO createReqVO) {
return success(documentService.createDocument(createReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新文档内容")
@PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
public CommonResult<Boolean> updateDocument(@Valid @RequestBody PmsKnowledgeDocumentUpdateReqVO updateReqVO) {
documentService.updateDocument(updateReqVO, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "将文档移入回收站")
@Parameter(name = "id", description = "文档编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
public CommonResult<Boolean> deleteDocument(@RequestParam("id") Long id) {
documentService.deleteDocument(id, getLoginUserId());
return success(true);
}
@PutMapping("/move")
@Operation(summary = "移动文档")
@PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
public CommonResult<Boolean> moveDocument(@Valid @RequestBody PmsKnowledgeDocumentMoveReqVO moveReqVO) {
documentService.moveDocument(moveReqVO, getLoginUserId());
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得文档详情")
@Parameter(name = "id", description = "文档编号", required = true, example = "1024")
@Parameter(name = "view", description = "是否记录本次浏览", example = "true")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<PmsKnowledgeDocumentRespVO> getDocument(
@RequestParam("id") Long id,
@RequestParam(value = "view", defaultValue = "false") Boolean view) {
// 查询文档
Long userId = getLoginUserId();
PmsKnowledgeDocumentDO document = documentService.getDocument(id, userId);
// 记录浏览
if (Boolean.TRUE.equals(view)) {
viewRecordService.createViewRecord(document.getLibraryId(), document.getType(), document.getId(), userId);
}
// 构建文档 VO
return success(buildDocumentRespVO(document, userId));
}
@GetMapping("/search-page")
@Operation(summary = "使用 MySQL 全局搜索文档")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<PageResult<PmsKnowledgeDocumentRespVO>> getDocumentSearchPage(
@Valid PmsKnowledgeDocumentSearchPageReqVO pageReqVO) {
PageResult<PmsKnowledgeDocumentDO> pageResult = documentService.getDocumentSearchPage(
pageReqVO, getLoginUserId());
return success(new PageResult<>(buildDocumentRespVOList(pageResult.getList()), pageResult.getTotal()));
}
// ==================== 拼接 VO ====================
private PmsKnowledgeDocumentRespVO buildDocumentRespVO(PmsKnowledgeDocumentDO document, Long userId) {
// 1.1 拼接当前用户的内容权限和互动状态
Integer currentUserLevel = contentPermissionService.getCurrentUserContentPermissionLevel(
document.getPermissionId(), document.getLibraryId(), userId);
Long creatorUserId = NumberUtils.parseLong(document.getCreator());
PmsKnowledgeDocumentRespVO documentVO = BeanUtils.toBean(document, PmsKnowledgeDocumentRespVO.class)
.setCreatorUserId(creatorUserId).setCurrentUserLevel(currentUserLevel)
.setDownloadStatus(PmsKnowledgeContentLevelEnum.canDownload(currentUserLevel))
.setPreviewUrl(document.getContent())
.setFavoriteStatus(favoriteService.isFavorite(document.getType(), document.getId(), userId));
// 1.2 文件不可下载时,隐藏实际文件地址
if (PmsKnowledgeDocumentTypeEnum.FILE.getType().equals(document.getType())
&& Boolean.FALSE.equals(documentVO.getDownloadStatus())) {
documentVO.setContent(null);
}
// 2. 一次查询并拼接创建人和点赞用户信息
List<PmsKnowledgeDocumentLikeDO> likes = documentLikeService.getDocumentLikeList(document.getId());
Set<Long> relatedUserIds = convertSet(likes, PmsKnowledgeDocumentLikeDO::getUserId);
relatedUserIds.add(creatorUserId);
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(relatedUserIds);
findAndThen(userMap, creatorUserId, user -> documentVO.setCreatorUserName(user.getNickname()));
documentVO.setLikeStatus(CollUtil.anyMatch(likes, like -> userId.equals(like.getUserId())))
.setLikeUsers(convertList(likes, like -> {
PmsKnowledgeDocumentRespVO.LikeUser likeUser = new PmsKnowledgeDocumentRespVO.LikeUser()
.setId(like.getUserId());
findAndThen(userMap, like.getUserId(), user ->
likeUser.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
return likeUser;
}));
return documentVO;
}
private List<PmsKnowledgeDocumentRespVO> buildDocumentRespVOList(List<PmsKnowledgeDocumentDO> documents) {
if (CollUtil.isEmpty(documents)) {
return Collections.emptyList();
}
Map<Long, PmsKnowledgeLibraryDO> libraryMap = libraryService.getLibraryMap(
convertSet(documents, PmsKnowledgeDocumentDO::getLibraryId));
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(documents,
document -> NumberUtils.parseLong(document.getCreator())));
return convertList(documents, document -> {
PmsKnowledgeDocumentRespVO documentVO = BeanUtils.toBean(document, PmsKnowledgeDocumentRespVO.class)
.setContent(null).setContentSummary(buildContentSummary(document.getContent()))
.setCreatorUserId(NumberUtils.parseLong(document.getCreator()));
findAndThen(libraryMap, document.getLibraryId(),
library -> documentVO.setLibraryName(library.getName()));
findAndThen(userMap, documentVO.getCreatorUserId(),
user -> documentVO.setCreatorUserName(user.getNickname()));
return documentVO;
});
}
/**
* 构造搜索结果摘要,避免直接返回完整正文
*/
private String buildContentSummary(String content) {
if (content == null || content.trim().isEmpty()) {
return null;
}
String summary = content.replaceAll("<[^>]+>", " ").replaceAll("\\s+", " ").trim();
return summary.length() > 120 ? summary.substring(0, 120) + "…" : summary;
}
}
@@ -0,0 +1,132 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document.PmsKnowledgeDocumentRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.label.PmsKnowledgeDocumentLabelPageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.label.PmsKnowledgeDocumentLabelRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.label.PmsKnowledgeDocumentLabelSaveReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.content.PmsKnowledgeDocumentDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.library.PmsKnowledgeLibraryDO;
import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeDocumentLabelService;
import cn.iocoder.yudao.module.pms.service.kb.library.PmsKnowledgeLibraryService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 知识库文档标签")
@RestController
@RequestMapping("/pms/kb/document-label")
@Validated
public class PmsKnowledgeDocumentLabelController {
@Resource
private PmsKnowledgeDocumentLabelService documentLabelService;
@Resource
private PmsKnowledgeLibraryService libraryService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建文档标签")
@PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
public CommonResult<Long> createDocumentLabel(
@Valid @RequestBody PmsKnowledgeDocumentLabelSaveReqVO saveReqVO) {
return success(documentLabelService.createDocumentLabel(saveReqVO));
}
@PutMapping("/update")
@Operation(summary = "修改文档标签")
@PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
public CommonResult<Boolean> updateDocumentLabel(
@Valid @RequestBody PmsKnowledgeDocumentLabelSaveReqVO saveReqVO) {
documentLabelService.updateDocumentLabel(saveReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除文档标签")
@Parameter(name = "id", description = "文档标签编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
public CommonResult<Boolean> deleteDocumentLabel(@RequestParam("id") Long id) {
documentLabelService.deleteDocumentLabel(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得文档标签详情")
@Parameter(name = "id", description = "文档标签编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<PmsKnowledgeDocumentLabelRespVO> getDocumentLabel(@RequestParam("id") Long id) {
return success(BeanUtils.toBean(documentLabelService.getDocumentLabel(id),
PmsKnowledgeDocumentLabelRespVO.class));
}
@GetMapping("/list")
@Operation(summary = "获得文档标签列表")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<List<PmsKnowledgeDocumentLabelRespVO>> getDocumentLabelList() {
return success(BeanUtils.toBean(documentLabelService.getDocumentLabelList(),
PmsKnowledgeDocumentLabelRespVO.class));
}
@GetMapping("/document-page")
@Operation(summary = "获得指定标签的文档分页")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<PageResult<PmsKnowledgeDocumentRespVO>> getDocumentPageByLabel(
@Valid PmsKnowledgeDocumentLabelPageReqVO pageReqVO) {
PageResult<PmsKnowledgeDocumentDO> pageResult = documentLabelService.getDocumentPageByLabel(
pageReqVO, getLoginUserId());
return success(new PageResult<>(buildDocumentRespVOList(pageResult.getList()), pageResult.getTotal()));
}
// ==================== 拼接 VO ====================
private List<PmsKnowledgeDocumentRespVO> buildDocumentRespVOList(List<PmsKnowledgeDocumentDO> documents) {
if (CollUtil.isEmpty(documents)) {
return Collections.emptyList();
}
Map<Long, PmsKnowledgeLibraryDO> libraryMap = libraryService.getLibraryMap(
convertSet(documents, PmsKnowledgeDocumentDO::getLibraryId));
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(documents,
document -> NumberUtils.parseLong(document.getCreator())));
return convertList(documents, document -> {
PmsKnowledgeDocumentRespVO documentVO = BeanUtils.toBean(document, PmsKnowledgeDocumentRespVO.class)
.setContent(null).setCreatorUserId(NumberUtils.parseLong(document.getCreator()));
findAndThen(libraryMap, document.getLibraryId(),
library -> documentVO.setLibraryName(library.getName()));
findAndThen(userMap, documentVO.getCreatorUserId(),
user -> documentVO.setCreatorUserName(user.getNickname()));
return documentVO;
});
}
}
@@ -0,0 +1,189 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.folder.PmsKnowledgeFolderMoveReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.folder.PmsKnowledgeFolderRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.folder.PmsKnowledgeFolderSaveReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.folder.PmsKnowledgeTreeRespVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.content.PmsKnowledgeDocumentDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.content.PmsKnowledgeFolderDO;
import cn.iocoder.yudao.module.pms.enums.kb.PmsKnowledgeObjectTypeEnum;
import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeContentPermissionService;
import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeDocumentService;
import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeFolderService;
import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeFavoriteService;
import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeViewRecordService;
import cn.iocoder.yudao.module.pms.service.kb.library.PmsKnowledgeLibraryMemberService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 知识库文件夹")
@RestController
@RequestMapping("/pms/kb/folder")
@Validated
public class PmsKnowledgeFolderController {
@Resource
private PmsKnowledgeFolderService folderService;
@Resource
private PmsKnowledgeDocumentService documentService;
@Resource
private PmsKnowledgeContentPermissionService contentPermissionService;
@Resource
private PmsKnowledgeLibraryMemberService libraryMemberService;
@Resource
private PmsKnowledgeFavoriteService favoriteService;
@Resource
private PmsKnowledgeViewRecordService viewRecordService;
@PostMapping("/create")
@Operation(summary = "创建文件夹")
@PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
public CommonResult<Long> createFolder(@Valid @RequestBody PmsKnowledgeFolderSaveReqVO saveReqVO) {
return success(folderService.createFolder(saveReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新文件夹")
@PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
public CommonResult<Boolean> updateFolder(@Valid @RequestBody PmsKnowledgeFolderSaveReqVO saveReqVO) {
folderService.updateFolder(saveReqVO, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "将文件夹移入回收站")
@Parameter(name = "id", description = "文件夹编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
public CommonResult<Boolean> deleteFolder(@RequestParam("id") Long id) {
folderService.deleteFolder(id, getLoginUserId());
return success(true);
}
@PutMapping("/move")
@Operation(summary = "移动文件夹")
@PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
public CommonResult<Boolean> moveFolder(@Valid @RequestBody PmsKnowledgeFolderMoveReqVO moveReqVO) {
folderService.moveFolder(moveReqVO, getLoginUserId());
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得文件夹详情")
@Parameter(name = "id", description = "文件夹编号", required = true, example = "1024")
@Parameter(name = "view", description = "是否记录本次浏览", example = "true")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<PmsKnowledgeFolderRespVO> getFolder(
@RequestParam("id") Long id,
@RequestParam(value = "view", defaultValue = "false") Boolean view) {
Long userId = getLoginUserId();
PmsKnowledgeFolderDO folder = folderService.getFolder(id, userId);
if (Boolean.TRUE.equals(view)) {
viewRecordService.createViewRecord(folder.getLibraryId(), PmsKnowledgeObjectTypeEnum.FOLDER.getType(),
folder.getId(), userId);
}
return success(buildFolderRespVO(folder, userId));
}
@GetMapping("/tree")
@Operation(summary = "获得知识库目录树")
@Parameter(name = "libraryId", description = "知识库编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<PmsKnowledgeTreeRespVO> getFolderTree(@RequestParam("libraryId") Long libraryId) {
Long userId = getLoginUserId();
List<PmsKnowledgeFolderDO> folders = folderService.getFolderList(libraryId, userId);
List<PmsKnowledgeDocumentDO> documents = documentService.getDocumentList(libraryId, userId);
return success(buildTreeRespVO(libraryId, userId,
libraryMemberService.isLibraryWritable(libraryId, userId),
libraryMemberService.isLibraryAdmin(libraryId, userId), folders, documents));
}
// ==================== 拼接 VO ====================
private PmsKnowledgeFolderRespVO buildFolderRespVO(PmsKnowledgeFolderDO folder, Long userId) {
List<PmsKnowledgeFolderDO> folders = folderService.getFolderList(folder.getLibraryId(), userId);
List<PmsKnowledgeDocumentDO> documents = documentService.getDocumentList(folder.getLibraryId(), userId);
int childFolderCount = CollUtil.count(folders,
item -> folder.getId().equals(item.getParentId()));
int documentCount = (int) CollUtil.count(documents,
item -> folder.getId().equals(item.getFolderId())
&& (item.getParentId() == null || item.getParentId() == 0L));
return BeanUtils.toBean(folder, PmsKnowledgeFolderRespVO.class)
.setChildFolderCount(childFolderCount).setDocumentCount(documentCount)
.setCurrentUserLevel(contentPermissionService.getCurrentUserContentPermissionLevel(folder.getPermissionId(), folder.getLibraryId(), userId))
.setFavoriteStatus(favoriteService.isFavorite(PmsKnowledgeObjectTypeEnum.FOLDER.getType(), folder.getId(), userId));
}
private PmsKnowledgeTreeRespVO buildTreeRespVO(Long libraryId, Long userId, boolean writeStatus,
boolean manageStatus,
List<PmsKnowledgeFolderDO> folders, List<PmsKnowledgeDocumentDO> documents) {
// 1. 批量获得当前用户的内容权限等级
Set<Long> permissionIds = convertSet(folders, PmsKnowledgeFolderDO::getPermissionId);
permissionIds.addAll(convertSet(documents, PmsKnowledgeDocumentDO::getPermissionId));
Map<Long, Integer> levelMap = contentPermissionService.getCurrentUserContentPermissionLevelMap(permissionIds, libraryId, userId);
// 2. 批量转换文件夹和文档节点
Map<Long, PmsKnowledgeTreeRespVO.FolderNode> folderNodeMap = convertMap(folders,
PmsKnowledgeFolderDO::getId, folder -> BeanUtils.toBean(folder, PmsKnowledgeTreeRespVO.FolderNode.class)
.setCurrentUserLevel(levelMap.get(folder.getPermissionId())).setChildren(new ArrayList<>()).setDocuments(new ArrayList<>()));
Map<Long, PmsKnowledgeTreeRespVO.DocumentNode> documentNodeMap = convertMap(documents,
PmsKnowledgeDocumentDO::getId, document -> BeanUtils.toBean(document, PmsKnowledgeTreeRespVO.DocumentNode.class)
.setCurrentUserLevel(levelMap.get(document.getPermissionId())).setChildren(new ArrayList<>()));
// 3. 按父文档或所属文件夹组装文档树
List<PmsKnowledgeTreeRespVO.DocumentNode> rootDocuments = new ArrayList<>();
for (PmsKnowledgeDocumentDO document : documents) {
PmsKnowledgeTreeRespVO.DocumentNode node = documentNodeMap.get(document.getId());
PmsKnowledgeTreeRespVO.DocumentNode parentNode = documentNodeMap.get(document.getParentId());
PmsKnowledgeTreeRespVO.FolderNode folderNode = folderNodeMap.get(document.getFolderId());
if (parentNode != null) {
parentNode.getChildren().add(node);
} else if (folderNode != null) {
folderNode.getDocuments().add(node);
} else {
rootDocuments.add(node);
}
}
// 4. 按父文件夹组装文件夹树
List<PmsKnowledgeTreeRespVO.FolderNode> rootFolders = new ArrayList<>();
for (PmsKnowledgeFolderDO folder : folders) {
PmsKnowledgeTreeRespVO.FolderNode node = folderNodeMap.get(folder.getId());
PmsKnowledgeTreeRespVO.FolderNode parentNode = folderNodeMap.get(folder.getParentId());
if (parentNode != null) {
parentNode.getChildren().add(node);
} else {
rootFolders.add(node);
}
}
return new PmsKnowledgeTreeRespVO().setLibraryId(libraryId).setWriteStatus(writeStatus)
.setManageStatus(manageStatus)
.setFolders(rootFolders).setDocuments(rootDocuments);
}
}
@@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.pms.enums.kb.content.PmsKnowledgeDocumentTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - PMS 知识库文档新增 Request VO")
@Data
public class PmsKnowledgeDocumentCreateReqVO {
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "知识库编号不能为空")
private Long libraryId;
@Schema(description = "文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "文件夹编号不能为空")
private Long folderId;
@Schema(description = "父文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "父文档编号不能为空")
private Long parentId;
@Schema(description = "文档标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品说明")
@NotBlank(message = "文档标题不能为空")
@Size(max = 255, message = "文档标题不能超过 255 个字符")
private String title;
@Schema(description = "文档类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
@NotNull(message = "文档类型不能为空")
@InEnum(PmsKnowledgeDocumentTypeEnum.class)
private Integer type;
@Schema(description = "文档内容或文件地址")
private String content;
@Schema(description = "文件类型", example = "pdf")
@Size(max = 50, message = "文件类型不能超过 50 个字符")
private String fileType;
@Schema(description = "文件大小,单位:字节", example = "102400")
@Min(value = 0, message = "文件大小不能小于 0")
@Max(value = 104857600, message = "文件大小不能超过 100 MB")
private Long fileSize;
}
@@ -0,0 +1,27 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - PMS 知识库文档移动 Request VO")
@Data
public class PmsKnowledgeDocumentMoveReqVO {
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "文档编号不能为空")
private Long id;
@Schema(description = "目标知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "目标知识库编号不能为空")
private Long targetLibraryId;
@Schema(description = "目标文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "目标文件夹编号不能为空")
private Long targetFolderId;
@Schema(description = "目标父文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "目标父文档编号不能为空")
private Long targetParentId;
}
@@ -0,0 +1,100 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - PMS 知识库文档 Response VO")
@Data
public class PmsKnowledgeDocumentRespVO {
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long libraryId;
@Schema(description = "协作权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long permissionId;
@Schema(description = "当前用户协作等级", example = "3")
private Integer currentUserLevel;
@Schema(description = "当前用户是否可下载", example = "true")
private Boolean downloadStatus;
@Schema(description = "知识库名称", example = "产品知识库")
private String libraryName;
@Schema(description = "文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long folderId;
@Schema(description = "父文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long parentId;
@Schema(description = "文档标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品说明")
private String title;
@Schema(description = "正文摘要")
private String contentSummary;
@Schema(description = "文档内容或文件地址")
private String content;
@Schema(description = "文件预览地址(下载权限不足时仍可用于在线预览)")
private String previewUrl;
@Schema(description = "文档类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer type;
@Schema(description = "文件类型", example = "pdf")
private String fileType;
@Schema(description = "文件大小,单位:字节", example = "102400")
private Long fileSize;
@Schema(description = "文档状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
@Schema(description = "标签编号列表", example = "[1024, 2048]")
private List<Long> labelIds;
@Schema(description = "创建人用户编号", example = "1")
private Long creatorUserId;
@Schema(description = "创建人姓名", example = "芋道")
private String creatorUserName;
@Schema(description = "当前用户是否已关注")
private Boolean favoriteStatus;
@Schema(description = "当前用户是否已点赞")
private Boolean likeStatus;
@Schema(description = "点赞用户列表")
private List<LikeUser> likeUsers;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
@Schema(description = "管理后台 - PMS 知识文档点赞用户 Response VO")
@Data
public static class LikeUser {
@Schema(description = "用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "用户昵称", example = "芋道")
private String nickname;
@Schema(description = "用户头像")
private String avatar;
}
}
@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - PMS 知识库文档全局搜索分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PmsKnowledgeDocumentSearchPageReqVO extends PageParam {
@Schema(description = "搜索关键字", example = "产品方案")
private String keyword;
@Schema(description = "知识库编号", example = "1024")
private Long libraryId;
@Schema(description = "创建人用户编号", example = "1")
private Long creatorUserId;
@Schema(description = "更新时间范围")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] updateTime;
}
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.document;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.Max;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - PMS 知识库文档修改 Request VO")
@Data
public class PmsKnowledgeDocumentUpdateReqVO {
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "文档编号不能为空")
private Long id;
@Schema(description = "文档标题", example = "产品说明")
@Size(max = 255, message = "文档标题不能超过 255 个字符")
private String title;
@Schema(description = "文档内容或文件地址")
private String content;
@Schema(description = "标签编号列表;传空集合表示清空标签")
private List<Long> labelIds;
@Schema(description = "文件类型", example = "pdf")
@Size(max = 50, message = "文件类型不能超过 50 个字符")
private String fileType;
@Schema(description = "文件大小,单位:字节", example = "102400")
@Min(value = 0, message = "文件大小不能小于 0")
@Max(value = 104857600, message = "文件大小不能超过 100 MB")
private Long fileSize;
}
@@ -0,0 +1,23 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.folder;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - PMS 知识库文件夹移动 Request VO")
@Data
public class PmsKnowledgeFolderMoveReqVO {
@Schema(description = "文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "文件夹编号不能为空")
private Long id;
@Schema(description = "目标知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "目标知识库编号不能为空")
private Long targetLibraryId;
@Schema(description = "目标父文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "目标父文件夹编号不能为空")
private Long targetParentId;
}
@@ -0,0 +1,48 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.folder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - PMS 知识库文件夹 Response VO")
@Data
public class PmsKnowledgeFolderRespVO {
@Schema(description = "文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long libraryId;
@Schema(description = "协作权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long permissionId;
@Schema(description = "当前用户协作等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer currentUserLevel;
@Schema(description = "父文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long parentId;
@Schema(description = "文件夹标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品文档")
private String title;
@Schema(description = "直属子文件夹数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer childFolderCount;
@Schema(description = "直属文档数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "12")
private Integer documentCount;
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
@Schema(description = "当前用户是否已关注", requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean favoriteStatus;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
}
@@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.folder;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - PMS 知识库文件夹新增/修改 Request VO")
@Data
public class PmsKnowledgeFolderSaveReqVO {
@Schema(description = "文件夹编号", example = "1024")
private Long id;
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "知识库编号不能为空")
private Long libraryId;
@Schema(description = "父文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "父文件夹编号不能为空")
private Long parentId;
@Schema(description = "文件夹标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品文档")
@NotBlank(message = "文件夹标题不能为空")
@Size(max = 255, message = "文件夹标题不能超过 255 个字符")
private String title;
}
@@ -0,0 +1,82 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.folder;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - PMS 知识库目录树 Response VO")
@Data
public class PmsKnowledgeTreeRespVO {
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long libraryId;
@Schema(description = "当前用户是否可编辑", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean writeStatus;
@Schema(description = "当前用户是否可管理目录结构", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean manageStatus;
@Schema(description = "根文件夹列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<FolderNode> folders;
@Schema(description = "根文档列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<DocumentNode> documents;
@Schema(description = "管理后台 - PMS 知识库文件夹树节点 Response VO")
@Data
public static class FolderNode {
@Schema(description = "文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "协作权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long permissionId;
@Schema(description = "当前用户协作等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer currentUserLevel;
@Schema(description = "父文件夹编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long parentId;
@Schema(description = "文件夹标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品文档")
private String title;
@Schema(description = "子文件夹列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<FolderNode> children;
@Schema(description = "文件夹下的文档列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<DocumentNode> documents;
}
@Schema(description = "管理后台 - PMS 知识库文档树节点 Response VO")
@Data
public static class DocumentNode {
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "协作权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long permissionId;
@Schema(description = "当前用户协作等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer currentUserLevel;
@Schema(description = "父文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long parentId;
@Schema(description = "文档标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品说明")
private String title;
@Schema(description = "文档类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer type;
@Schema(description = "文件类型", example = "pdf")
private String fileType;
@Schema(description = "子文档列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<DocumentNode> children;
}
}
@@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.label;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - PMS 按标签查询文档分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PmsKnowledgeDocumentLabelPageReqVO extends PageParam {
@Schema(description = "文档标签编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "文档标签编号不能为空")
private Long labelId;
}
@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.label;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - PMS 知识库文档标签 Response VO")
@Data
public class PmsKnowledgeDocumentLabelRespVO {
@Schema(description = "文档标签编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "标签名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品")
private String name;
@Schema(description = "标签颜色", requiredMode = Schema.RequiredMode.REQUIRED, example = "#409EFF")
private String color;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.label;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - PMS 知识库文档标签新增/修改 Request VO")
@Data
public class PmsKnowledgeDocumentLabelSaveReqVO {
@Schema(description = "文档标签编号", example = "1024")
private Long id;
@Schema(description = "标签名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品")
@NotBlank(message = "标签名称不能为空")
@Size(max = 255, message = "标签名称不能超过 255 个字符")
private String name;
@Schema(description = "标签颜色", requiredMode = Schema.RequiredMode.REQUIRED, example = "#409EFF")
@NotBlank(message = "标签颜色不能为空")
@Size(max = 20, message = "标签颜色不能超过 20 个字符")
private String color;
}
@@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.permission;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - PMS 知识内容协作权限 Response VO")
@Data
public class PmsKnowledgeContentPermissionRespVO {
@Schema(description = "协作权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long libraryId;
@Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean openStatus;
@Schema(description = "公开协作等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer openLevel;
@Schema(description = "创建人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long creatorUserId;
@Schema(description = "当前用户协作等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer currentUserLevel;
@Schema(description = "协作者列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<Member> members;
@Schema(description = "管理后台 - PMS 知识内容协作者 Response VO")
@Data
public static class Member {
@Schema(description = "协作者编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "用户编号", example = "1")
private Long userId;
@Schema(description = "用户昵称", example = "芋道")
private String userName;
@Schema(description = "部门编号", example = "100")
private Long deptId;
@Schema(description = "部门名称", example = "研发部")
private String deptName;
@Schema(description = "协作等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer level;
}
}
@@ -0,0 +1,59 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.content.vo.permission;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.pms.enums.kb.content.PmsKnowledgeContentLevelEnum;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - PMS 更新知识内容协作权限 Request VO")
@Data
public class PmsKnowledgeContentPermissionUpdateReqVO {
@Schema(description = "协作权限编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "协作权限编号不能为空")
private Long id;
@Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "是否公开不能为空")
private Boolean openStatus;
@Schema(description = "公开协作等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
@NotNull(message = "公开协作等级不能为空")
@InEnum(PmsKnowledgeContentLevelEnum.class)
private Integer openLevel;
@Schema(description = "协作者列表", requiredMode = Schema.RequiredMode.REQUIRED)
@Valid
@NotNull(message = "协作者列表不能为空")
private List<Member> members;
@Schema(description = "管理后台 - PMS 知识内容协作者保存项")
@Data
public static class Member {
@Schema(description = "用户编号", example = "1")
private Long userId;
@Schema(description = "部门编号", example = "100")
private Long deptId;
@Schema(description = "协作等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
@NotNull(message = "协作等级不能为空")
@InEnum(PmsKnowledgeContentLevelEnum.class)
private Integer level;
@AssertTrue(message = "协作者必须且只能选择用户或部门")
@JsonIgnore
public boolean isUserOrDeptValid() {
return (userId != null && deptId == null) || (userId == null && deptId != null);
}
}
}
@@ -0,0 +1,112 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.comment.PmsKnowledgeDocumentCommentRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.comment.PmsKnowledgeDocumentCommentSaveReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.interaction.PmsKnowledgeDocumentCommentDO;
import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeDocumentCommentService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.stream.Stream;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 知识库文档评论")
@RestController
@RequestMapping("/pms/kb/document-comment")
@Validated
public class PmsKnowledgeDocumentCommentController {
@Resource
private PmsKnowledgeDocumentCommentService commentService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建知识库文档评论")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<Long> createDocumentComment(@Valid @RequestBody PmsKnowledgeDocumentCommentSaveReqVO saveReqVO) {
return success(commentService.createDocumentComment(saveReqVO, getLoginUserId()));
}
@DeleteMapping("/delete")
@Operation(summary = "删除知识库文档评论")
@Parameter(name = "id", description = "评论编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<Boolean> deleteDocumentComment(@RequestParam("id") Long id) {
commentService.deleteDocumentComment(id, getLoginUserId());
return success(true);
}
@GetMapping("/list")
@Operation(summary = "获得知识库文档评论列表")
@Parameter(name = "documentId", description = "文档编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<List<PmsKnowledgeDocumentCommentRespVO>> getDocumentCommentList(
@RequestParam("documentId") Long documentId) {
List<PmsKnowledgeDocumentCommentDO> comments = commentService.getDocumentCommentList(documentId, getLoginUserId());
return success(buildCommentRespVOList(comments));
}
// ==================== 拼接 VO ====================
private List<PmsKnowledgeDocumentCommentRespVO> buildCommentRespVOList(
List<PmsKnowledgeDocumentCommentDO> comments) {
if (CollUtil.isEmpty(comments)) {
return Collections.emptyList();
}
// 1. 批量查询评论人和回复对象
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSetByFlatMap(comments,
comment -> Stream.of(comment.getUserId(), comment.getReplyUserId())));
// 2. 批量转换评论,避免回复组装依赖查询顺序
Map<Long, PmsKnowledgeDocumentCommentRespVO> commentMap = convertMap(comments,
PmsKnowledgeDocumentCommentDO::getId, comment -> {
PmsKnowledgeDocumentCommentRespVO commentVO = BeanUtils.toBean(comment,
PmsKnowledgeDocumentCommentRespVO.class).setChildren(new ArrayList<>());
findAndThen(userMap, comment.getUserId(), user -> commentVO.setUserName(user.getNickname()));
findAndThen(userMap, comment.getReplyUserId(),
user -> commentVO.setReplyUserName(user.getNickname()));
return commentVO;
});
// 3. 按主评论组织回复列表
List<PmsKnowledgeDocumentCommentRespVO> result = new ArrayList<>();
for (PmsKnowledgeDocumentCommentDO comment : comments) {
PmsKnowledgeDocumentCommentRespVO commentVO = commentMap.get(comment.getId());
if (PmsKnowledgeDocumentCommentDO.MAIN_ID_ROOT.equals(comment.getMainId())) {
result.add(commentVO);
continue;
}
findAndThen(commentMap, comment.getMainId(), mainComment -> mainComment.getChildren().add(commentVO));
}
return result;
}
}
@@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeDocumentLikeService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 知识库文档点赞")
@RestController
@RequestMapping("/pms/kb/document-like")
@Validated
public class PmsKnowledgeDocumentLikeController {
@Resource
private PmsKnowledgeDocumentLikeService documentLikeService;
@PostMapping("/create")
@Operation(summary = "点赞文档")
@Parameter(name = "documentId", description = "文档编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<Boolean> createDocumentLike(@RequestParam("documentId") Long documentId) {
documentLikeService.createDocumentLike(documentId, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "取消点赞文档")
@Parameter(name = "documentId", description = "文档编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<Boolean> deleteDocumentLike(@RequestParam("documentId") Long documentId) {
documentLikeService.deleteDocumentLike(documentId, getLoginUserId());
return success(true);
}
}
@@ -0,0 +1,101 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.tenant.core.aop.TenantIgnore;
import cn.iocoder.yudao.module.infra.api.file.FileApi;
import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.share.PmsKnowledgeDocumentSharePublicRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.share.PmsKnowledgeDocumentShareRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.share.PmsKnowledgeDocumentShareSaveReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.share.PmsKnowledgeDocumentShareUpdateMemberListReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.content.PmsKnowledgeDocumentDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.interaction.PmsKnowledgeDocumentShareDO;
import cn.iocoder.yudao.module.pms.enums.kb.content.PmsKnowledgeDocumentTypeEnum;
import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeDocumentShareService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.annotation.security.PermitAll;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 知识库文档分享")
@RestController
@RequestMapping("/pms/kb/document-share")
@Validated
public class PmsKnowledgeDocumentShareController {
@Resource
private PmsKnowledgeDocumentShareService documentShareService;
@Resource
private FileApi fileApi;
@PostMapping("/open")
@Operation(summary = "开启文档分享")
@PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
public CommonResult<PmsKnowledgeDocumentShareRespVO> openShare(
@Valid @RequestBody PmsKnowledgeDocumentShareSaveReqVO reqVO) {
PmsKnowledgeDocumentShareDO share = documentShareService.openShare(reqVO.getDocumentId(),
reqVO.getShareUserIds(), getLoginUserId());
return success(BeanUtils.toBean(share, PmsKnowledgeDocumentShareRespVO.class));
}
@PutMapping("/update-member-list")
@Operation(summary = "更新文档内部分享成员")
@PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
public CommonResult<Boolean> updateShareMemberList(
@Valid @RequestBody PmsKnowledgeDocumentShareUpdateMemberListReqVO reqVO) {
documentShareService.updateShareMemberList(reqVO.getDocumentId(), reqVO.getShareUserIds(), getLoginUserId());
return success(true);
}
@PutMapping("/close")
@Operation(summary = "关闭文档分享")
@Parameter(name = "documentId", description = "文档编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
public CommonResult<Boolean> closeShare(@RequestParam("documentId") Long documentId) {
documentShareService.closeShare(documentId, getLoginUserId());
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得文档分享配置")
@Parameter(name = "documentId", description = "文档编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<PmsKnowledgeDocumentShareRespVO> getDocumentShare(@RequestParam("documentId") Long documentId) {
PmsKnowledgeDocumentShareDO share = documentShareService.getActiveDocumentShare(documentId, getLoginUserId());
return success(BeanUtils.toBean(share, PmsKnowledgeDocumentShareRespVO.class));
}
@GetMapping("/get-by-token")
@Operation(summary = "通过分享令牌获得文档")
@Parameter(name = "token", description = "分享令牌", required = true)
@PermitAll
@TenantIgnore
public CommonResult<PmsKnowledgeDocumentSharePublicRespVO> getDocumentByToken(
@RequestParam("token") @NotBlank(message = "分享令牌不能为空") String token) {
PmsKnowledgeDocumentDO document = documentShareService.getDocumentByShareToken(token);
PmsKnowledgeDocumentSharePublicRespVO documentVO = BeanUtils.toBean(
document, PmsKnowledgeDocumentSharePublicRespVO.class);
if (PmsKnowledgeDocumentTypeEnum.FILE.getType().equals(document.getType())) {
documentVO.setContent(null).setPreviewUrl(
fileApi.presignGetUrl(document.getContent(), null));
}
return success(documentVO);
}
}
@@ -0,0 +1,77 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.PmsKnowledgeInteractionItemRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.favorite.PmsKnowledgeFavoritePageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.favorite.PmsKnowledgeFavoriteSaveReqVO;
import cn.iocoder.yudao.module.pms.enums.kb.PmsKnowledgeObjectTypeEnum;
import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeFavoriteService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 知识关注")
@RestController
@RequestMapping("/pms/kb/favorite")
@Validated
public class PmsKnowledgeFavoriteController {
@Resource
private PmsKnowledgeFavoriteService favoriteService;
@PostMapping("/create")
@Operation(summary = "关注知识对象")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<Boolean> createFavorite(@Valid @RequestBody PmsKnowledgeFavoriteSaveReqVO saveReqVO) {
favoriteService.createFavorite(saveReqVO, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "取消关注知识对象")
@Parameter(name = "type", description = "对象类型", required = true, example = "3")
@Parameter(name = "entityId", description = "对象编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<Boolean> deleteFavorite(
@RequestParam("type") @InEnum(PmsKnowledgeObjectTypeEnum.class) Integer type,
@RequestParam("entityId") Long entityId) {
favoriteService.deleteFavorite(type, entityId, getLoginUserId());
return success(true);
}
@GetMapping("/page")
@Operation(summary = "获得我的关注分页")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<PageResult<PmsKnowledgeInteractionItemRespVO>> getFavoritePage(
@Valid PmsKnowledgeFavoritePageReqVO pageReqVO) {
return success(favoriteService.getFavoritePage(pageReqVO, getLoginUserId()));
}
@GetMapping("/list")
@Operation(summary = "获得知识库内我的关注列表")
@Parameter(name = "libraryId", description = "知识库编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<List<PmsKnowledgeInteractionItemRespVO>> getFavoriteListByLibraryId(
@RequestParam("libraryId") Long libraryId) {
return success(favoriteService.getFavoriteListByLibraryId(libraryId, getLoginUserId()));
}
}
@@ -0,0 +1,38 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.viewrecord.PmsKnowledgeRecentListRespVO;
import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeViewRecordService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 知识最近浏览")
@RestController
@RequestMapping("/pms/kb/view-record")
@Validated
public class PmsKnowledgeViewRecordController {
@Resource
private PmsKnowledgeViewRecordService viewRecordService;
@GetMapping("/recent-list")
@Operation(summary = "获得最近浏览列表")
@Parameter(name = "libraryId", description = "知识库编号;不传时查询全部可读知识库", example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<PmsKnowledgeRecentListRespVO> getRecentViewRecordList(
@RequestParam(value = "libraryId", required = false) Long libraryId) {
return success(viewRecordService.getRecentViewRecordList(libraryId, getLoginUserId()));
}
}
@@ -0,0 +1,51 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - PMS 知识交互对象 Response VO")
@Data
public class PmsKnowledgeInteractionItemRespVO {
@Schema(description = "记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "对象类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer type;
@Schema(description = "对象编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long entityId;
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long libraryId;
@Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品知识库")
private String libraryName;
@Schema(description = "对象名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品说明")
private String name;
@Schema(description = "知识库简介")
private String description;
@Schema(description = "文件夹编号", example = "1024")
private Long folderId;
@Schema(description = "文档编号", example = "1024")
private Long documentId;
@Schema(description = "文件类型", example = "pdf")
private String fileType;
@Schema(description = "文件大小,单位:字节")
private Long fileSize;
@Schema(description = "对象更新时间")
private LocalDateTime targetUpdateTime;
@Schema(description = "记录时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
@@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.comment;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - PMS 知识库文档评论 Response VO")
@Data
public class PmsKnowledgeDocumentCommentRespVO {
@Schema(description = "评论编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long documentId;
@Schema(description = "评论人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long userId;
@Schema(description = "评论人昵称", example = "芋道")
private String userName;
@Schema(description = "主评论编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Long mainId;
@Schema(description = "回复对象用户编号", example = "2")
private Long replyUserId;
@Schema(description = "回复对象昵称", example = "源码")
private String replyUserName;
@Schema(description = "评论内容", requiredMode = Schema.RequiredMode.REQUIRED)
private String content;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "回复列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<PmsKnowledgeDocumentCommentRespVO> children;
}
@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.comment;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - PMS 知识库文档评论保存 Request VO")
@Data
public class PmsKnowledgeDocumentCommentSaveReqVO {
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "文档编号不能为空")
private Long documentId;
@Schema(description = "主评论编号", example = "0")
private Long mainId;
@Schema(description = "回复对象用户编号", example = "1")
private Long replyUserId;
@Schema(description = "评论内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "文档内容很清楚")
@NotBlank(message = "评论内容不能为空")
@Size(max = 2000, message = "评论内容不能超过 2000 个字符")
private String content;
}
@@ -0,0 +1,19 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.favorite;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.pms.enums.kb.PmsKnowledgeObjectTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Schema(description = "管理后台 - PMS 知识收藏(关注)分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class PmsKnowledgeFavoritePageReqVO extends PageParam {
@Schema(description = "对象类型", example = "3")
@InEnum(value = PmsKnowledgeObjectTypeEnum.class, message = "对象类型必须是 {value}")
private Integer type;
}
@@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.favorite;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.pms.enums.kb.PmsKnowledgeObjectTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - PMS 知识收藏(关注)保存 Request VO")
@Data
public class PmsKnowledgeFavoriteSaveReqVO {
@Schema(description = "对象类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
@NotNull(message = "对象类型不能为空")
@InEnum(value = PmsKnowledgeObjectTypeEnum.class, message = "对象类型必须是 {value}")
private Integer type;
@Schema(description = "对象编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "对象编号不能为空")
private Long entityId;
}
@@ -0,0 +1,33 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.share;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "PMS 知识库文档公开分享 Response VO")
@Data
public class PmsKnowledgeDocumentSharePublicRespVO {
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "文档标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品说明")
private String title;
@Schema(description = "文档内容或文件地址")
private String content;
@Schema(description = "文件临时预览地址")
private String previewUrl;
@Schema(description = "文档类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer type;
@Schema(description = "文件类型", example = "pdf")
private String fileType;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
}
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.share;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - PMS 知识库文档分享 Response VO")
@Data
public class PmsKnowledgeDocumentShareRespVO {
@Schema(description = "分享编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long documentId;
@Schema(description = "内部分享成员用户编号列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<Long> shareUserIds;
@Schema(description = "外部查看令牌", requiredMode = Schema.RequiredMode.REQUIRED)
private String token;
@Schema(description = "分享状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "关闭人用户编号")
private Long closeUserId;
@Schema(description = "关闭时间")
private LocalDateTime closeTime;
}
@@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.share;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - PMS 知识库文档分享保存 Request VO")
@Data
public class PmsKnowledgeDocumentShareSaveReqVO {
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "文档编号不能为空")
private Long documentId;
@Schema(description = "内部分享成员用户编号列表")
private List<@NotNull(message = "分享成员用户编号不能为空") Long> shareUserIds;
}
@@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.share;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - PMS 知识库文档分享成员列表更新 Request VO")
@Data
public class PmsKnowledgeDocumentShareUpdateMemberListReqVO {
@Schema(description = "文档编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "文档编号不能为空")
private Long documentId;
@Schema(description = "内部分享成员用户编号列表")
private List<@NotNull(message = "分享成员用户编号不能为空") Long> shareUserIds;
}
@@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.viewrecord;
import cn.iocoder.yudao.module.pms.controller.admin.kb.interaction.vo.PmsKnowledgeInteractionItemRespVO;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - PMS 知识最近浏览 Response VO")
@Data
public class PmsKnowledgeRecentListRespVO {
@Schema(description = "今天浏览列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<PmsKnowledgeInteractionItemRespVO> todayItems;
@Schema(description = "昨天浏览列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<PmsKnowledgeInteractionItemRespVO> yesterdayItems;
@Schema(description = "更早 30 天浏览列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<PmsKnowledgeInteractionItemRespVO> recent30DayItems;
}
@@ -0,0 +1,106 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.library;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.group.PmsKnowledgeGroupRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.group.PmsKnowledgeGroupSaveReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.group.PmsKnowledgeGroupSortReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.group.PmsKnowledgeLibraryMoveGroupReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.library.PmsKnowledgeGroupDO;
import cn.iocoder.yudao.module.pms.service.kb.library.PmsKnowledgeGroupService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 知识库分组")
@RestController
@RequestMapping("/pms/kb/group")
@Validated
public class PmsKnowledgeGroupController {
@Resource
private PmsKnowledgeGroupService knowledgeGroupService;
@PostMapping("/create")
@Operation(summary = "创建知识库分组")
@PreAuthorize("@ss.hasPermission('pms:kb:library:create')")
public CommonResult<Long> createGroup(@Valid @RequestBody PmsKnowledgeGroupSaveReqVO saveReqVO) {
return success(knowledgeGroupService.createGroup(saveReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "修改知识库分组")
@PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
public CommonResult<Boolean> updateGroup(@Valid @RequestBody PmsKnowledgeGroupSaveReqVO saveReqVO) {
knowledgeGroupService.updateGroup(saveReqVO, getLoginUserId());
return success(true);
}
@PutMapping("/update-sort")
@Operation(summary = "修改知识库分组排序")
@PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
public CommonResult<Boolean> updateGroupSort(
@Valid @RequestBody PmsKnowledgeGroupSortReqVO sortReqVO) {
knowledgeGroupService.updateGroupSort(sortReqVO, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除知识库分组")
@Parameter(name = "id", description = "知识库分组编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
public CommonResult<Boolean> deleteGroup(@RequestParam("id") Long id) {
knowledgeGroupService.deleteGroup(id, getLoginUserId());
return success(true);
}
@GetMapping("/list")
@Operation(summary = "获得知识库分组列表")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<List<PmsKnowledgeGroupRespVO>> getGroupList() {
Long userId = getLoginUserId();
List<PmsKnowledgeGroupDO> groups = knowledgeGroupService.getGroupList(userId);
Map<Long, Integer> groupCountMap = knowledgeGroupService.getGroupLibraryCountMap(userId,
convertSet(groups, PmsKnowledgeGroupDO::getId));
return success(convertList(groups, group -> BeanUtils.toBean(group, PmsKnowledgeGroupRespVO.class)
.setLibraryCount(groupCountMap.getOrDefault(group.getId(), 0))));
}
@GetMapping("/get")
@Operation(summary = "获得知识库分组")
@Parameter(name = "id", description = "知识库分组编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<PmsKnowledgeGroupRespVO> getGroup(@RequestParam("id") Long id) {
return success(BeanUtils.toBean(knowledgeGroupService.getGroup(id, getLoginUserId()),
PmsKnowledgeGroupRespVO.class));
}
@PutMapping("/move")
@Operation(summary = "移动知识库到个人分组")
@PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
public CommonResult<Boolean> moveLibraryToGroup(
@Valid @RequestBody PmsKnowledgeLibraryMoveGroupReqVO moveReqVO) {
knowledgeGroupService.moveLibraryToGroup(moveReqVO, getLoginUserId());
return success(true);
}
}
@@ -0,0 +1,159 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.library;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.library.PmsKnowledgeLibraryPageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.library.PmsKnowledgeLibraryRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.library.PmsKnowledgeLibrarySaveReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.library.PmsKnowledgeLibraryDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.library.PmsKnowledgeLibraryMemberDO;
import cn.iocoder.yudao.module.pms.enums.kb.PmsKnowledgeObjectTypeEnum;
import cn.iocoder.yudao.module.pms.enums.kb.content.PmsKnowledgeDocumentTypeEnum;
import cn.iocoder.yudao.module.pms.enums.kb.library.PmsKnowledgeLibraryMemberLevelEnum;
import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeDocumentService;
import cn.iocoder.yudao.module.pms.service.kb.interaction.PmsKnowledgeFavoriteService;
import cn.iocoder.yudao.module.pms.service.kb.library.PmsKnowledgeLibraryMemberService;
import cn.iocoder.yudao.module.pms.service.kb.library.PmsKnowledgeLibraryService;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 知识库")
@RestController
@RequestMapping("/pms/kb/library")
@Validated
public class PmsKnowledgeLibraryController {
@Resource
private PmsKnowledgeLibraryService libraryService;
@Resource
private PmsKnowledgeLibraryMemberService memberService;
@Resource
private PmsKnowledgeDocumentService documentService;
@Resource
private PmsKnowledgeFavoriteService favoriteService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private PermissionApi permissionApi;
@PostMapping("/create")
@Operation(summary = "创建知识库")
@PreAuthorize("@ss.hasPermission('pms:kb:library:create')")
public CommonResult<Long> createLibrary(@Valid @RequestBody PmsKnowledgeLibrarySaveReqVO saveReqVO) {
return success(libraryService.createLibrary(saveReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新知识库")
@PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
public CommonResult<Boolean> updateLibrary(@Valid @RequestBody PmsKnowledgeLibrarySaveReqVO saveReqVO) {
libraryService.updateLibrary(saveReqVO, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除知识库")
@Parameter(name = "id", description = "知识库编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
public CommonResult<Boolean> deleteLibrary(@RequestParam("id") Long id) {
libraryService.deleteLibrary(id, getLoginUserId());
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得知识库详情")
@Parameter(name = "id", description = "知识库编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<PmsKnowledgeLibraryRespVO> getLibrary(@RequestParam("id") Long id) {
Long userId = getLoginUserId();
PmsKnowledgeLibraryDO library = memberService.validateLibraryReadable(id, userId);
return success(CollUtil.getFirst(buildLibraryRespVOList(Collections.singletonList(library), userId)));
}
@GetMapping("/page")
@Operation(summary = "获得知识库分页")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<PageResult<PmsKnowledgeLibraryRespVO>> getLibraryPage(
@Valid PmsKnowledgeLibraryPageReqVO pageReqVO) {
Long userId = getLoginUserId();
PageResult<PmsKnowledgeLibraryDO> pageResult = libraryService.getLibraryPage(pageReqVO, userId);
return success(new PageResult<>(buildLibraryRespVOList(pageResult.getList(), userId), pageResult.getTotal()));
}
// ==================== 拼接 VO ====================
private List<PmsKnowledgeLibraryRespVO> buildLibraryRespVOList(
List<PmsKnowledgeLibraryDO> libraries, Long userId) {
if (CollUtil.isEmpty(libraries)) {
return Collections.emptyList();
}
// 1. 批量查询知识库成员、内容数量、创建人和当前用户权限信息
Set<Long> libraryIds = convertSet(libraries, PmsKnowledgeLibraryDO::getId);
Map<Long, List<PmsKnowledgeLibraryMemberDO>> memberListMap = memberService.getLibraryMemberListMap(libraryIds);
Map<Long, Map<Integer, Long>> documentTypeCountMap = documentService.getDocumentTypeCountMap(libraryIds);
Set<Long> userIds = convertSet(libraries, library -> NumberUtils.parseLong(library.getCreator()));
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
AdminUserRespDTO currentUser = adminUserApi.getUser(userId);
Long deptId = currentUser != null ? currentUser.getDeptId() : null;
boolean superAdmin = permissionApi.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode());
Set<Long> favoriteLibraryIds = favoriteService.getFavoriteEntityIdSet(
PmsKnowledgeObjectTypeEnum.LIBRARY.getType(), libraryIds, userId);
// 2. 拼接每个知识库的成员权限、统计数量、收藏状态和创建人名称
return convertList(libraries, library -> {
List<PmsKnowledgeLibraryMemberDO> members = memberListMap.getOrDefault(library.getId(), Collections.emptyList());
PmsKnowledgeLibraryMemberDO directMember = CollUtil.findOne(members, member -> userId.equals(member.getUserId()));
PmsKnowledgeLibraryMemberDO currentMember = directMember;
if (currentMember == null && deptId != null) {
currentMember = CollUtil.findOne(members, member -> deptId.equals(member.getDeptId()));
}
boolean adminStatus = superAdmin || currentMember != null
&& (PmsKnowledgeLibraryMemberLevelEnum.CREATOR.getLevel().equals(currentMember.getLevel())
|| PmsKnowledgeLibraryMemberLevelEnum.ADMIN.getLevel().equals(currentMember.getLevel()));
PmsKnowledgeLibraryRespVO libraryVO = BeanUtils.toBean(library, PmsKnowledgeLibraryRespVO.class)
.setCreatorUserId(NumberUtils.parseLong(library.getCreator())).setMemberCount(members.size())
.setWriteStatus(superAdmin || currentMember != null).setAdminStatus(adminStatus)
.setExitStatus(directMember != null && !PmsKnowledgeLibraryMemberLevelEnum.CREATOR.getLevel().equals(directMember.getLevel()))
.setFavoriteStatus(favoriteLibraryIds.contains(library.getId()));
Map<Integer, Long> typeCountMap = documentTypeCountMap.getOrDefault(library.getId(), Collections.emptyMap());
libraryVO.setDocumentCount(typeCountMap.getOrDefault(PmsKnowledgeDocumentTypeEnum.RICH_TEXT.getType(), 0L))
.setFileCount(typeCountMap.getOrDefault(PmsKnowledgeDocumentTypeEnum.FILE.getType(), 0L));
findAndThen(userMap, libraryVO.getCreatorUserId(), user -> libraryVO.setCreatorUserName(user.getNickname()));
return libraryVO;
});
}
}
@@ -0,0 +1,106 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.library;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.member.PmsKnowledgeLibraryMemberRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.member.PmsKnowledgeLibraryUpdateMemberListReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.library.PmsKnowledgeLibraryMemberDO;
import cn.iocoder.yudao.module.pms.service.kb.library.PmsKnowledgeLibraryMemberService;
import cn.iocoder.yudao.module.system.api.dept.DeptApi;
import cn.iocoder.yudao.module.system.api.dept.dto.DeptRespDTO;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 知识库成员")
@RestController
@RequestMapping("/pms/kb/library-member")
@Validated
public class PmsKnowledgeLibraryMemberController {
@Resource
private PmsKnowledgeLibraryMemberService memberService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private DeptApi deptApi;
@GetMapping("/list")
@Operation(summary = "获得知识库成员列表")
@Parameter(name = "libraryId", description = "知识库编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<List<PmsKnowledgeLibraryMemberRespVO>> getLibraryMemberList(@RequestParam("libraryId") Long libraryId) {
List<PmsKnowledgeLibraryMemberDO> members = memberService.getLibraryMemberList(libraryId, getLoginUserId());
return success(buildLibraryMemberRespVOList(members));
}
@PutMapping("/update-list")
@Operation(summary = "更新知识库成员")
@PreAuthorize("@ss.hasPermission('pms:kb:library:update')")
public CommonResult<Boolean> updateLibraryMemberList(@Valid @RequestBody PmsKnowledgeLibraryUpdateMemberListReqVO updateReqVO) {
memberService.updateLibraryMemberList(updateReqVO, getLoginUserId());
return success(true);
}
@DeleteMapping("/exit")
@Operation(summary = "主动退出知识库")
@Parameter(name = "libraryId", description = "知识库编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:query')")
public CommonResult<Boolean> exitLibrary(@RequestParam("libraryId") Long libraryId) {
memberService.exitLibrary(libraryId, getLoginUserId());
return success(true);
}
// ==================== 拼接 VO ====================
private List<PmsKnowledgeLibraryMemberRespVO> buildLibraryMemberRespVOList(
List<PmsKnowledgeLibraryMemberDO> members) {
if (CollUtil.isEmpty(members)) {
return Collections.emptyList();
}
// 1. 批量查询用户和部门
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSet(members, PmsKnowledgeLibraryMemberDO::getUserId));
Map<Long, DeptRespDTO> deptMap = deptApi.getDeptMap(
convertSet(members, PmsKnowledgeLibraryMemberDO::getDeptId));
Map<Long, DeptRespDTO> parentDeptMap = deptApi.getDeptMap(convertSet(deptMap.values(), DeptRespDTO::getParentId));
// 2. 转换成员响应,并拼接用户和部门名称
return convertList(members, member -> {
PmsKnowledgeLibraryMemberRespVO memberVO = BeanUtils.toBean(member, PmsKnowledgeLibraryMemberRespVO.class);
findAndThen(userMap, member.getUserId(), user ->
memberVO.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
findAndThen(deptMap, member.getDeptId(), dept -> {
memberVO.setDeptName(dept.getName()).setParentDeptId(dept.getParentId());
findAndThen(parentDeptMap, dept.getParentId(), parent -> memberVO.setParentDeptName(parent.getName()));
});
return memberVO;
});
}
}
@@ -0,0 +1,103 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.library;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.template.PmsKnowledgeLibraryTemplatePageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.template.PmsKnowledgeLibraryTemplateRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.template.PmsKnowledgeLibraryTemplateSaveReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.library.PmsKnowledgeLibraryTemplateDO;
import cn.iocoder.yudao.module.pms.service.kb.library.PmsKnowledgeLibraryTemplateService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
/**
* 管理后台 - PMS 知识库模板 Controller
*
* @author 芋道源码
*/
@Tag(name = "管理后台 - PMS 知识库模板")
@RestController
@RequestMapping("/pms/kb/library-template")
@Validated
public class PmsKnowledgeLibraryTemplateController {
@Resource
private PmsKnowledgeLibraryTemplateService libraryTemplateService;
@PostMapping("/create")
@Operation(summary = "创建知识库模板")
@PreAuthorize("@ss.hasPermission('pms:kb:library-template:create')")
public CommonResult<Long> createLibraryTemplate(
@Valid @RequestBody PmsKnowledgeLibraryTemplateSaveReqVO createReqVO) {
return success(libraryTemplateService.createLibraryTemplate(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新知识库模板")
@PreAuthorize("@ss.hasPermission('pms:kb:library-template:update')")
public CommonResult<Boolean> updateLibraryTemplate(
@Valid @RequestBody PmsKnowledgeLibraryTemplateSaveReqVO updateReqVO) {
libraryTemplateService.updateLibraryTemplate(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除知识库模板")
@Parameter(name = "id", description = "模板编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library-template:delete')")
public CommonResult<Boolean> deleteLibraryTemplate(@RequestParam("id") Long id) {
libraryTemplateService.deleteLibraryTemplate(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得知识库模板")
@Parameter(name = "id", description = "模板编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library-template:query')")
public CommonResult<PmsKnowledgeLibraryTemplateRespVO> getLibraryTemplate(@RequestParam("id") Long id) {
PmsKnowledgeLibraryTemplateDO template = libraryTemplateService.getLibraryTemplate(id);
return success(BeanUtils.toBean(template, PmsKnowledgeLibraryTemplateRespVO.class));
}
@GetMapping("/simple-list")
@Operation(summary = "获取知识库模板全列表", description = "只包含开启状态模板,返回模板选择所需的基本信息和文档标题")
public CommonResult<List<PmsKnowledgeLibraryTemplateRespVO>> getSimpleLibraryTemplateList() {
return success(convertList(libraryTemplateService.getLibraryTemplateList(CommonStatusEnum.ENABLE.getStatus()),
template -> new PmsKnowledgeLibraryTemplateRespVO().setId(template.getId())
.setName(template.getName()).setCoverUrl(template.getCoverUrl()).setDescription(template.getDescription())
.setDocuments(convertList(template.getDocuments(), document ->
new PmsKnowledgeLibraryTemplateSaveReqVO.Document().setTitle(document.getTitle())))));
}
@GetMapping("/page")
@Operation(summary = "获得知识库模板分页")
@PreAuthorize("@ss.hasPermission('pms:kb:library-template:query')")
public CommonResult<PageResult<PmsKnowledgeLibraryTemplateRespVO>> getLibraryTemplatePage(
@Valid PmsKnowledgeLibraryTemplatePageReqVO pageReqVO) {
PageResult<PmsKnowledgeLibraryTemplateDO> pageResult = libraryTemplateService
.getLibraryTemplatePage(pageReqVO);
return success(new PageResult<>(BeanUtils.toBean(pageResult.getList(), PmsKnowledgeLibraryTemplateRespVO.class),
pageResult.getTotal()));
}
}
@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.group;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - PMS 知识库分组 Response VO")
@Data
public class PmsKnowledgeGroupRespVO {
@Schema(description = "知识库分组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "分组名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品知识库")
private String name;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer sort;
@Schema(description = "分组类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer type;
@Schema(description = "分组内知识库数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
private Integer libraryCount;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
@@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.group;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - PMS 知识库分组新增/修改 Request VO")
@Data
public class PmsKnowledgeGroupSaveReqVO {
@Schema(description = "知识库分组编号", example = "1024")
private Long id;
@Schema(description = "分组名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品知识库")
@NotBlank(message = "分组名称不能为空")
@Size(max = 100, message = "分组名称不能超过 100 个字符")
private String name;
}
@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.group;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - PMS 知识库分组排序 Request VO")
@Data
public class PmsKnowledgeGroupSortReqVO {
@Schema(description = "知识库分组排序项", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "知识库分组排序项不能为空")
private List<@Valid Item> items;
@Schema(description = "管理后台 - PMS 知识库分组排序项 Request VO")
@Data
public static class Item {
@Schema(description = "知识库分组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "知识库分组编号不能为空")
private Long id;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "显示顺序不能为空")
@Min(value = 0, message = "显示顺序不能小于 0")
private Integer sort;
}
}
@@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.group;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - PMS 知识库移动分组 Request VO")
@Data
public class PmsKnowledgeLibraryMoveGroupReqVO {
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "知识库编号不能为空")
private Long libraryId;
@Schema(description = "目标分组编号;为空表示移出分组", example = "2048")
private Long groupId;
}
@@ -0,0 +1,21 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.library;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - PMS 知识库分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PmsKnowledgeLibraryPageReqVO extends PageParam {
@Schema(description = "知识库名称", example = "产品")
private String name;
@Schema(description = "个人知识库分组编号", example = "1024")
private Long groupId;
}
@@ -0,0 +1,57 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.library;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - PMS 知识库 Response VO")
@Data
public class PmsKnowledgeLibraryRespVO {
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品知识库")
private String name;
@Schema(description = "知识库简介")
private String description;
@Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean openStatus;
@Schema(description = "知识库封面")
private String coverUrl;
@Schema(description = "创建人用户编号", example = "1")
private Long creatorUserId;
@Schema(description = "创建人姓名", example = "芋道源码")
private String creatorUserName;
@Schema(description = "成员数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
private Integer memberCount;
@Schema(description = "文档数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "12")
private Long documentCount;
@Schema(description = "文件数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "4")
private Long fileCount;
@Schema(description = "当前用户是否可以编辑内容", requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean writeStatus;
@Schema(description = "当前用户是否为知识库管理员", requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean adminStatus;
@Schema(description = "当前用户是否可以主动退出知识库", requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean exitStatus;
@Schema(description = "当前用户是否已关注", requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean favoriteStatus;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
@@ -0,0 +1,46 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.library;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Positive;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - PMS 知识库新增/修改 Request VO")
@Data
public class PmsKnowledgeLibrarySaveReqVO {
@Schema(description = "知识库编号", example = "1024")
private Long id;
@Schema(description = "知识库名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品知识库")
@NotBlank(message = "知识库名称不能为空")
@Size(max = 50, message = "知识库名称不能超过 50 个字符")
private String name;
@Schema(description = "知识库简介", example = "沉淀产品文档")
@Size(max = 300, message = "知识库简介不能超过 300 个字符")
private String description;
@Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "false")
@NotNull(message = "知识库可见范围不能为空")
private Boolean openStatus;
@Schema(description = "知识库封面")
@Size(max = 255, message = "知识库封面地址不能超过 255 个字符")
private String coverUrl;
@Schema(description = "创建时的初始管理员用户编号列表", example = "[1, 2]")
private List<Long> adminUserIds;
@Schema(description = "创建时的初始普通成员用户编号列表", example = "[3, 4]")
private List<Long> memberUserIds;
@Schema(description = "模板编号,仅创建时有效", example = "1")
@Positive(message = "模板编号必须大于 0")
private Long templateId;
}
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.member;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - PMS 知识库成员 Response VO")
@Data
public class PmsKnowledgeLibraryMemberRespVO {
@Schema(description = "成员编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "用户编号", example = "1")
private Long userId;
@Schema(description = "用户姓名", example = "芋道源码")
private String nickname;
@Schema(description = "用户头像")
private String avatar;
@Schema(description = "部门编号", example = "100")
private Long deptId;
@Schema(description = "部门名称", example = "研发部")
private String deptName;
@Schema(description = "父部门编号")
private Long parentDeptId;
@Schema(description = "父部门名称")
private String parentDeptName;
@Schema(description = "成员等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer level;
}
@@ -0,0 +1,50 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.member;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.pms.enums.kb.library.PmsKnowledgeLibraryMemberLevelEnum;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - PMS 更新知识库成员列表 Request VO")
@Data
public class PmsKnowledgeLibraryUpdateMemberListReqVO {
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "知识库编号不能为空")
private Long libraryId;
@Schema(description = "成员列表", requiredMode = Schema.RequiredMode.REQUIRED)
@Valid
@NotNull(message = "成员列表不能为空")
private List<Member> members;
@Schema(description = "管理后台 - PMS 知识库成员更新项")
@Data
public static class Member {
@Schema(description = "用户编号", example = "1")
private Long userId;
@Schema(description = "部门编号", example = "100")
private Long deptId;
@Schema(description = "成员等级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
@NotNull(message = "成员等级不能为空")
@InEnum(PmsKnowledgeLibraryMemberLevelEnum.class)
private Integer level;
@AssertTrue(message = "知识库成员必须且只能选择用户或部门")
@JsonIgnore
public boolean isUserOrDeptValid() {
return (userId != null && deptId == null) || (userId == null && deptId != null);
}
}
}
@@ -0,0 +1,22 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.template;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Schema(description = "管理后台 - PMS 知识库模板分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class PmsKnowledgeLibraryTemplatePageReqVO extends PageParam {
@Schema(description = "模板名称,模糊匹配", example = "产品研发")
private String name;
@Schema(description = "模板状态", example = "0")
@InEnum(CommonStatusEnum.class)
private Integer status;
}
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.template;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - PMS 知识库模板 Response VO")
@Data
public class PmsKnowledgeLibraryTemplateRespVO {
@Schema(description = "模板编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "模板名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品研发")
private String name;
@Schema(description = "模板简介", example = "提供完善的产品流程文档")
private String description;
@Schema(description = "模板封面地址", example = "/static/pms/kb/template/product.png")
private String coverUrl;
@Schema(description = "模板状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Integer status;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer sort;
@Schema(description = "模板文档列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<PmsKnowledgeLibraryTemplateSaveReqVO.Document> documents;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
@@ -0,0 +1,66 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.library.vo.template;
import cn.iocoder.yudao.framework.common.enums.CommonStatusEnum;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.PositiveOrZero;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - PMS 知识库模板新增/修改 Request VO")
@Data
public class PmsKnowledgeLibraryTemplateSaveReqVO {
@Schema(description = "模板编号", example = "1024")
private Long id;
@Schema(description = "模板名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品研发")
@NotBlank(message = "模板名称不能为空")
@Size(max = 100, message = "模板名称不能超过 100 个字符")
private String name;
@Schema(description = "模板简介", example = "提供完善的产品流程文档")
@Size(max = 500, message = "模板简介不能超过 500 个字符")
private String description;
@Schema(description = "模板封面地址", example = "/static/pms/kb/template/product.png")
@Size(max = 255, message = "模板封面地址不能超过 255 个字符")
private String coverUrl;
@Schema(description = "模板状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "模板状态不能为空")
@InEnum(CommonStatusEnum.class)
private Integer status;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "显示顺序不能为空")
@PositiveOrZero(message = "显示顺序不能小于 0")
private Integer sort;
@Schema(description = "模板文档列表", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "模板文档不能为空")
@Valid
private List<Document> documents;
@Schema(description = "模板文档")
@Data
public static class Document {
@Schema(description = "文档标题", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品需求文档")
@NotBlank(message = "文档标题不能为空")
@Size(max = 255, message = "文档标题不能超过 255 个字符")
private String title;
@Schema(description = "文档内容", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "文档内容不能为空")
private String content;
}
}
@@ -0,0 +1,125 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.recycle;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.kb.recycle.vo.PmsKnowledgeRecycleDetailRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.recycle.vo.PmsKnowledgeRecyclePreviewRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.kb.recycle.vo.PmsKnowledgeRecycleRespVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.content.PmsKnowledgeDocumentDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.kb.recycle.PmsKnowledgeRecycleRecordDO;
import cn.iocoder.yudao.module.pms.enums.kb.PmsKnowledgeObjectTypeEnum;
import cn.iocoder.yudao.module.pms.service.kb.content.PmsKnowledgeDocumentService;
import cn.iocoder.yudao.module.pms.service.kb.recycle.PmsKnowledgeRecycleService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 知识库回收站")
@RestController
@RequestMapping("/pms/kb/recycle")
@Validated
public class PmsKnowledgeRecycleController {
@Resource
private PmsKnowledgeRecycleService recycleService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private PmsKnowledgeDocumentService documentService;
@GetMapping("/library-list")
@Operation(summary = "获得已删除知识库列表")
@PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
public CommonResult<List<PmsKnowledgeRecycleRespVO>> getLibraryRecycleList() {
List<PmsKnowledgeRecycleRecordDO> records = recycleService.getLibraryRecycleList(getLoginUserId());
return success(buildRecycleRespVOList(records));
}
@GetMapping("/content-list")
@Operation(summary = "获得知识库最近删除内容")
@Parameter(name = "libraryId", description = "知识库编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
public CommonResult<List<PmsKnowledgeRecycleRespVO>> getContentRecycleList(
@RequestParam("libraryId") Long libraryId) {
List<PmsKnowledgeRecycleRecordDO> records = recycleService.getContentRecycleList(
libraryId, getLoginUserId());
return success(buildRecycleRespVOList(records));
}
@GetMapping("/content-detail")
@Operation(summary = "获得知识库最近删除详情")
@Parameter(name = "id", description = "回收站记录编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
public CommonResult<PmsKnowledgeRecycleDetailRespVO> getContentRecycleDetail(
@RequestParam("id") Long id) {
return success(recycleService.getContentRecycleDetail(id, getLoginUserId()));
}
@GetMapping("/content-preview")
@Operation(summary = "预览知识库最近删除内容")
@Parameter(name = "id", description = "回收站记录编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
public CommonResult<PmsKnowledgeRecyclePreviewRespVO> getContentRecyclePreview(
@RequestParam("id") Long id,
@RequestParam(value = "entityId", required = false) Long entityId) {
PmsKnowledgeDocumentDO document = recycleService.getContentRecyclePreview(id, entityId, getLoginUserId());
return success(BeanUtils.toBean(document, PmsKnowledgeRecyclePreviewRespVO.class,
o -> o.setName(document.getTitle())));
}
@PutMapping("/restore")
@Operation(summary = "恢复回收站对象")
@Parameter(name = "id", description = "回收站记录编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
public CommonResult<Boolean> restoreContentRecycle(@RequestParam("id") Long id) {
recycleService.restoreContentRecycle(id, getLoginUserId());
return success(true);
}
@DeleteMapping("/permanent-delete")
@Operation(summary = "彻底删除回收站对象")
@Parameter(name = "id", description = "回收站记录编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:kb:library:delete')")
public CommonResult<Boolean> deleteContentRecycle(@RequestParam("id") Long id) {
recycleService.deleteContentRecycle(id, getLoginUserId());
return success(true);
}
// ==================== 拼接 VO ====================
private List<PmsKnowledgeRecycleRespVO> buildRecycleRespVOList(List<PmsKnowledgeRecycleRecordDO> records) {
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSet(records, PmsKnowledgeRecycleRecordDO::getDeleteUserId));
Map<Long, PmsKnowledgeDocumentDO> documentMap = documentService.getDocumentMap(convertSet(records, record ->
PmsKnowledgeObjectTypeEnum.FILE.getType().equals(record.getType()) ? record.getEntityId() : null));
return convertList(records, record -> {
PmsKnowledgeRecycleRespVO recordVO = BeanUtils.toBean(record, PmsKnowledgeRecycleRespVO.class);
findAndThen(documentMap, record.getEntityId(), document -> recordVO.setFileSize(document.getFileSize()));
findAndThen(userMap, record.getDeleteUserId(), user -> recordVO.setDeleteUserName(user.getNickname()));
return recordVO;
});
}
}
@@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.recycle.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - PMS 知识库回收站详情 Response VO")
@Data
public class PmsKnowledgeRecycleDetailRespVO {
@Schema(description = "根回收站记录")
private PmsKnowledgeRecycleRespVO root;
@Schema(description = "级联删除内容")
private List<Item> children;
@Data
public static class Item {
@Schema(description = "内容编号", requiredMode = Schema.RequiredMode.REQUIRED)
private Long id;
@Schema(description = "内容类型", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer type;
@Schema(description = "内容名称", requiredMode = Schema.RequiredMode.REQUIRED)
private String name;
@Schema(description = "父文档或父文件夹编号")
private Long parentId;
@Schema(description = "所属文件夹编号")
private Long folderId;
@Schema(description = "删除时间")
private LocalDateTime deleteTime;
}
}
@@ -0,0 +1,28 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.recycle.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - PMS 知识库回收站内容预览 Response VO")
@Data
public class PmsKnowledgeRecyclePreviewRespVO {
@Schema(description = "内容编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "内容类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer type;
@Schema(description = "内容名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品说明")
private String name;
@Schema(description = "文档正文或文件地址")
private String content;
@Schema(description = "文件类型", example = "pdf")
private String fileType;
@Schema(description = "文件大小,单位:字节", example = "102400")
private Long fileSize;
}
@@ -0,0 +1,45 @@
package cn.iocoder.yudao.module.pms.controller.admin.kb.recycle.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - PMS 知识库回收站记录 Response VO")
@Data
public class PmsKnowledgeRecycleRespVO {
@Schema(description = "回收站记录编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "知识库编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long libraryId;
@Schema(description = "对象类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer type;
@Schema(description = "对象编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private Long entityId;
@Schema(description = "对象名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "产品说明")
private String name;
@Schema(description = "文件大小,单位:字节")
private Long fileSize;
@Schema(description = "删除人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long deleteUserId;
@Schema(description = "删除人姓名", example = "芋道")
private String deleteUserName;
@Schema(description = "删除时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime deleteTime;
@Schema(description = "父对象编号,回收站详情中用于组装层级", example = "2048")
private Long parentId;
@Schema(description = "所属文件夹编号,回收站详情中用于组装层级", example = "2048")
private Long folderId;
}
@@ -0,0 +1,152 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.iteration;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo.PmsIterationOverviewRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo.PmsIterationPageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo.PmsIterationRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo.PmsIterationSaveReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo.PmsIterationStartReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.iteration.PmsIterationDO;
import cn.iocoder.yudao.module.pms.service.pm.iteration.PmsIterationService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 项目迭代")
@RestController
@RequestMapping("/pms/pm/iteration")
@Validated
public class PmsIterationController {
@Resource
private PmsIterationService iterationService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建项目迭代")
@PreAuthorize("@ss.hasPermission('pms:pm:iteration:create')")
public CommonResult<Long> createIteration(@Valid @RequestBody PmsIterationSaveReqVO saveReqVO) {
return success(iterationService.createIteration(saveReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新项目迭代")
@PreAuthorize("@ss.hasPermission('pms:pm:iteration:update')")
public CommonResult<Boolean> updateIteration(@Valid @RequestBody PmsIterationSaveReqVO saveReqVO) {
iterationService.updateIteration(saveReqVO, getLoginUserId());
return success(true);
}
@PutMapping("/start")
@Operation(summary = "开始项目迭代")
@PreAuthorize("@ss.hasPermission('pms:pm:iteration:update')")
public CommonResult<Boolean> startIteration(@Valid @RequestBody PmsIterationStartReqVO startReqVO) {
iterationService.startIteration(startReqVO, getLoginUserId());
return success(true);
}
@PutMapping("/complete")
@Operation(summary = "完成项目迭代")
@Parameter(name = "id", description = "迭代编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:iteration:update')")
public CommonResult<Boolean> completeIteration(@RequestParam("id") Long id) {
iterationService.completeIteration(id, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除项目迭代")
@Parameter(name = "id", description = "迭代编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:iteration:delete')")
public CommonResult<Boolean> deleteIteration(@RequestParam("id") Long id) {
iterationService.deleteIteration(id, getLoginUserId());
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得项目迭代详情")
@Parameter(name = "id", description = "迭代编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:iteration:query')")
public CommonResult<PmsIterationRespVO> getIteration(@RequestParam("id") Long id) {
PmsIterationDO iteration = iterationService.getIteration(id, getLoginUserId());
PmsIterationRespVO iterationVO = buildIterationRespVO(iteration);
return success(iterationVO);
}
@GetMapping("/page")
@Operation(summary = "获得项目迭代分页")
@PreAuthorize("@ss.hasPermission('pms:pm:iteration:query')")
public CommonResult<PageResult<PmsIterationRespVO>> getIterationPage(
@Valid PmsIterationPageReqVO pageReqVO) {
PageResult<PmsIterationDO> pageResult = iterationService.getIterationPage(pageReqVO, getLoginUserId());
List<PmsIterationRespVO> iterationVOList = buildIterationRespVOList(pageResult.getList());
Map<Long, Integer> progressMap = iterationService.getIterationProgressMap(
convertSet(pageResult.getList(), PmsIterationDO::getId));
iterationVOList.forEach(iteration -> iteration.setProgress(progressMap.getOrDefault(iteration.getId(), 0)));
return success(new PageResult<>(iterationVOList, pageResult.getTotal()));
}
@GetMapping("/overview")
@Operation(summary = "获得迭代概览统计")
@Parameter(name = "id", description = "迭代编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:iteration:query')")
public CommonResult<PmsIterationOverviewRespVO> getIterationOverview(@RequestParam("id") Long id) {
PmsIterationOverviewRespVO overview = iterationService.getIterationOverview(id, getLoginUserId());
List<PmsIterationOverviewRespVO.ActivityItem> activities = overview.getRecentActivities();
Map<Long, AdminUserRespDTO> activityUserMap = adminUserApi.getUserMap(
convertSet(activities, PmsIterationOverviewRespVO.ActivityItem::getOperatorUserId));
activities.forEach(activity -> findAndThen(activityUserMap, activity.getOperatorUserId(),
user -> activity.setOperatorUserName(user.getNickname())));
return success(overview.setRecentActivities(activities));
}
// ==================== 拼接 VO ====================
private PmsIterationRespVO buildIterationRespVO(PmsIterationDO iteration) {
return CollUtil.getFirst(buildIterationRespVOList(Collections.singletonList(iteration)));
}
private List<PmsIterationRespVO> buildIterationRespVOList(List<PmsIterationDO> iterations) {
if (CollUtil.isEmpty(iterations)) {
return Collections.emptyList();
}
// 1. 批量查询负责人
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSet(iterations, PmsIterationDO::getOwnerUserId));
// 2. 转换迭代响应,并拼接负责人姓名
return BeanUtils.toBean(iterations, PmsIterationRespVO.class, iterationVO ->
findAndThen(userMap, iterationVO.getOwnerUserId(),
user -> iterationVO.setOwnerUserName(user.getNickname())));
}
}
@@ -0,0 +1,107 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@Schema(description = "管理后台 - PMS 迭代概览 Response VO")
@Data
public class PmsIterationOverviewRespVO {
@Schema(description = "工作项总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "100")
private Integer totalCount;
@Schema(description = "未开始工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
private Integer pendingCount;
@Schema(description = "进行中工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "30")
private Integer processingCount;
@Schema(description = "已完成工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
private Integer completedCount;
@Schema(description = "完成进度百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
private Integer progress;
@Schema(description = "工作项类型数量统计", requiredMode = Schema.RequiredMode.REQUIRED, example = "{\"1\": 60, \"2\": 40}")
private Map<Integer, Integer> typeCountMap;
@Schema(description = "工作项类型和状态数量统计", requiredMode = Schema.RequiredMode.REQUIRED)
private Map<Integer, Map<Integer, Integer>> typeStatusCountMap;
@Schema(description = "状态趋势", requiredMode = Schema.RequiredMode.REQUIRED)
private List<TrendItem> statusTrends;
@Schema(description = "燃尽图数据", requiredMode = Schema.RequiredMode.REQUIRED)
private List<BurnDownItem> burnDowns;
@Schema(description = "最近动态列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ActivityItem> recentActivities;
@Schema(description = "管理后台 - PMS 迭代概览状态趋势项 VO")
@Data
public static class TrendItem {
@Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-01-01")
private String date;
@Schema(description = "当日未开始工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
private Integer pendingCount;
@Schema(description = "当日进行中工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer processingCount;
@Schema(description = "当日已完成工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer completedCount;
}
@Schema(description = "管理后台 - PMS 迭代概览燃尽图项 VO")
@Data
public static class BurnDownItem {
@Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED, example = "2026-01-01")
private String date;
@Schema(description = "理想剩余工时,单位:小时", requiredMode = Schema.RequiredMode.REQUIRED, example = "40")
private Integer idealRemaining;
@Schema(description = "实际剩余工时,单位:小时", requiredMode = Schema.RequiredMode.REQUIRED, example = "38")
private Integer actualRemaining;
}
@Schema(description = "管理后台 - PMS 迭代概览动态项 VO")
@Data
public static class ActivityItem {
@Schema(description = "动态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long id;
@Schema(description = "工作项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long workItemId;
@Schema(description = "工作项序号", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer workItemSerialNumber;
@Schema(description = "工作项名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "优化登录页")
private String workItemName;
@Schema(description = "操作人编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long operatorUserId;
@Schema(description = "操作人昵称", example = "芋道")
private String operatorUserName;
@Schema(description = "动态内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "将状态更新为「已完成」")
private String content;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
}
@@ -0,0 +1,29 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.pms.enums.pm.iteration.PmsIterationStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - PMS 项目迭代分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PmsIterationPageReqVO extends PageParam {
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "项目编号不能为空")
private Long projectId;
@Schema(description = "迭代名称", example = "第一期")
private String name;
@Schema(description = "迭代状态", example = "1")
@InEnum(PmsIterationStatusEnum.class)
private Integer status;
}
@@ -0,0 +1,54 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - PMS 项目迭代 Response VO")
@Data
public class PmsIterationRespVO {
@Schema(description = "迭代编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long projectId;
@Schema(description = "迭代名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "第一期")
private String name;
@Schema(description = "负责人用户编号", example = "1")
private Long ownerUserId;
@Schema(description = "迭代状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
@Schema(description = "负责人姓名", example = "芋道源码")
private String ownerUserName;
@Schema(description = "开始时间")
private LocalDateTime startTime;
@Schema(description = "结束时间")
private LocalDateTime endTime;
@Schema(description = "迭代目标", example = "完成核心流程")
private String target;
@Schema(description = "迭代描述")
private String description;
@Schema(description = "完成时间")
private LocalDateTime finishTime;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer sort;
@Schema(description = "完成进度百分比", requiredMode = Schema.RequiredMode.REQUIRED, example = "50")
private Integer progress;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
@@ -0,0 +1,53 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - PMS 项目迭代新增/修改 Request VO")
@Data
public class PmsIterationSaveReqVO {
@Schema(description = "迭代编号", example = "1024")
private Long id;
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "项目编号不能为空")
private Long projectId;
@Schema(description = "迭代名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "第一期")
@NotBlank(message = "迭代名称不能为空")
@Size(max = 100, message = "迭代名称长度不能超过 100 个字符")
private String name;
@Schema(description = "负责人用户编号", example = "1")
private Long ownerUserId;
@Schema(description = "开始时间")
private LocalDateTime startTime;
@Schema(description = "结束时间")
private LocalDateTime endTime;
@Schema(description = "迭代目标", example = "完成核心流程")
@Size(max = 255, message = "迭代目标长度不能超过 255 个字符")
private String target;
@Schema(description = "迭代描述")
private String description;
@Schema(hidden = true)
@AssertTrue(message = "迭代结束时间必须晚于开始时间,且开始、结束时间需要同时填写")
@JsonIgnore
public boolean isTimeRangeValid() {
return startTime == null && endTime == null
|| startTime != null && endTime != null && endTime.isAfter(startTime);
}
}
@@ -0,0 +1,34 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.iteration.vo;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - PMS 项目迭代开始 Request VO")
@Data
public class PmsIterationStartReqVO {
@Schema(description = "迭代编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "迭代编号不能为空")
private Long id;
@Schema(description = "开始时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "迭代开始时间不能为空")
private LocalDateTime startTime;
@Schema(description = "结束时间", requiredMode = Schema.RequiredMode.REQUIRED)
@NotNull(message = "迭代结束时间不能为空")
private LocalDateTime endTime;
@Schema(hidden = true)
@AssertTrue(message = "迭代结束时间必须晚于开始时间")
@JsonIgnore
public boolean isTimeRangeValid() {
return startTime == null || endTime == null || endTime.isAfter(startTime);
}
}
@@ -0,0 +1,129 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.announcement.PmsProjectAnnouncementRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.announcement.PmsProjectAnnouncementSaveReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.project.PmsProjectAnnouncementDO;
import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectAnnouncementService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 项目公告")
@RestController
@RequestMapping("/pms/pm/project-announcement")
@Validated
public class PmsProjectAnnouncementController {
@Resource
private PmsProjectAnnouncementService projectAnnouncementService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建项目公告")
@PreAuthorize("@ss.hasPermission('pms:pm:project:update')")
public CommonResult<Long> createProjectAnnouncement(@Valid @RequestBody PmsProjectAnnouncementSaveReqVO saveReqVO) {
return success(projectAnnouncementService.createProjectAnnouncement(saveReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新项目公告")
@PreAuthorize("@ss.hasPermission('pms:pm:project:update')")
public CommonResult<Boolean> updateProjectAnnouncement(
@Valid @RequestBody PmsProjectAnnouncementSaveReqVO saveReqVO) {
projectAnnouncementService.updateProjectAnnouncement(saveReqVO, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除项目公告")
@Parameter(name = "id", description = "公告编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:project:update')")
public CommonResult<Boolean> deleteProjectAnnouncement(@RequestParam("id") Long id) {
projectAnnouncementService.deleteProjectAnnouncement(id, getLoginUserId());
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得项目公告详情")
@Parameter(name = "id", description = "公告编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:project:query')")
public CommonResult<PmsProjectAnnouncementRespVO> getProjectAnnouncement(@RequestParam("id") Long id) {
PmsProjectAnnouncementDO announcement = projectAnnouncementService
.getProjectAnnouncement(id, getLoginUserId());
return success(buildProjectAnnouncementRespVO(announcement));
}
@GetMapping("/list")
@Operation(summary = "获得项目公告列表")
@Parameter(name = "projectId", description = "项目编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:project:query')")
public CommonResult<List<PmsProjectAnnouncementRespVO>> getProjectAnnouncementList(
@RequestParam("projectId") Long projectId) {
List<PmsProjectAnnouncementDO> announcements = projectAnnouncementService
.getProjectAnnouncementList(projectId, getLoginUserId());
List<PmsProjectAnnouncementRespVO> announcementVOList = buildProjectAnnouncementRespVOList(announcements);
return success(announcementVOList);
}
// ==================== 拼接 VO ====================
/**
* 构建项目公告响应
*
* @param announcement 项目公告
* @return 项目公告响应
*/
private PmsProjectAnnouncementRespVO buildProjectAnnouncementRespVO(PmsProjectAnnouncementDO announcement) {
return CollUtil.getFirst(buildProjectAnnouncementRespVOList(Collections.singletonList(announcement)));
}
/**
* 构建项目公告响应列表
*
* @param announcements 项目公告列表
* @return 项目公告响应列表
*/
private List<PmsProjectAnnouncementRespVO> buildProjectAnnouncementRespVOList(
List<PmsProjectAnnouncementDO> announcements) {
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(announcements,
announcement -> NumberUtils.parseLong(announcement.getCreator())));
return convertList(announcements, announcement -> {
PmsProjectAnnouncementRespVO announcementVO = BeanUtils.toBean(
announcement, PmsProjectAnnouncementRespVO.class)
.setCreatorUserId(NumberUtils.parseLong(announcement.getCreator()));
findAndThen(userMap, announcementVO.getCreatorUserId(),
user -> announcementVO.setCreatorUserName(user.getNickname()));
return announcementVO;
});
}
}
@@ -0,0 +1,207 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.project.PmsProjectOverviewRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.project.PmsProjectPageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.project.PmsProjectRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.project.PmsProjectSaveReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.project.PmsProjectDO;
import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemStatusTypeEnum;
import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectFavoriteService;
import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectMemberService;
import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectService;
import cn.iocoder.yudao.module.pms.service.pm.workitem.PmsWorkItemService;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 项目")
@RestController
@RequestMapping("/pms/pm/project")
@Validated
public class PmsProjectController {
@Resource
private PmsProjectService projectService;
@Resource
private PmsProjectMemberService projectMemberService;
@Resource
private PmsProjectFavoriteService projectFavoriteService;
@Resource
private PmsWorkItemService workItemService;
@Resource
private AdminUserApi adminUserApi;
@Resource
private PermissionApi permissionApi;
@PostMapping("/create")
@Operation(summary = "创建项目")
@PreAuthorize("@ss.hasPermission('pms:pm:project:create')")
public CommonResult<Long> createProject(@Valid @RequestBody PmsProjectSaveReqVO saveReqVO) {
return success(projectService.createProject(saveReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新项目")
@PreAuthorize("@ss.hasPermission('pms:pm:project:update')")
public CommonResult<Boolean> updateProject(@Valid @RequestBody PmsProjectSaveReqVO saveReqVO) {
projectService.updateProject(saveReqVO, getLoginUserId());
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得项目详情")
@Parameter(name = "id", description = "项目编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:project:query')")
public CommonResult<PmsProjectRespVO> getProject(@RequestParam("id") Long id) {
Long userId = getLoginUserId();
PmsProjectDO project = projectService.getProjectAndUpdateVisitTime(id, userId);
return success(buildProjectRespVO(project, userId));
}
@GetMapping("/page")
@Operation(summary = "获得项目分页")
@PreAuthorize("@ss.hasPermission('pms:pm:project:query')")
public CommonResult<PageResult<PmsProjectRespVO>> getProjectPage(@Valid PmsProjectPageReqVO pageReqVO) {
Long userId = getLoginUserId();
PageResult<PmsProjectDO> pageResult = projectService.getProjectPage(pageReqVO, userId);
return success(new PageResult<>(buildProjectRespVOList(pageResult.getList(), userId), pageResult.getTotal()));
}
@GetMapping("/favorite-list")
@Operation(summary = "获得星标项目列表")
@PreAuthorize("@ss.hasPermission('pms:pm:project:query')")
public CommonResult<List<PmsProjectRespVO>> getFavoriteProjectList() {
Long userId = getLoginUserId();
return success(buildProjectRespVOList(projectService.getFavoriteProjectList(userId), userId));
}
@GetMapping("/overview")
@Operation(summary = "获得项目概况")
@Parameter(name = "projectId", description = "项目编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:project:query')")
public CommonResult<PmsProjectOverviewRespVO> getProjectOverview(@RequestParam("projectId") Long projectId) {
return success(projectService.getProjectOverview(projectId, getLoginUserId()));
}
@PutMapping("/archive")
@Operation(summary = "归档项目")
@Parameter(name = "id", description = "项目编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:project:update')")
public CommonResult<Boolean> archiveProject(@RequestParam("id") Long id) {
projectService.archiveProject(id, getLoginUserId());
return success(true);
}
@PutMapping("/recycle")
@Operation(summary = "将项目移入回收站")
@Parameter(name = "id", description = "项目编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:project:update')")
public CommonResult<Boolean> recycleProject(@RequestParam("id") Long id) {
projectService.recycleProject(id, getLoginUserId());
return success(true);
}
@PutMapping("/restore")
@Operation(summary = "恢复项目")
@Parameter(name = "id", description = "项目编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:project:update')")
public CommonResult<Boolean> restoreProject(@RequestParam("id") Long id) {
projectService.restoreProject(id, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "彻底删除回收站项目")
@Parameter(name = "id", description = "项目编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:project:delete')")
public CommonResult<Boolean> deleteProject(@RequestParam("id") Long id) {
projectService.deleteProject(id, getLoginUserId());
return success(true);
}
// ==================== 拼接 VO ====================
private PmsProjectRespVO buildProjectRespVO(PmsProjectDO project, Long userId) {
if (project == null) {
return null;
}
return CollUtil.getFirst(buildProjectRespVOList(Collections.singletonList(project), userId));
}
private List<PmsProjectRespVO> buildProjectRespVOList(List<PmsProjectDO> projects, Long userId) {
if (CollUtil.isEmpty(projects)) {
return Collections.emptyList();
}
// 1. 批量查询创建人、项目管理员、成员数量和工作项完成度
Set<Long> projectIds = convertSet(projects, PmsProjectDO::getId);
Map<Long, List<Long>> managerUserIdListMap = projectMemberService.getProjectManagerUserIdListMap(projectIds);
Map<Long, Integer> memberCountMap = projectMemberService.getProjectMemberCountMap(projectIds);
Map<Long, Map<Integer, Long>> projectStatusCountMap = workItemService
.getProjectWorkItemStatusCountMap(projectIds);
Set<Long> userIds = convertSet(projects, project ->
NumberUtils.parseLong(project.getCreator()));
managerUserIdListMap.values().forEach(userIds::addAll);
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
Set<Long> ownerProjectIds = convertSet(projectMemberService.getOwnerProjectIdListByUserId(userId));
Set<Long> managedProjectIds = convertSet(projectMemberService.getManagedProjectIdListByUserId(userId));
Set<Long> memberProjectIds = convertSet(projectMemberService.getProjectIdListByUserId(userId));
Set<Long> writableProjectIds = convertSet(projectMemberService.getWritableProjectIdListByUserId(userId));
Set<Long> favoriteProjectIds = convertSet(projectFavoriteService.getFavoriteProjectIdListByUserId(userId));
favoriteProjectIds.retainAll(projectIds);
boolean superAdmin = permissionApi.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode());
// 2. 转换项目响应,并拼接展示字段
return BeanUtils.toBean(projects, PmsProjectRespVO.class, projectVO -> {
findAndThen(userMap, NumberUtils.parseLong(projectVO.getCreator()),
creator -> projectVO.setCreatorName(creator.getNickname()));
List<String> adminNames = new ArrayList<>();
for (Long adminUserId : managerUserIdListMap.getOrDefault(projectVO.getId(), Collections.emptyList())) {
findAndThen(userMap, adminUserId, admin -> adminNames.add(admin.getNickname()));
}
Map<Integer, Long> statusCountMap = projectStatusCountMap.getOrDefault(
projectVO.getId(), Collections.emptyMap());
projectVO.setAdminNames(adminNames).setMemberCount(memberCountMap.getOrDefault(projectVO.getId(), 0))
.setPendingWorkItemCount(statusCountMap.getOrDefault(PmsWorkItemStatusTypeEnum.PENDING.getType(), 0L))
.setProcessingWorkItemCount(statusCountMap.getOrDefault(PmsWorkItemStatusTypeEnum.PROCESSING.getType(), 0L))
.setCompletedWorkItemCount(statusCountMap.getOrDefault(PmsWorkItemStatusTypeEnum.COMPLETED.getType(), 0L))
.setOwnerStatus(superAdmin || ownerProjectIds.contains(projectVO.getId()))
.setAdminStatus(superAdmin || managedProjectIds.contains(projectVO.getId()))
.setMemberStatus(memberProjectIds.contains(projectVO.getId()))
.setExitStatus(memberProjectIds.contains(projectVO.getId()) && !ownerProjectIds.contains(projectVO.getId()))
.setWriteStatus(superAdmin || writableProjectIds.contains(projectVO.getId()))
.setFavoriteStatus(favoriteProjectIds.contains(projectVO.getId()));
});
}
}
@@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectFavoriteService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 项目收藏")
@RestController
@RequestMapping("/pms/pm/project-favorite")
@Validated
public class PmsProjectFavoriteController {
@Resource
private PmsProjectFavoriteService projectFavoriteService;
@PostMapping("/create")
@Operation(summary = "收藏项目")
@Parameter(name = "projectId", description = "项目编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:project:query')")
public CommonResult<Boolean> createProjectFavorite(@RequestParam("projectId") Long projectId) {
projectFavoriteService.createProjectFavorite(projectId, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "取消收藏项目")
@Parameter(name = "projectId", description = "项目编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:project:query')")
public CommonResult<Boolean> deleteProjectFavorite(@RequestParam("projectId") Long projectId) {
projectFavoriteService.deleteProjectFavorite(projectId, getLoginUserId());
return success(true);
}
}
@@ -0,0 +1,97 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.group.PmsProjectGroupMoveReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.group.PmsProjectGroupRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.group.PmsProjectGroupSaveReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.group.PmsProjectGroupSortReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.project.PmsProjectGroupDO;
import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectGroupService;
import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectMemberService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 项目分组")
@RestController
@RequestMapping("/pms/pm/project-group")
@Validated
public class PmsProjectGroupController {
@Resource
private PmsProjectGroupService projectGroupService;
@Resource
private PmsProjectMemberService projectMemberService;
@PostMapping("/create")
@Operation(summary = "创建项目分组")
@PreAuthorize("@ss.hasPermission('pms:pm:project-group:create')")
public CommonResult<Long> createProjectGroup(@Valid @RequestBody PmsProjectGroupSaveReqVO createReqVO) {
return success(projectGroupService.createProjectGroup(createReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "修改项目分组")
@PreAuthorize("@ss.hasPermission('pms:pm:project-group:update')")
public CommonResult<Boolean> updateProjectGroup(@Valid @RequestBody PmsProjectGroupSaveReqVO updateReqVO) {
projectGroupService.updateProjectGroup(updateReqVO, getLoginUserId());
return success(true);
}
@PutMapping("/update-sort")
@Operation(summary = "修改项目分组排序")
@PreAuthorize("@ss.hasPermission('pms:pm:project-group:update')")
public CommonResult<Boolean> updateProjectGroupSort(@Valid @RequestBody PmsProjectGroupSortReqVO sortReqVO) {
projectGroupService.updateProjectGroupSort(sortReqVO, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除项目分组")
@Parameter(name = "id", description = "项目分组编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:project-group:delete')")
public CommonResult<Boolean> deleteProjectGroup(@RequestParam("id") Long id) {
projectGroupService.deleteProjectGroup(id, getLoginUserId());
return success(true);
}
@GetMapping("/list")
@Operation(summary = "获得项目分组列表")
@PreAuthorize("@ss.hasPermission('pms:pm:project-group:query')")
public CommonResult<List<PmsProjectGroupRespVO>> getProjectGroupList() {
Long userId = getLoginUserId();
List<PmsProjectGroupDO> groups = projectGroupService.getProjectGroupList(userId);
List<Long> projectIds = projectMemberService.getActiveProjectIdListByUserId(userId);
Map<Long, Integer> groupCountMap = projectGroupService.getProjectGroupCountMap(userId, groups, projectIds);
return success(BeanUtils.toBean(groups, PmsProjectGroupRespVO.class,
group -> group.setProjectCount(groupCountMap.getOrDefault(group.getId(), 0))));
}
@PutMapping("/move-project")
@Operation(summary = "移动项目到个人分组")
@PreAuthorize("@ss.hasPermission('pms:pm:project-group:update')")
public CommonResult<Boolean> moveProjectToGroup(@Valid @RequestBody PmsProjectGroupMoveReqVO moveReqVO) {
projectGroupService.moveProjectToGroup(moveReqVO, getLoginUserId());
return success(true);
}
}
@@ -0,0 +1,104 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.collection.MapUtils;
import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.member.PmsProjectMemberRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.member.PmsProjectMemberSaveReqVO;
import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectMemberService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 项目成员")
@RestController
@RequestMapping("/pms/pm/project-member")
@Validated
public class PmsProjectMemberController {
@Resource
private PmsProjectMemberService projectMemberService;
@Resource
private AdminUserApi adminUserApi;
@GetMapping("/list")
@Operation(summary = "获得项目成员列表")
@Parameter(name = "projectId", description = "项目编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:project-member:query')")
public CommonResult<List<PmsProjectMemberRespVO>> getProjectMemberList(
@RequestParam("projectId") Long projectId) {
List<PmsProjectMemberRespVO> members = projectMemberService.getProjectMemberList(projectId, getLoginUserId());
return success(fillProjectMemberRespVOList(members));
}
@PutMapping("/update-list")
@Operation(summary = "保存项目成员及其权限级别")
@PreAuthorize("@ss.hasPermission('pms:pm:project-member:update')")
public CommonResult<Boolean> updateProjectMemberList(@Valid @RequestBody PmsProjectMemberSaveReqVO saveReqVO) {
projectMemberService.updateProjectMemberList(saveReqVO, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "移除项目成员")
@Parameters({
@Parameter(name = "projectId", description = "项目编号", required = true, example = "1024"),
@Parameter(name = "userId", description = "后台用户编号", required = true, example = "1")
})
@PreAuthorize("@ss.hasPermission('pms:pm:project-member:update')")
public CommonResult<Boolean> deleteProjectMember(@RequestParam("projectId") Long projectId,
@RequestParam("userId") Long userId) {
projectMemberService.deleteProjectMember(projectId, userId, getLoginUserId());
return success(true);
}
@DeleteMapping("/exit")
@Operation(summary = "主动退出项目")
@Parameter(name = "projectId", description = "项目编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:project-member:query')")
public CommonResult<Boolean> exitProject(@RequestParam("projectId") Long projectId) {
projectMemberService.exitProject(projectId, getLoginUserId());
return success(true);
}
// ==================== 拼接 VO ====================
private List<PmsProjectMemberRespVO> fillProjectMemberRespVOList(List<PmsProjectMemberRespVO> members) {
if (CollUtil.isEmpty(members)) {
return Collections.emptyList();
}
// 1. 批量查询用户
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSet(members, PmsProjectMemberRespVO::getUserId));
// 2. 拼接用户信息
for (PmsProjectMemberRespVO memberVO : members) {
MapUtils.findAndThen(userMap, memberVO.getUserId(),
user -> memberVO.setNickname(user.getNickname()).setAvatar(user.getAvatar()));
}
return members;
}
}
@@ -0,0 +1,81 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.template.PmsProjectTemplatePageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.template.PmsProjectTemplateRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.template.PmsProjectTemplateSaveReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.project.PmsProjectTemplateDO;
import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectTemplateService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
@Tag(name = "管理后台 - PMS 项目模板")
@RestController
@RequestMapping("/pms/pm/project-template")
@Validated
public class PmsProjectTemplateController {
@Resource
private PmsProjectTemplateService projectTemplateService;
@PostMapping("/create")
@Operation(summary = "创建项目模板")
@PreAuthorize("@ss.hasPermission('pms:pm:project-template:create')")
public CommonResult<Long> createProjectTemplate(@Valid @RequestBody PmsProjectTemplateSaveReqVO createReqVO) {
return success(projectTemplateService.createProjectTemplate(createReqVO));
}
@PutMapping("/update")
@Operation(summary = "更新项目模板")
@PreAuthorize("@ss.hasPermission('pms:pm:project-template:update')")
public CommonResult<Boolean> updateProjectTemplate(@Valid @RequestBody PmsProjectTemplateSaveReqVO updateReqVO) {
projectTemplateService.updateProjectTemplate(updateReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除项目模板")
@Parameter(name = "id", description = "模板编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:project-template:delete')")
public CommonResult<Boolean> deleteProjectTemplate(@RequestParam("id") Long id) {
projectTemplateService.deleteProjectTemplate(id);
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得项目模板")
@Parameter(name = "id", description = "模板编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:project-template:query')")
public CommonResult<PmsProjectTemplateRespVO> getProjectTemplate(@RequestParam("id") Long id) {
PmsProjectTemplateDO template = projectTemplateService.getProjectTemplate(id);
return success(BeanUtils.toBean(template, PmsProjectTemplateRespVO.class));
}
@GetMapping("/page")
@Operation(summary = "获得项目模板分页")
@PreAuthorize("@ss.hasPermission('pms:pm:project-template:query')")
public CommonResult<PageResult<PmsProjectTemplateRespVO>> getProjectTemplatePage(
@Valid PmsProjectTemplatePageReqVO pageReqVO) {
PageResult<PmsProjectTemplateDO> pageResult = projectTemplateService.getProjectTemplatePage(pageReqVO);
return success(BeanUtils.toBean(pageResult, PmsProjectTemplateRespVO.class));
}
}
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.announcement;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - PMS 项目公告 Response VO")
@Data
public class PmsProjectAnnouncementRespVO {
@Schema(description = "公告编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private Long projectId;
@Schema(description = "公告内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "项目启动")
private String content;
@Schema(description = "附件地址列表", example = "[\"https://example.com/a.pdf\"]")
private List<String> fileUrls;
@Schema(description = "创建者用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long creatorUserId;
@Schema(description = "创建者用户昵称", example = "芋道")
private String creatorUserName;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
@Schema(description = "更新时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime updateTime;
}
@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.announcement;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - PMS 项目公告保存 Request VO")
@Data
public class PmsProjectAnnouncementSaveReqVO {
@Schema(description = "公告编号", example = "1024")
private Long id;
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "项目编号不能为空")
private Long projectId;
@Schema(description = "公告内容", requiredMode = Schema.RequiredMode.REQUIRED)
@NotBlank(message = "公告内容不能为空")
@Size(max = 5000, message = "公告内容不能超过 5000 个字符")
private String content;
@Schema(description = "附件地址列表")
private List<String> fileUrls;
}
@@ -0,0 +1,18 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.group;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
@Schema(description = "管理后台 - PMS 项目移动分组 Request VO")
@Data
public class PmsProjectGroupMoveReqVO {
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "项目编号不能为空")
private Long projectId;
@Schema(description = "目标项目分组编号;不传表示移动到未分组", example = "2048")
private Long groupId;
}
@@ -0,0 +1,30 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.group;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - PMS 项目分组 Response VO")
@Data
public class PmsProjectGroupRespVO {
@Schema(description = "项目分组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "分组名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "重点项目")
private String name;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer sort;
@Schema(description = "分组类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer type;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "项目数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
private Integer projectCount;
}
@@ -0,0 +1,20 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.group;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - PMS 项目分组新增/修改 Request VO")
@Data
public class PmsProjectGroupSaveReqVO {
@Schema(description = "项目分组编号", example = "1024")
private Long id;
@Schema(description = "分组名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "重点项目")
@NotBlank(message = "分组名称不能为空")
@Size(max = 100, message = "分组名称不能超过 100 个字符")
private String name;
}
@@ -0,0 +1,36 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.group;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Min;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - PMS 项目分组排序 Request VO")
@Data
public class PmsProjectGroupSortReqVO {
@Schema(description = "项目分组排序项", requiredMode = Schema.RequiredMode.REQUIRED)
@Valid
@NotEmpty(message = "项目分组排序项不能为空")
private List<Item> items;
@Schema(description = "管理后台 - PMS 项目分组排序项")
@Data
public static class Item {
@Schema(description = "项目分组编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "项目分组编号不能为空")
private Long id;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "显示顺序不能为空")
@Min(value = 0, message = "显示顺序不能小于 0")
private Integer sort;
}
}
@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.member;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - PMS 项目成员 Response VO")
@Data
public class PmsProjectMemberRespVO {
@Schema(description = "后台用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long userId;
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "芋道源码")
private String nickname;
@Schema(description = "用户头像")
private String avatar;
@Schema(description = "成员权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer level;
@Schema(description = "是否项目创建人", example = "true")
private Boolean creatorStatus;
}
@@ -0,0 +1,41 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.member;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectMemberLevelEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - PMS 项目成员保存 Request VO")
@Data
public class PmsProjectMemberSaveReqVO {
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "项目编号不能为空")
private Long projectId;
@Schema(description = "项目成员列表", requiredMode = Schema.RequiredMode.REQUIRED)
@NotEmpty(message = "项目成员不能为空")
@Valid
private List<Member> members;
@Schema(description = "管理后台 - PMS 项目成员保存项")
@Data
public static class Member {
@Schema(description = "后台用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "后台用户编号不能为空")
private Long userId;
@Schema(description = "成员权限级别", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
@NotNull(message = "成员权限级别不能为空")
@InEnum(PmsProjectMemberLevelEnum.class)
private Integer level;
}
}
@@ -0,0 +1,74 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.project;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
import java.util.Map;
@Schema(description = "管理后台 - PMS 项目概况 Response VO")
@Data
public class PmsProjectOverviewRespVO {
@Schema(description = "工作项总数", requiredMode = Schema.RequiredMode.REQUIRED, example = "20")
private Long totalCount;
@Schema(description = "未开始工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "8")
private Long pendingCount;
@Schema(description = "进行中工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "6")
private Long processingCount;
@Schema(description = "已完成工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "6")
private Long completedCount;
@Schema(description = "按工作项类型统计")
private Map<Integer, Long> typeCountMap;
@Schema(description = "近十四日完成趋势")
private List<TrendPoint> completedTrends;
@Schema(description = "分配给当前用户的未完成工作项")
private List<AssignedWorkItem> assignedWorkItems;
@Schema(description = "PMS 项目概况 - 趋势点")
@Data
public static class TrendPoint {
@Schema(description = "日期", requiredMode = Schema.RequiredMode.REQUIRED)
private String date;
@Schema(description = "当日完成数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Long count;
}
@Schema(description = "PMS 项目概况 - 分配给我的工作项")
@Data
public static class AssignedWorkItem {
@Schema(description = "工作项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "项目内序号", requiredMode = Schema.RequiredMode.REQUIRED, example = "12")
private Integer serialNumber;
@Schema(description = "工作项类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer type;
@Schema(description = "工作项标题", requiredMode = Schema.RequiredMode.REQUIRED)
private String name;
@Schema(description = "语义状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer status;
@Schema(description = "截止时间")
private LocalDateTime endTime;
@Schema(description = "完成进度", requiredMode = Schema.RequiredMode.REQUIRED, example = "60")
private Integer progress;
}
}
@@ -0,0 +1,37 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.project;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectSceneTypeEnum;
import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectSortTypeEnum;
import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectStatusEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
@Schema(description = "管理后台 - PMS 项目分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PmsProjectPageReqVO extends PageParam {
@Schema(description = "项目名称", example = "官网")
private String name;
@Schema(description = "列表场景", example = "1")
@InEnum(PmsProjectSceneTypeEnum.class)
private Integer sceneType = PmsProjectSceneTypeEnum.ALL.getType();
@Schema(description = "个人项目分组编号", example = "1024")
private Long groupId;
@Schema(description = "项目状态", example = "1")
@InEnum(PmsProjectStatusEnum.class)
private Integer status = PmsProjectStatusEnum.ACTIVE.getStatus();
@Schema(description = "排序类型", example = "1")
@InEnum(PmsProjectSortTypeEnum.class)
private Integer sortType = PmsProjectSortTypeEnum.ACCESS_TIME.getType();
}
@@ -0,0 +1,94 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.project;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - PMS 项目 Response VO")
@Data
public class PmsProjectRespVO {
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "官网重构")
private String name;
@Schema(description = "项目状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status;
@Schema(description = "项目类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer type;
@Schema(description = "项目优先级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Integer level;
@Schema(description = "项目描述", example = "完成官网前后端重构")
private String description;
@Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean openStatus;
@Schema(description = "项目图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "ep:folder")
private String icon;
@Schema(description = "开始时间")
private LocalDateTime startTime;
@Schema(description = "截止时间")
private LocalDateTime endTime;
@Schema(description = "归档时间")
private LocalDateTime archiveTime;
@Schema(description = "移入回收站时间")
private LocalDateTime recycleTime;
@Schema(description = "最近访问时间")
private LocalDateTime accessTime;
@Schema(description = "创建时间")
private LocalDateTime createTime;
@Schema(description = "创建人编号", example = "1")
private String creator;
@Schema(description = "创建人姓名", example = "芋道源码")
private String creatorName;
@Schema(description = "项目管理员姓名列表")
private List<String> adminNames;
@Schema(description = "项目成员数量", example = "5")
private Integer memberCount;
@Schema(description = "未开始工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
private Long pendingWorkItemCount;
@Schema(description = "进行中工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Long processingWorkItemCount;
@Schema(description = "已完成工作项数量", requiredMode = Schema.RequiredMode.REQUIRED, example = "5")
private Long completedWorkItemCount;
@Schema(description = "当前用户是否为项目管理员", example = "true")
private Boolean adminStatus;
@Schema(description = "当前用户是否拥有项目所有者权限", example = "true")
private Boolean ownerStatus;
@Schema(description = "当前用户是否为项目成员", example = "true")
private Boolean memberStatus;
@Schema(description = "当前用户是否可以主动退出项目", requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean exitStatus;
@Schema(description = "当前用户是否可以编辑项目业务数据", requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean writeStatus;
@Schema(description = "当前用户是否已收藏", requiredMode = Schema.RequiredMode.REQUIRED)
private Boolean favoriteStatus;
}
@@ -0,0 +1,73 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.project;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectLevelEnum;
import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectTypeEnum;
import com.fasterxml.jackson.annotation.JsonIgnore;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.AssertTrue;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import java.util.List;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - PMS 项目新增/修改 Request VO")
@Data
public class PmsProjectSaveReqVO {
@Schema(description = "项目编号", example = "1024")
private Long id;
@Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "官网重构")
@NotBlank(message = "项目名称不能为空")
@Size(max = 31, message = "项目名称不能超过 31 个字符")
private String name;
@Schema(description = "项目类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "项目类型不能为空")
@InEnum(PmsProjectTypeEnum.class)
private Integer type;
@Schema(description = "项目优先级", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
@NotNull(message = "项目优先级不能为空")
@InEnum(PmsProjectLevelEnum.class)
private Integer level;
@Schema(description = "项目描述", example = "完成官网前后端重构")
@Size(max = 500, message = "项目描述不能超过 500 个字符")
private String description;
@Schema(description = "是否公开", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "项目可见范围不能为空")
private Boolean openStatus;
@Schema(description = "项目图标", requiredMode = Schema.RequiredMode.REQUIRED, example = "ep:folder")
@NotBlank(message = "项目图标不能为空")
private String icon;
@Schema(description = "开始时间", example = "2026-08-01 00:00:00")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime startTime;
@Schema(description = "截止时间", example = "2026-12-31 23:59:59")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime endTime;
@Schema(description = "初始成员编号列表", example = "[1, 2]")
private List<Long> memberUserIds;
@JsonIgnore
@Schema(hidden = true)
@AssertTrue(message = "项目截止时间必须晚于开始时间,且开始、截止时间需要同时填写")
public boolean isTimeValid() {
return startTime == null && endTime == null
|| startTime != null && endTime != null && startTime.isBefore(endTime);
}
}
@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.template;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Schema(description = "管理后台 - PMS 项目模板分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
public class PmsProjectTemplatePageReqVO extends PageParam {
@Schema(description = "模板名称,模糊匹配", example = "敏捷研发模板")
private String name;
@Schema(description = "项目类型", example = "2")
@InEnum(PmsProjectTypeEnum.class)
private Integer projectType;
@Schema(description = "模板状态", example = "0")
private Integer status;
}
@@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.template;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - PMS 项目模板 Response VO")
@Data
public class PmsProjectTemplateRespVO {
@Schema(description = "模板编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "模板名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "敏捷研发模板")
private String name;
@Schema(description = "模板描述", example = "适用于标准敏捷研发项目")
private String description;
@Schema(description = "项目类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer projectType;
@Schema(description = "模板状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
private Integer status;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer sort;
@Schema(description = "启用的工作项类型列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<Integer> itemTypes;
@Schema(description = "工作项状态模板列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<PmsProjectTemplateSaveReqVO.StatusTemplate> statuses;
@Schema(description = "看板列模板列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<PmsProjectTemplateSaveReqVO.BoardTemplate> boards;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
@@ -0,0 +1,126 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.project.vo.template;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectTypeEnum;
import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemStatusTypeEnum;
import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.Valid;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - PMS 项目模板新增/修改 Request VO")
@Data
public class PmsProjectTemplateSaveReqVO {
@Schema(description = "模板编号", example = "1024")
private Long id;
@Schema(description = "模板名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "敏捷研发模板")
@NotBlank(message = "模板名称不能为空")
@Size(max = 100, message = "模板名称不能超过 100 个字符")
private String name;
@Schema(description = "模板描述", example = "适用于标准敏捷研发项目")
@Size(max = 500, message = "模板描述不能超过 500 个字符")
private String description;
@Schema(description = "项目类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
@NotNull(message = "项目类型不能为空")
@InEnum(PmsProjectTypeEnum.class)
private Integer projectType;
@Schema(description = "模板状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "0")
@NotNull(message = "模板状态不能为空")
private Integer status;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "显示顺序不能为空")
private Integer sort;
@Schema(description = "启用的工作项类型列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[2, 3, 4]")
@NotEmpty(message = "工作项类型不能为空")
private List<Integer> itemTypes;
@Schema(description = "工作项状态模板列表", requiredMode = Schema.RequiredMode.REQUIRED)
@Valid
@NotEmpty(message = "工作项状态不能为空")
private List<StatusTemplate> statuses;
@Schema(description = "看板列模板列表", requiredMode = Schema.RequiredMode.REQUIRED)
@Valid
@NotEmpty(message = "看板列不能为空")
private List<BoardTemplate> boards;
@Schema(description = "工作项状态模板")
@Data
public static class StatusTemplate {
@Schema(description = "模板内稳定编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "task_todo")
@NotBlank(message = "状态编码不能为空")
@Size(max = 50, message = "状态编码不能超过 50 个字符")
private String code;
@Schema(description = "状态名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "待处理")
@NotBlank(message = "状态名称不能为空")
@Size(max = 50, message = "状态名称不能超过 50 个字符")
private String name;
@Schema(description = "工作项类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
@NotNull(message = "状态工作项类型不能为空")
@InEnum(PmsWorkItemTypeEnum.class)
private Integer workItemType;
@Schema(description = "语义状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
@NotNull(message = "语义状态不能为空")
@InEnum(PmsWorkItemStatusTypeEnum.class)
private Integer statusType;
@Schema(description = "是否为初始状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
@NotNull(message = "是否为初始状态不能为空")
private Boolean defaultStatus;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "状态显示顺序不能为空")
private Integer sort;
@Schema(description = "所属看板列编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "todo")
@NotBlank(message = "状态所属看板列不能为空")
private String boardCode;
}
@Schema(description = "看板列模板")
@Data
public static class BoardTemplate {
@Schema(description = "模板内稳定编码", requiredMode = Schema.RequiredMode.REQUIRED, example = "todo")
@NotBlank(message = "看板列编码不能为空")
@Size(max = 50, message = "看板列编码不能超过 50 个字符")
private String code;
@Schema(description = "看板列名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "待处理")
@NotBlank(message = "看板列名称不能为空")
@Size(max = 50, message = "看板列名称不能超过 50 个字符")
private String name;
@Schema(description = "工作项类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "3")
@NotNull(message = "看板列工作项类型不能为空")
@InEnum(PmsWorkItemTypeEnum.class)
private Integer workItemType;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "看板列显示顺序不能为空")
private Integer sort;
@Schema(description = "关联的状态编码列表", requiredMode = Schema.RequiredMode.REQUIRED,
example = "[\"task_todo\"]")
@NotEmpty(message = "看板列关联状态不能为空")
private List<String> statusCodes;
}
}
@@ -0,0 +1,124 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.workbench;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workbench.vo.PmsWorkbenchCountRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workbench.vo.PmsWorkbenchIterationRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workbench.vo.PmsWorkbenchPageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workbench.vo.PmsWorkbenchWorkItemRespVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.iteration.PmsIterationDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.project.PmsProjectDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemStatusDO;
import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectMemberService;
import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectService;
import cn.iocoder.yudao.module.pms.service.pm.workbench.PmsWorkbenchService;
import cn.iocoder.yudao.module.pms.service.pm.workitem.PmsWorkItemStatusService;
import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import cn.iocoder.yudao.module.system.enums.permission.RoleCodeEnum;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 工作台")
@RestController
@RequestMapping("/pms/pm/workbench")
@Validated
public class PmsWorkbenchController {
@Resource
private PmsWorkbenchService workbenchService;
@Resource
private PmsProjectService projectService;
@Resource
private PmsProjectMemberService projectMemberService;
@Resource
private PmsWorkItemStatusService workItemStatusService;
@Resource
private PermissionApi permissionApi;
@Resource
private AdminUserApi adminUserApi;
@GetMapping("/work-item-page")
@Operation(summary = "获得我负责的工作项分页")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:query')")
public CommonResult<PageResult<PmsWorkbenchWorkItemRespVO>> getWorkbenchWorkItemPage(
@Valid PmsWorkbenchPageReqVO pageReqVO) {
// 1. 查询当前用户负责的工作项分页
Long userId = getLoginUserId();
PageResult<PmsWorkItemDO> pageResult = workbenchService.getWorkbenchWorkItemPage(pageReqVO, userId);
// 2.1 批量查询项目、状态及项目写权限
Map<Long, PmsProjectDO> projectMap = projectService.getProjectMap(
convertSet(pageResult.getList(), PmsWorkItemDO::getProjectId));
Map<Long, PmsWorkItemStatusDO> statusMap = workItemStatusService.getWorkItemStatusMap(
convertSet(pageResult.getList(), PmsWorkItemDO::getStatusId));
Set<Long> writableProjectIds = convertSet(projectMemberService.getWritableProjectIdListByUserId(userId));
boolean superAdmin = permissionApi.hasAnyRoles(userId, RoleCodeEnum.SUPER_ADMIN.getCode());
// 2.2 批量查询负责人和创建人
Set<Long> userIds = convertSet(pageResult.getList(), PmsWorkItemDO::getAssigneeUserId);
userIds.addAll(convertSet(pageResult.getList(), workItem -> NumberUtils.parseLong(workItem.getCreator())));
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
Map<Long, Long> creatorUserIdMap = convertMap(pageResult.getList(), PmsWorkItemDO::getId,
workItem -> NumberUtils.parseLong(workItem.getCreator()));
// 3. 转换响应 VO,并拼接项目、状态、用户及写权限
List<PmsWorkbenchWorkItemRespVO> workItemVOList = BeanUtils.toBean(pageResult.getList(),
PmsWorkbenchWorkItemRespVO.class, itemVO -> {
findAndThen(projectMap, itemVO.getProjectId(), project -> itemVO
.setProjectName(project.getName()).setProjectType(project.getType()));
findAndThen(statusMap, itemVO.getStatusId(), status -> itemVO.setStatusName(status.getName()));
findAndThen(userMap, itemVO.getAssigneeUserId(),
user -> itemVO.setAssigneeUserName(user.getNickname()));
itemVO.setCreatorUserId(creatorUserIdMap.get(itemVO.getId()));
findAndThen(userMap, itemVO.getCreatorUserId(),
user -> itemVO.setCreatorUserName(user.getNickname()));
itemVO.setWriteStatus(superAdmin || writableProjectIds.contains(itemVO.getProjectId()));
});
return success(new PageResult<>(workItemVOList, pageResult.getTotal()));
}
@GetMapping("/iteration-page")
@Operation(summary = "获得我负责的迭代分页")
@PreAuthorize("@ss.hasPermission('pms:pm:iteration:query')")
public CommonResult<PageResult<PmsWorkbenchIterationRespVO>> getWorkbenchIterationPage(
@Valid PmsWorkbenchPageReqVO pageReqVO) {
// 1.1 查询当前用户负责的迭代分页
PageResult<PmsIterationDO> pageResult = workbenchService.getWorkbenchIterationPage(pageReqVO, getLoginUserId());
// 1.2 批量查询关联项目
Map<Long, PmsProjectDO> projectMap = projectService.getProjectMap(
convertSet(pageResult.getList(), PmsIterationDO::getProjectId));
// 2. 转换响应 VO,并拼接项目展示字段
List<PmsWorkbenchIterationRespVO> iterationVOList = BeanUtils.toBean(pageResult.getList(),
PmsWorkbenchIterationRespVO.class, itemVO ->
findAndThen(projectMap, itemVO.getProjectId(), project -> itemVO
.setProjectName(project.getName()).setProjectType(project.getType())));
return success(new PageResult<>(iterationVOList, pageResult.getTotal()));
}
@GetMapping("/count")
@Operation(summary = "获得我的未完成事项数量")
@PreAuthorize("@ss.hasAnyPermissions('pms:pm:work-item:query', 'pms:pm:iteration:query')")
public CommonResult<PmsWorkbenchCountRespVO> getWorkbenchCount(PmsWorkbenchPageReqVO pageReqVO) {
return success(workbenchService.getWorkbenchCount(pageReqVO, getLoginUserId()));
}
}
@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.workbench.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
@Schema(description = "管理后台 - PMS 工作台数量响应 VO")
@Data
@Accessors(chain = true)
public class PmsWorkbenchCountRespVO {
@Schema(description = "需求数量", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer requirementCount;
@Schema(description = "任务数量", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer taskCount;
@Schema(description = "缺陷数量", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer defectCount;
@Schema(description = "迭代数量", requiredMode = Schema.RequiredMode.REQUIRED)
private Integer iterationCount;
}
@@ -0,0 +1,39 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.workbench.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - PMS 工作台迭代 Response VO")
@Data
public class PmsWorkbenchIterationRespVO {
@Schema(description = "迭代编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private Long projectId;
@Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "商城项目")
private String projectName;
@Schema(description = "项目类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer projectType;
@Schema(description = "迭代名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "第一个迭代")
private String name;
@Schema(description = "迭代状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer status;
@Schema(description = "开始时间", example = "2026-01-01 00:00:00")
private LocalDateTime startTime;
@Schema(description = "结束时间", example = "2026-01-31 23:59:59")
private LocalDateTime endTime;
@Schema(description = "迭代目标", example = "完成登录模块")
private String target;
}
@@ -0,0 +1,47 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.workbench.vo;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.validation.InEnum;
import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemPriorityEnum;
import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemTypeEnum;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import org.springframework.format.annotation.DateTimeFormat;
import java.time.LocalDateTime;
import static cn.iocoder.yudao.framework.common.util.date.DateUtils.FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND;
@Schema(description = "管理后台 - PMS 工作台分页 Request VO")
@Data
@EqualsAndHashCode(callSuper = true)
@ToString(callSuper = true)
public class PmsWorkbenchPageReqVO extends PageParam {
@Schema(description = "项目编号", example = "1024")
private Long projectId;
@Schema(description = "工作项类型", example = "3")
@InEnum(PmsWorkItemTypeEnum.class)
private Integer type;
@Schema(description = "名称", example = "登录")
private String name;
@Schema(description = "状态,工作项为语义状态,迭代为迭代状态", example = "2")
private Integer status;
@Schema(description = "优先级", example = "2")
@InEnum(PmsWorkItemPriorityEnum.class)
private Integer priority;
@Schema(description = "迭代编号", example = "1024")
private Long iterationId;
@Schema(description = "截止时间范围")
@DateTimeFormat(pattern = FORMAT_YEAR_MONTH_DAY_HOUR_MINUTE_SECOND)
private LocalDateTime[] endTime;
}
@@ -0,0 +1,72 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.workbench.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - PMS 工作台工作项 Response VO")
@Data
public class PmsWorkbenchWorkItemRespVO {
@Schema(description = "工作项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private Long projectId;
@Schema(description = "项目名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "商城项目")
private String projectName;
@Schema(description = "项目类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer projectType;
@Schema(description = "工作项类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "2")
private Integer type;
@Schema(description = "工作项序号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer serialNumber;
@Schema(description = "工作项名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "优化登录页")
private String name;
@Schema(description = "优先级", example = "2")
private Integer priority;
@Schema(description = "负责人用户编号", example = "1024")
private Long assigneeUserId;
@Schema(description = "负责人姓名", example = "芋道源码")
private String assigneeUserName;
@Schema(description = "创建人用户编号", example = "2048")
private Long creatorUserId;
@Schema(description = "创建人姓名", example = "源码")
private String creatorUserName;
@Schema(description = "状态编号", example = "1")
private Long statusId;
@Schema(description = "状态名称", example = "进行中")
private String statusName;
@Schema(description = "语义状态", example = "2")
private Integer status;
@Schema(description = "进度", example = "60")
private Integer progress;
@Schema(description = "开始时间", example = "2026-01-01 10:00:00")
private LocalDateTime startTime;
@Schema(description = "结束时间", example = "2026-01-31 18:00:00")
private LocalDateTime endTime;
@Schema(description = "是否可编辑", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean writeStatus;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
@@ -0,0 +1,62 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.workitem;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.activity.PmsWorkItemActivityRespVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemActivityDO;
import cn.iocoder.yudao.module.pms.service.pm.workitem.PmsWorkItemActivityService;
import cn.iocoder.yudao.module.pms.service.pm.workitem.PmsWorkItemService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 工作项动态")
@RestController
@RequestMapping("/pms/pm/work-item-activity")
@Validated
public class PmsWorkItemActivityController {
@Resource
private PmsWorkItemActivityService activityService;
@Resource
private PmsWorkItemService workItemService;
@Resource
private AdminUserApi adminUserApi;
@GetMapping("/list")
@Operation(summary = "获得工作项动态列表")
@Parameter(name = "workItemId", description = "工作项编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:query')")
public CommonResult<List<PmsWorkItemActivityRespVO>> getWorkItemActivityList(
@RequestParam("workItemId") Long workItemId) {
workItemService.getWorkItem(workItemId, getLoginUserId());
List<PmsWorkItemActivityDO> activities = activityService.getWorkItemActivityList(workItemId);
// 拼接 VO:批量查询操作人,并补充用户展示字段
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(
convertSet(activities, PmsWorkItemActivityDO::getOperatorUserId));
List<PmsWorkItemActivityRespVO> activityVOList = BeanUtils.toBean(
activities, PmsWorkItemActivityRespVO.class, activityVO ->
findAndThen(userMap, activityVO.getOperatorUserId(), user -> activityVO
.setOperatorUserName(user.getNickname()).setOperatorUserAvatar(user.getAvatar())));
return success(activityVOList);
}
}
@@ -0,0 +1,104 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.workitem;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.comment.PmsWorkItemCommentRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.comment.PmsWorkItemCommentSaveReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemCommentDO;
import cn.iocoder.yudao.module.pms.service.pm.workitem.PmsWorkItemCommentService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 工作项评论")
@RestController
@RequestMapping("/pms/pm/work-item-comment")
@Validated
public class PmsWorkItemCommentController {
@Resource
private PmsWorkItemCommentService commentService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建工作项评论")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Long> createWorkItemComment(@Valid @RequestBody PmsWorkItemCommentSaveReqVO saveReqVO) {
return success(commentService.createWorkItemComment(saveReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "修改工作项评论")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> updateWorkItemComment(@Valid @RequestBody PmsWorkItemCommentSaveReqVO saveReqVO) {
commentService.updateWorkItemComment(saveReqVO, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除工作项评论")
@Parameter(name = "id", description = "评论编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> deleteWorkItemComment(@RequestParam("id") Long id) {
commentService.deleteWorkItemComment(id, getLoginUserId());
return success(true);
}
@GetMapping("/list")
@Operation(summary = "获得工作项评论列表")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:query')")
public CommonResult<List<PmsWorkItemCommentRespVO>> getWorkItemCommentList(
@RequestParam("workItemId") Long workItemId) {
List<PmsWorkItemCommentDO> comments = commentService.getWorkItemCommentList(workItemId, getLoginUserId());
// 拼接 VO:批量查询评论人和回复对象
Set<Long> userIds = convertSet(comments, PmsWorkItemCommentDO::getUserId);
userIds.addAll(convertSet(comments, PmsWorkItemCommentDO::getReplyUserId));
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
// 拼接 VO:转换评论,并按主评论组织回复列表
Map<Long, PmsWorkItemCommentRespVO> mainCommentMap = new LinkedHashMap<>();
List<PmsWorkItemCommentRespVO> mainComments = new ArrayList<>();
for (PmsWorkItemCommentDO comment : comments) {
PmsWorkItemCommentRespVO commentVO = BeanUtils.toBean(comment, PmsWorkItemCommentRespVO.class).setChildren(new ArrayList<>());
findAndThen(userMap, comment.getUserId(), user -> commentVO.setUserName(user.getNickname()));
findAndThen(userMap, comment.getReplyUserId(), user -> commentVO.setReplyUserName(user.getNickname()));
if (PmsWorkItemCommentDO.MAIN_ID_ROOT.equals(comment.getMainId())) {
mainCommentMap.put(comment.getId(), commentVO);
mainComments.add(commentVO);
} else {
PmsWorkItemCommentRespVO mainComment = mainCommentMap.get(comment.getMainId());
if (mainComment != null) {
mainComment.getChildren().add(commentVO);
}
}
}
return success(mainComments);
}
}
@@ -0,0 +1,299 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.workitem;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.pojo.PageParam;
import cn.iocoder.yudao.framework.common.pojo.PageResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.framework.excel.core.util.ExcelUtils;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.label.PmsWorkItemLabelRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.status.PmsWorkItemStatusUpdateReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.workitem.PmsWorkItemBoardReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.workitem.PmsWorkItemBoardRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.workitem.PmsWorkItemImportExcelVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.workitem.PmsWorkItemImportRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.workitem.PmsWorkItemIterationUpdateReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.workitem.PmsWorkItemNameUpdateReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.workitem.PmsWorkItemPageReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.workitem.PmsWorkItemPlanningSortReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.workitem.PmsWorkItemRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.workitem.PmsWorkItemSaveReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.workitem.PmsWorkItemSortReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.iteration.PmsIterationDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemLabelDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemStatusDO;
import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemPriorityEnum;
import cn.iocoder.yudao.module.pms.service.pm.iteration.PmsIterationService;
import cn.iocoder.yudao.module.pms.service.pm.workitem.PmsWorkItemLabelService;
import cn.iocoder.yudao.module.pms.service.pm.workitem.PmsWorkItemService;
import cn.iocoder.yudao.module.pms.service.pm.workitem.PmsWorkItemStatusService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.time.LocalDateTime;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertListByFlatMap;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSetByFlatMap;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 工作项")
@RestController
@RequestMapping("/pms/pm/work-item")
@Validated
public class PmsWorkItemController {
@Resource
private PmsWorkItemService workItemService;
@Resource
private PmsWorkItemLabelService workItemLabelService;
@Resource
private PmsWorkItemStatusService workItemStatusService;
@Resource
private PmsIterationService iterationService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建工作项")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:create')")
public CommonResult<Long> createWorkItem(@Valid @RequestBody PmsWorkItemSaveReqVO saveReqVO) {
return success(workItemService.createWorkItem(saveReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新工作项")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> updateWorkItem(@Valid @RequestBody PmsWorkItemSaveReqVO saveReqVO) {
workItemService.updateWorkItem(saveReqVO, getLoginUserId());
return success(true);
}
@PutMapping("/update-name")
@Operation(summary = "更新工作项名称")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> updateWorkItemName(@Valid @RequestBody PmsWorkItemNameUpdateReqVO updateReqVO) {
workItemService.updateWorkItemName(updateReqVO, getLoginUserId());
return success(true);
}
@PutMapping("/update-status")
@Operation(summary = "更新工作项状态")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> updateWorkItemStatus(@Valid @RequestBody PmsWorkItemStatusUpdateReqVO updateReqVO) {
workItemService.updateWorkItemStatus(updateReqVO, getLoginUserId());
return success(true);
}
@PutMapping("/update-iteration")
@Operation(summary = "更新工作项所属迭代")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> updateWorkItemIteration(@Valid @RequestBody PmsWorkItemIterationUpdateReqVO updateReqVO) {
workItemService.updateWorkItemIteration(updateReqVO, getLoginUserId());
return success(true);
}
@PutMapping("/update-sort")
@Operation(summary = "更新看板列内工作项顺序")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> updateWorkItemSort(@Valid @RequestBody PmsWorkItemSortReqVO sortReqVO) {
workItemService.updateWorkItemSort(sortReqVO, getLoginUserId());
return success(true);
}
@PutMapping("/update-planning-sort")
@Operation(summary = "更新待规划或迭代内工作项顺序")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> updateWorkItemPlanningSort(@Valid @RequestBody PmsWorkItemPlanningSortReqVO sortReqVO) {
workItemService.updateWorkItemPlanningSort(sortReqVO, getLoginUserId());
return success(true);
}
@PutMapping("/archive")
@Operation(summary = "归档工作项")
@Parameter(name = "id", description = "工作项编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> archiveWorkItem(@RequestParam("id") Long id) {
workItemService.archiveWorkItem(id, getLoginUserId());
return success(true);
}
@PutMapping("/recycle")
@Operation(summary = "将工作项移入回收站")
@Parameter(name = "id", description = "工作项编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> recycleWorkItem(@RequestParam("id") Long id) {
workItemService.recycleWorkItem(id, getLoginUserId());
return success(true);
}
@PutMapping("/restore")
@Operation(summary = "恢复工作项")
@Parameter(name = "id", description = "工作项编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> restoreWorkItem(@RequestParam("id") Long id) {
workItemService.restoreWorkItem(id, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "彻底删除回收站中的工作项")
@Parameter(name = "id", description = "工作项编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:delete')")
public CommonResult<Boolean> deleteWorkItem(@RequestParam("id") Long id) {
workItemService.deleteWorkItem(id, getLoginUserId());
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得工作项详情")
@Parameter(name = "id", description = "工作项编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:query')")
public CommonResult<PmsWorkItemRespVO> getWorkItem(@RequestParam("id") Long id) {
PmsWorkItemDO workItem = workItemService.getWorkItem(id, getLoginUserId());
PmsWorkItemRespVO workItemVO = buildWorkItemRespVO(workItem);
return success(workItemVO);
}
@GetMapping("/page")
@Operation(summary = "获得工作项分页")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:query')")
public CommonResult<PageResult<PmsWorkItemRespVO>> getWorkItemPage(@Valid PmsWorkItemPageReqVO pageReqVO) {
PageResult<PmsWorkItemDO> pageResult = workItemService.getWorkItemPage(pageReqVO, getLoginUserId());
return success(new PageResult<>(buildWorkItemRespVOList(pageResult.getList()), pageResult.getTotal()));
}
@GetMapping("/export-excel")
@Operation(summary = "导出工作项")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:export')")
public void exportWorkItemList(@Valid PmsWorkItemPageReqVO pageReqVO,
HttpServletResponse response) throws IOException {
pageReqVO.setPageSize(PageParam.PAGE_SIZE_NONE);
List<PmsWorkItemDO> list = workItemService.getWorkItemList(pageReqVO, getLoginUserId());
List<PmsWorkItemRespVO> workItemVOList = buildWorkItemRespVOList(list);
ExcelUtils.write(response, "工作项.xlsx", "工作项", PmsWorkItemRespVO.class, workItemVOList);
}
@GetMapping("/get-import-template")
@Operation(summary = "下载工作项导入模板")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:import')")
public void getWorkItemImportTemplate(HttpServletResponse response) throws IOException {
LocalDateTime now = LocalDateTime.now();
LocalDateTime deadline = now.plusDays(3);
PmsWorkItemImportExcelVO example = PmsWorkItemImportExcelVO.builder()
.name("登录页面开发").description("实现账号密码登录").statusName("")
.priority(PmsWorkItemPriorityEnum.MEDIUM.getPriority())
.startTime(now).endTime(deadline)
.labels(Collections.emptyList()).progress(0).estimatedHours(16)
.build();
ExcelUtils.write(response, "工作项导入模板.xlsx", "工作项", PmsWorkItemImportExcelVO.class,
Collections.singletonList(example));
}
@PostMapping("/import")
@Operation(summary = "导入工作项")
@Parameters({
@Parameter(name = "projectId", description = "项目编号", required = true, example = "1024"),
@Parameter(name = "type", description = "工作项类型(需求、任务、缺陷)", required = true, example = "1"),
@Parameter(name = "file", description = "Excel 文件", required = true)
})
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:import')")
public CommonResult<PmsWorkItemImportRespVO> importWorkItemList(
@RequestParam("projectId") Long projectId,
@RequestParam("type") Integer workItemType,
@RequestParam("file") MultipartFile file) throws Exception {
return success(workItemService.importWorkItemList(projectId, workItemType,
ExcelUtils.read(file, PmsWorkItemImportExcelVO.class), getLoginUserId()));
}
@GetMapping("/board")
@Operation(summary = "获得工作项看板")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:query')")
public CommonResult<List<PmsWorkItemBoardRespVO>> getWorkItemBoard(@Valid PmsWorkItemBoardReqVO queryReqVO) {
List<PmsWorkItemBoardRespVO> columns = workItemService.getWorkItemBoard(queryReqVO, getLoginUserId());
fillWorkItemRespVOList(convertListByFlatMap(columns, column -> column.getItems().stream()));
return success(columns);
}
// ==================== 拼接 VO ====================
private PmsWorkItemRespVO buildWorkItemRespVO(PmsWorkItemDO workItem) {
return CollUtil.getFirst(buildWorkItemRespVOList(Collections.singletonList(workItem)));
}
private List<PmsWorkItemRespVO> buildWorkItemRespVOList(List<PmsWorkItemDO> workItems) {
if (CollUtil.isEmpty(workItems)) {
return Collections.emptyList();
}
return fillWorkItemRespVOList(BeanUtils.toBean(workItems, PmsWorkItemRespVO.class));
}
private List<PmsWorkItemRespVO> fillWorkItemRespVOList(List<PmsWorkItemRespVO> workItems) {
if (CollUtil.isEmpty(workItems)) {
return Collections.emptyList();
}
// 1.1 批量查询状态、迭代、关联需求和参与人
Map<Long, PmsWorkItemStatusDO> statusMap = workItemStatusService.getWorkItemStatusMap(
convertSet(workItems, PmsWorkItemRespVO::getStatusId));
Map<Long, PmsIterationDO> iterationMap = iterationService.getIterationMap(
convertSet(workItems, PmsWorkItemRespVO::getIterationId));
Map<Long, PmsWorkItemDO> requirementMap = workItemService.getWorkItemMap(
convertSet(workItems, PmsWorkItemRespVO::getRelatedRequirementId));
Map<Long, List<Long>> memberUserIdListMap = workItemService.getWorkItemMemberUserIdListMap(
convertSet(workItems, PmsWorkItemRespVO::getId));
Map<Long, PmsWorkItemLabelDO> labelMap = workItemLabelService.getWorkItemLabelMap(
convertSetByFlatMap(workItems, PmsWorkItemRespVO::getLabelIds, List::stream));
// 1.2 批量查询负责人和参与人用户
Set<Long> userIds = convertSetByFlatMap(workItems, workItem -> java.util.stream.Stream.concat(
java.util.stream.Stream.of(workItem.getAssigneeUserId()),
memberUserIdListMap.getOrDefault(workItem.getId(), Collections.emptyList()).stream()));
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(userIds);
// 2. 拼接工作项展示字段
for (PmsWorkItemRespVO workItemVO : workItems) {
findAndThen(statusMap, workItemVO.getStatusId(), status -> workItemVO.setStatusName(status.getName()));
findAndThen(iterationMap, workItemVO.getIterationId(),
iteration -> workItemVO.setIterationName(iteration.getName()));
findAndThen(requirementMap, workItemVO.getRelatedRequirementId(),
requirement -> workItemVO.setRelatedRequirementName(requirement.getName()));
findAndThen(userMap, workItemVO.getAssigneeUserId(),
assignee -> workItemVO.setAssigneeUserName(assignee.getNickname()));
List<Long> memberUserIds = memberUserIdListMap.getOrDefault(workItemVO.getId(), Collections.emptyList());
List<String> memberUserNames = convertList(convertList(memberUserIds, userMap::get),
AdminUserRespDTO::getNickname);
workItemVO.setMemberUserIds(memberUserIds).setMemberUserNames(memberUserNames);
workItemVO.setLabels(BeanUtils.toBean(convertList(workItemVO.getLabelIds(), labelMap::get),
PmsWorkItemLabelRespVO.class));
}
return workItems;
}
}
@@ -0,0 +1,71 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.workitem;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.label.PmsWorkItemLabelRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.label.PmsWorkItemLabelSaveReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemLabelDO;
import cn.iocoder.yudao.module.pms.service.pm.workitem.PmsWorkItemLabelService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
@Tag(name = "管理后台 - PMS 工作项标签")
@RestController
@RequestMapping("/pms/pm/work-item-label")
@Validated
public class PmsWorkItemLabelController {
@Resource
private PmsWorkItemLabelService labelService;
@PostMapping("/create")
@Operation(summary = "创建工作项标签")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Long> createWorkItemLabel(@Valid @RequestBody PmsWorkItemLabelSaveReqVO saveReqVO) {
return success(labelService.createWorkItemLabel(saveReqVO));
}
@PutMapping("/update")
@Operation(summary = "修改工作项标签")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> updateWorkItemLabel(@Valid @RequestBody PmsWorkItemLabelSaveReqVO saveReqVO) {
labelService.updateWorkItemLabel(saveReqVO);
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除工作项标签")
@Parameter(name = "id", description = "标签编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> deleteWorkItemLabel(@RequestParam("id") Long id) {
labelService.deleteWorkItemLabel(id);
return success(true);
}
@GetMapping("/list")
@Operation(summary = "获得工作项标签列表")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:query')")
public CommonResult<List<PmsWorkItemLabelRespVO>> getWorkItemLabelList(
@RequestParam(value = "name", required = false) String name) {
List<PmsWorkItemLabelDO> labels = labelService.getWorkItemLabelList(name);
return success(BeanUtils.toBean(labels, PmsWorkItemLabelRespVO.class));
}
}
@@ -0,0 +1,159 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.workitem;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.status.PmsWorkItemBoardConfigRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.status.PmsWorkItemBoardConfigSaveReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.status.PmsWorkItemStatusConfigUpdateReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.status.PmsWorkItemStatusCreateReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.status.PmsWorkItemStatusDeleteReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.status.PmsWorkItemStatusRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.status.PmsWorkItemStatusSortReqVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemBoardDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemStatusDO;
import cn.iocoder.yudao.module.pms.service.pm.workitem.PmsWorkItemStatusService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 工作项状态")
@RestController
@RequestMapping("/pms/pm/work-item-status")
@Validated
public class PmsWorkItemStatusController {
@Resource
private PmsWorkItemStatusService workItemStatusService;
@PostMapping("/create")
@Operation(summary = "创建工作项状态")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Long> createWorkItemStatus(
@Valid @RequestBody PmsWorkItemStatusCreateReqVO createReqVO) {
return success(workItemStatusService.createWorkItemStatus(createReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新工作项状态")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> updateWorkItemStatusConfig(
@Valid @RequestBody PmsWorkItemStatusConfigUpdateReqVO updateReqVO) {
workItemStatusService.updateWorkItemStatusConfig(updateReqVO, getLoginUserId());
return success(true);
}
@PutMapping("/update-default")
@Operation(summary = "更新工作项初始状态")
@Parameter(name = "id", description = "状态编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> updateDefaultWorkItemStatus(@RequestParam("id") Long id) {
workItemStatusService.updateDefaultWorkItemStatus(id, getLoginUserId());
return success(true);
}
@PutMapping("/update-sort")
@Operation(summary = "更新工作项状态顺序")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> updateWorkItemStatusSort(
@Valid @RequestBody PmsWorkItemStatusSortReqVO sortReqVO) {
workItemStatusService.updateWorkItemStatusSort(sortReqVO, getLoginUserId());
return success(true);
}
@PutMapping("/update-board-config")
@Operation(summary = "更新工作项看板列和状态映射")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> updateWorkItemBoardConfig(
@Valid @RequestBody PmsWorkItemBoardConfigSaveReqVO saveReqVO) {
workItemStatusService.updateWorkItemBoardConfig(saveReqVO, getLoginUserId());
return success(true);
}
@DeleteMapping("/delete")
@Operation(summary = "删除工作项状态并迁移工作项")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> deleteWorkItemStatus(
@Valid @RequestBody PmsWorkItemStatusDeleteReqVO deleteReqVO) {
workItemStatusService.deleteWorkItemStatus(deleteReqVO, getLoginUserId());
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得工作项状态详情")
@Parameter(name = "id", description = "状态编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:query')")
public CommonResult<PmsWorkItemStatusRespVO> getWorkItemStatus(@RequestParam("id") Long id) {
PmsWorkItemStatusDO status = workItemStatusService.getWorkItemStatus(id, getLoginUserId());
return success(BeanUtils.toBean(status, PmsWorkItemStatusRespVO.class));
}
@GetMapping("/list")
@Operation(summary = "获得工作项看板状态列表")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:query')")
public CommonResult<List<PmsWorkItemStatusRespVO>> getWorkItemStatusList(
@RequestParam("projectId") Long projectId, @RequestParam("type") Integer workItemType) {
List<PmsWorkItemStatusDO> statuses = workItemStatusService.getWorkItemStatusList(projectId, workItemType,
getLoginUserId());
return success(BeanUtils.toBean(statuses, PmsWorkItemStatusRespVO.class));
}
@GetMapping("/get-board-config")
@Operation(summary = "获得工作项看板列和状态映射")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:query')")
public CommonResult<PmsWorkItemBoardConfigRespVO> getWorkItemBoardConfig(
@RequestParam("projectId") Long projectId, @RequestParam("type") Integer workItemType) {
// 1. 校验项目可读并查询已保存的看板列、状态
List<PmsWorkItemStatusDO> statuses = workItemStatusService
.getWorkItemStatusList(projectId, workItemType, getLoginUserId());
List<PmsWorkItemBoardDO> boards = workItemStatusService.getWorkItemBoardList(projectId, workItemType);
// 2.1 按看板列名称聚合状态编号
Map<String, List<Long>> statusIdMap = new LinkedHashMap<>();
for (PmsWorkItemStatusDO status : statuses) {
if (StrUtil.isNotBlank(status.getBoardName())) {
statusIdMap.computeIfAbsent(status.getBoardName(), key -> new ArrayList<>()).add(status.getId());
}
}
// 2.2 按看板列顺序组装响应,并保留空列
Set<Long> assignedStatusIds = new LinkedHashSet<>();
List<PmsWorkItemBoardConfigRespVO.Board> boardVOs = new ArrayList<>();
for (PmsWorkItemBoardDO board : boards) {
List<Long> statusIds = statusIdMap.getOrDefault(board.getName(), new ArrayList<>());
assignedStatusIds.addAll(statusIds);
boardVOs.add(new PmsWorkItemBoardConfigRespVO.Board().setId(board.getId())
.setName(board.getName()).setStatusIds(statusIds));
}
// 2.3 收集未分配到看板列的状态
List<Long> unassignedStatusIds = new ArrayList<>();
for (PmsWorkItemStatusDO status : statuses) {
if (!assignedStatusIds.contains(status.getId())) {
unassignedStatusIds.add(status.getId());
}
}
return success(new PmsWorkItemBoardConfigRespVO().setBoards(boardVOs).setUnassignedStatusIds(unassignedStatusIds));
}
}
@@ -0,0 +1,124 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.workitem;
import cn.hutool.core.collection.CollUtil;
import cn.iocoder.yudao.framework.common.pojo.CommonResult;
import cn.iocoder.yudao.framework.common.util.number.NumberUtils;
import cn.iocoder.yudao.framework.common.util.object.BeanUtils;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.worklog.PmsProjectWorkLogReportReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.worklog.PmsProjectWorkLogReportRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.worklog.PmsWorkItemWorkLogRespVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.worklog.PmsWorkItemWorkLogSaveReqVO;
import cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.worklog.PmsWorkItemWorkLogSummaryRespVO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemDO;
import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemWorkLogDO;
import cn.iocoder.yudao.module.pms.service.pm.workitem.PmsWorkItemService;
import cn.iocoder.yudao.module.pms.service.pm.workitem.PmsWorkItemWorkLogService;
import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.validation.Valid;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import static cn.iocoder.yudao.framework.common.pojo.CommonResult.success;
import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertSet;
import static cn.iocoder.yudao.framework.common.util.collection.MapUtils.findAndThen;
import static cn.iocoder.yudao.framework.security.core.util.SecurityFrameworkUtils.getLoginUserId;
@Tag(name = "管理后台 - PMS 工作项工时")
@RestController
@RequestMapping("/pms/pm/work-item-work-log")
@Validated
public class PmsWorkItemWorkLogController {
@Resource
private PmsWorkItemWorkLogService workLogService;
@Resource
private PmsWorkItemService workItemService;
@Resource
private AdminUserApi adminUserApi;
@PostMapping("/create")
@Operation(summary = "创建工作项工时记录")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Long> createWorkItemWorkLog(@Valid @RequestBody PmsWorkItemWorkLogSaveReqVO saveReqVO) {
return success(workLogService.createWorkItemWorkLog(saveReqVO, getLoginUserId()));
}
@PutMapping("/update")
@Operation(summary = "更新工作项工时记录")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:update')")
public CommonResult<Boolean> updateWorkItemWorkLog(@Valid @RequestBody PmsWorkItemWorkLogSaveReqVO saveReqVO) {
workLogService.updateWorkItemWorkLog(saveReqVO, getLoginUserId());
return success(true);
}
@GetMapping("/get")
@Operation(summary = "获得工作项工时记录")
@Parameter(name = "id", description = "工时记录编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:query')")
public CommonResult<PmsWorkItemWorkLogRespVO> getWorkItemWorkLog(@RequestParam("id") Long id) {
PmsWorkItemWorkLogDO workLog = workLogService.getWorkItemWorkLog(id, getLoginUserId());
PmsWorkItemWorkLogRespVO respVO = BeanUtils.toBean(workLog, PmsWorkItemWorkLogRespVO.class)
.setCreatorUserId(NumberUtils.parseLong(workLog.getCreator()));
fillWorkItemWorkLogRespVOList(Collections.singletonList(respVO));
return success(respVO);
}
@GetMapping("/summary")
@Operation(summary = "获得工作项工时汇总")
@Parameter(name = "workItemId", description = "工作项编号", required = true, example = "1024")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:query')")
public CommonResult<PmsWorkItemWorkLogSummaryRespVO> getWorkItemWorkLogSummary(
@RequestParam("workItemId") Long workItemId) {
PmsWorkItemWorkLogSummaryRespVO summary = workLogService.getWorkItemWorkLogSummary(workItemId, getLoginUserId());
fillWorkItemWorkLogRespVOList(summary.getRecords());
return success(summary);
}
@GetMapping("/project-report")
@Operation(summary = "获得项目工时报表")
@PreAuthorize("@ss.hasPermission('pms:pm:work-item:query')")
public CommonResult<PmsProjectWorkLogReportRespVO> getProjectWorkItemWorkLogReport(
@Valid PmsProjectWorkLogReportReqVO reqVO) {
PmsProjectWorkLogReportRespVO report = workLogService.getProjectWorkItemWorkLogReport(
reqVO, getLoginUserId());
return success(report);
}
// ==================== 拼接 VO ====================
private List<PmsWorkItemWorkLogRespVO> fillWorkItemWorkLogRespVOList(List<PmsWorkItemWorkLogRespVO> workLogs) {
if (CollUtil.isEmpty(workLogs)) {
return Collections.emptyList();
}
// 1. 批量查询创建人和工作项
Map<Long, AdminUserRespDTO> userMap = adminUserApi.getUserMap(convertSet(
workLogs, PmsWorkItemWorkLogRespVO::getCreatorUserId));
Map<Long, PmsWorkItemDO> workItemMap = workItemService.getWorkItemMap(
convertSet(workLogs, PmsWorkItemWorkLogRespVO::getWorkItemId));
// 2. 拼接创建人和工作项名称
for (PmsWorkItemWorkLogRespVO workLog : workLogs) {
findAndThen(userMap, workLog.getCreatorUserId(), user -> workLog.setCreatorUserName(user.getNickname()));
findAndThen(workItemMap, workLog.getWorkItemId(), item -> workLog.setWorkItemName(item.getName()));
}
return workLogs;
}
}
@@ -0,0 +1,35 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.activity;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import lombok.experimental.Accessors;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - PMS 工作项动态响应 VO")
@Data
@Accessors(chain = true)
public class PmsWorkItemActivityRespVO {
@Schema(description = "动态编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "工作项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "2048")
private Long workItemId;
@Schema(description = "操作人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Long operatorUserId;
@Schema(description = "操作人姓名", example = "芋道")
private String operatorUserName;
@Schema(description = "操作人头像")
private String operatorUserAvatar;
@Schema(description = "动态内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "更新了工作项")
private String content;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
@@ -0,0 +1,43 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.comment;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
import java.util.List;
@Schema(description = "管理后台 - PMS 工作项评论 Response VO")
@Data
public class PmsWorkItemCommentRespVO {
@Schema(description = "评论编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "工作项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long workItemId;
@Schema(description = "评论人用户编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long userId;
@Schema(description = "评论人姓名", example = "芋道源码")
private String userName;
@Schema(description = "主评论编号", example = "1024")
private Long mainId;
@Schema(description = "回复对象用户编号", example = "2048")
private Long replyUserId;
@Schema(description = "回复对象姓名", example = "源码")
private String replyUserName;
@Schema(description = "评论内容", requiredMode = Schema.RequiredMode.REQUIRED)
private String content;
@Schema(description = "回复列表")
private List<PmsWorkItemCommentRespVO> children;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
@@ -0,0 +1,31 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.comment;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - PMS 工作项评论新增/修改 Request VO")
@Data
public class PmsWorkItemCommentSaveReqVO {
@Schema(description = "评论编号", example = "1024")
private Long id;
@Schema(description = "工作项编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "工作项编号不能为空")
private Long workItemId;
@Schema(description = "主评论编号,回复时传入", example = "1024")
private Long mainId;
@Schema(description = "回复对象用户编号", example = "2048")
private Long replyUserId;
@Schema(description = "评论内容", requiredMode = Schema.RequiredMode.REQUIRED, example = "已处理")
@NotBlank(message = "评论内容不能为空")
@Size(max = 2000, message = "评论内容不能超过 2000 个字符")
private String content;
}
@@ -0,0 +1,24 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.label;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDateTime;
@Schema(description = "管理后台 - PMS 工作项标签 Response VO")
@Data
public class PmsWorkItemLabelRespVO {
@Schema(description = "标签编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long id;
@Schema(description = "标签名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "紧急")
private String name;
@Schema(description = "标签颜色", requiredMode = Schema.RequiredMode.REQUIRED, example = "#409EFF")
private String color;
@Schema(description = "创建时间", requiredMode = Schema.RequiredMode.REQUIRED)
private LocalDateTime createTime;
}
@@ -0,0 +1,25 @@
package cn.iocoder.yudao.module.pms.controller.admin.pm.workitem.vo.label;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import lombok.Data;
@Schema(description = "管理后台 - PMS 工作项标签新增/修改 Request VO")
@Data
public class PmsWorkItemLabelSaveReqVO {
@Schema(description = "标签编号", example = "1024")
private Long id;
@Schema(description = "标签名称", requiredMode = Schema.RequiredMode.REQUIRED, example = "紧急")
@NotBlank(message = "标签名称不能为空")
@Size(max = 50, message = "标签名称不能超过 50 个字符")
private String name;
@Schema(description = "标签颜色", requiredMode = Schema.RequiredMode.REQUIRED, example = "#409EFF")
@NotBlank(message = "标签颜色不能为空")
@Size(max = 20, message = "标签颜色不能超过 20 个字符")
private String color;
}

Some files were not shown because too many files have changed in this diff Show More