实现流程项目逻辑删除与恢复
This commit is contained in:
@@ -10,6 +10,7 @@ public final class CcdiProjectStatusConstants {
|
||||
public static final String ARCHIVED = "2";
|
||||
public static final String TAGGING = "3";
|
||||
public static final String TAG_FAILED = "4";
|
||||
public static final String DELETED = "5";
|
||||
|
||||
private CcdiProjectStatusConstants() {
|
||||
}
|
||||
|
||||
@@ -74,12 +74,23 @@ public class CcdiProjectController extends BaseController {
|
||||
*/
|
||||
@DeleteMapping("/{projectId}")
|
||||
@Operation(summary = "删除项目")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:remove')")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:list')")
|
||||
public AjaxResult deleteProject(@PathVariable Long projectId) {
|
||||
boolean success = projectService.deleteProject(projectId);
|
||||
boolean success = projectService.deleteProject(projectId, SecurityUtils.getUsername());
|
||||
return success ? AjaxResult.success("项目删除成功") : AjaxResult.error("项目删除失败");
|
||||
}
|
||||
|
||||
/**
|
||||
* 恢复项目
|
||||
*/
|
||||
@PostMapping("/{projectId}/restore")
|
||||
@Operation(summary = "恢复项目")
|
||||
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
|
||||
public AjaxResult restoreProject(@PathVariable Long projectId) {
|
||||
projectService.restoreProject(projectId, SecurityUtils.getUsername());
|
||||
return AjaxResult.success("项目恢复成功");
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询项目详情
|
||||
*/
|
||||
|
||||
@@ -32,7 +32,7 @@ public class CcdiProject implements Serializable {
|
||||
/** 配置方式:default-全局默认,custom-自定义 */
|
||||
private String configType;
|
||||
|
||||
/** 项目状态:0-进行中,1-已完成,2-已归档,3-打标中,4-打标失败 */
|
||||
/** 项目状态:0-进行中,1-已完成,2-已归档,3-打标中,4-打标失败,5-已删除 */
|
||||
private String status;
|
||||
|
||||
/** 是否归档:0-未归档,1-已归档 */
|
||||
@@ -54,7 +54,7 @@ public class CcdiProject implements Serializable {
|
||||
private Integer lsfxProjectId;
|
||||
|
||||
/** 删除标志:0-存在,2-删除 */
|
||||
@TableLogic
|
||||
@TableLogic(value = "0", delval = "2")
|
||||
private String delFlag;
|
||||
|
||||
/** 创建者 */
|
||||
|
||||
@@ -14,4 +14,7 @@ public class CcdiProjectQueryDTO {
|
||||
|
||||
/** 项目状态 */
|
||||
private String status;
|
||||
|
||||
/** 是否查询已删除项目列表 */
|
||||
private Boolean includeDeleted;
|
||||
}
|
||||
|
||||
@@ -26,4 +26,7 @@ public class CcdiProjectStatusCountsVO {
|
||||
|
||||
/** 打标失败项目数(状态4) */
|
||||
private Long status4;
|
||||
|
||||
/** 已删除项目数(状态5) */
|
||||
private Long status5;
|
||||
}
|
||||
|
||||
@@ -67,4 +67,7 @@ public class CcdiProjectVO {
|
||||
|
||||
/** 当前用户是否可操作 */
|
||||
private Boolean canOperate;
|
||||
|
||||
/** 当前用户是否可删除 */
|
||||
private Boolean canDelete;
|
||||
}
|
||||
|
||||
@@ -39,6 +39,32 @@ public interface CcdiProjectMapper extends BaseMapper<CcdiProject> {
|
||||
List<CcdiProjectHistoryListItemVO> selectHistoryProjects(@Param("queryDTO") CcdiProjectQueryDTO queryDTO,
|
||||
@Param("scope") ProjectAccessScope scope);
|
||||
|
||||
/**
|
||||
* 业务逻辑删除项目,仅更新项目主表状态和删除标记。
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param operator 操作人
|
||||
* @return 更新行数
|
||||
*/
|
||||
int markProjectDeleted(@Param("projectId") Long projectId, @Param("operator") String operator);
|
||||
|
||||
/**
|
||||
* 恢复已删除项目为已完成。
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param operator 操作人
|
||||
* @return 更新行数
|
||||
*/
|
||||
int restoreDeletedProject(@Param("projectId") Long projectId, @Param("operator") String operator);
|
||||
|
||||
/**
|
||||
* 统计已删除项目数量。
|
||||
*
|
||||
* @param scope 项目访问范围
|
||||
* @return 已删除项目数量
|
||||
*/
|
||||
Long selectDeletedProjectCount(@Param("scope") ProjectAccessScope scope);
|
||||
|
||||
/**
|
||||
* 更新项目总人数与风险人数
|
||||
*
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.ccdi.project.service;
|
||||
|
||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||
import com.ruoyi.ccdi.project.domain.ProjectAccessScope;
|
||||
import com.ruoyi.ccdi.project.constants.CcdiProjectStatusConstants;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiBankStatement;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiEvidence;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord;
|
||||
@@ -58,8 +59,17 @@ public class CcdiProjectAccessService {
|
||||
return scope.isSuperAdmin() || Objects.equals(scope.getUsername(), project.getCreateBy());
|
||||
}
|
||||
|
||||
public boolean canDelete(CcdiProject project) {
|
||||
if (project == null) {
|
||||
return false;
|
||||
}
|
||||
ProjectAccessScope scope = buildCurrentScope();
|
||||
return isProjectAdmin(scope) || Objects.equals(scope.getUsername(), project.getCreateBy());
|
||||
}
|
||||
|
||||
public void assertCanRead(Long projectId) {
|
||||
CcdiProject project = getRequiredProject(projectId);
|
||||
assertProjectNotDeleted(project);
|
||||
ProjectAccessScope scope = buildCurrentScope();
|
||||
if (scope.isViewAllProjects() || Objects.equals(scope.getUsername(), project.getCreateBy())) {
|
||||
return;
|
||||
@@ -69,12 +79,29 @@ public class CcdiProjectAccessService {
|
||||
|
||||
public void assertCanOperate(Long projectId) {
|
||||
CcdiProject project = getRequiredProject(projectId);
|
||||
assertProjectNotDeleted(project);
|
||||
if (canOperate(project)) {
|
||||
return;
|
||||
}
|
||||
throw new ServiceException("无权操作该项目");
|
||||
}
|
||||
|
||||
public void assertCanDelete(Long projectId) {
|
||||
CcdiProject project = getRequiredProject(projectId);
|
||||
assertProjectNotDeleted(project);
|
||||
if (canDelete(project)) {
|
||||
return;
|
||||
}
|
||||
throw new ServiceException("无权删除该项目");
|
||||
}
|
||||
|
||||
public void assertCanManageDeletedProjects() {
|
||||
if (isProjectAdmin(buildCurrentScope())) {
|
||||
return;
|
||||
}
|
||||
throw new ServiceException("无权管理已删除项目");
|
||||
}
|
||||
|
||||
public void assertCanReadByBankStatementId(Long bankStatementId) {
|
||||
CcdiBankStatement statement = getRequiredBankStatement(bankStatementId);
|
||||
assertCanRead(statement.getProjectId());
|
||||
@@ -115,6 +142,19 @@ public class CcdiProjectAccessService {
|
||||
return project;
|
||||
}
|
||||
|
||||
private void assertProjectNotDeleted(CcdiProject project) {
|
||||
if (project == null) {
|
||||
return;
|
||||
}
|
||||
if (CcdiProjectStatusConstants.DELETED.equals(project.getStatus()) || "2".equals(project.getDelFlag())) {
|
||||
throw new ServiceException("项目已删除");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isProjectAdmin(ProjectAccessScope scope) {
|
||||
return scope != null && (scope.isSuperAdmin() || scope.isProjectManager());
|
||||
}
|
||||
|
||||
private CcdiBankStatement getRequiredBankStatement(Long bankStatementId) {
|
||||
if (bankStatementId == null) {
|
||||
throw new ServiceException("流水ID不能为空");
|
||||
|
||||
@@ -36,9 +36,18 @@ public interface ICcdiProjectService {
|
||||
* 删除项目
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param operator 操作人
|
||||
* @return 是否成功
|
||||
*/
|
||||
boolean deleteProject(Long projectId);
|
||||
boolean deleteProject(Long projectId, String operator);
|
||||
|
||||
/**
|
||||
* 恢复已删除项目
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param operator 操作人
|
||||
*/
|
||||
void restoreProject(Long projectId, String operator);
|
||||
|
||||
/**
|
||||
* 查询项目详情
|
||||
|
||||
@@ -117,9 +117,22 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean deleteProject(Long projectId) {
|
||||
projectAccessService.assertCanOperate(projectId);
|
||||
return projectMapper.deleteById(projectId) > 0;
|
||||
public boolean deleteProject(Long projectId, String operator) {
|
||||
projectAccessService.assertCanDelete(projectId);
|
||||
return projectMapper.markProjectDeleted(projectId, resolveOperator(operator)) > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void restoreProject(Long projectId, String operator) {
|
||||
projectAccessService.assertCanManageDeletedProjects();
|
||||
int rows = projectMapper.restoreDeletedProject(projectId, resolveOperator(operator));
|
||||
if (rows <= 0) {
|
||||
throw new ServiceException("仅已删除项目允许恢复");
|
||||
}
|
||||
log.info("【项目】项目状态变更: projectId={}, oldStatus={}, oldStatusLabel={}, newStatus={}, newStatusLabel={}, operator={}",
|
||||
projectId, CcdiProjectStatusConstants.DELETED, resolveStatusLabel(CcdiProjectStatusConstants.DELETED),
|
||||
CcdiProjectStatusConstants.COMPLETED, resolveStatusLabel(CcdiProjectStatusConstants.COMPLETED),
|
||||
resolveOperator(operator));
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -201,6 +214,12 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
|
||||
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.TAG_FAILED));
|
||||
vo.setStatus4(status4Count);
|
||||
|
||||
Long status5Count = 0L;
|
||||
if (isProjectAdmin(scope)) {
|
||||
status5Count = projectMapper.selectDeletedProjectCount(scope);
|
||||
}
|
||||
vo.setStatus5(status5Count);
|
||||
|
||||
return vo;
|
||||
}
|
||||
|
||||
@@ -229,6 +248,9 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
|
||||
public void updateProjectStatus(Long projectId, String status, String operator) {
|
||||
CcdiProject project = getRequiredProject(projectId);
|
||||
String oldStatus = project.getStatus();
|
||||
if (CcdiProjectStatusConstants.DELETED.equals(project.getStatus())) {
|
||||
throw new ServiceException("已删除项目不允许更新状态");
|
||||
}
|
||||
if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus())
|
||||
&& !CcdiProjectStatusConstants.ARCHIVED.equals(status)) {
|
||||
throw new ServiceException("已归档项目不允许重新进入打标流程");
|
||||
@@ -247,6 +269,9 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
|
||||
@Override
|
||||
public void ensureProjectCanStartTagging(Long projectId) {
|
||||
CcdiProject project = getRequiredProject(projectId);
|
||||
if (CcdiProjectStatusConstants.DELETED.equals(project.getStatus())) {
|
||||
throw new ServiceException("已删除项目不允许重新进入打标流程");
|
||||
}
|
||||
if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus())) {
|
||||
throw new ServiceException("已归档项目不允许重新进入打标流程");
|
||||
}
|
||||
@@ -255,6 +280,9 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
|
||||
@Override
|
||||
public void ensureProjectNotArchived(Long projectId, String message) {
|
||||
CcdiProject project = getRequiredProject(projectId);
|
||||
if (CcdiProjectStatusConstants.DELETED.equals(project.getStatus())) {
|
||||
throw new ServiceException("已删除项目暂不允许操作");
|
||||
}
|
||||
if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus())) {
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
@@ -263,6 +291,9 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
|
||||
@Override
|
||||
public void ensureProjectWritable(Long projectId, String message) {
|
||||
CcdiProject project = getRequiredProject(projectId);
|
||||
if (CcdiProjectStatusConstants.DELETED.equals(project.getStatus())) {
|
||||
throw new ServiceException("已删除项目暂不允许操作");
|
||||
}
|
||||
if (CcdiProjectStatusConstants.TAGGING.equals(project.getStatus())) {
|
||||
throw new ServiceException(message);
|
||||
}
|
||||
@@ -283,6 +314,7 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
|
||||
case CcdiProjectStatusConstants.ARCHIVED -> "已归档";
|
||||
case CcdiProjectStatusConstants.TAGGING -> "打标中";
|
||||
case CcdiProjectStatusConstants.TAG_FAILED -> "打标失败";
|
||||
case CcdiProjectStatusConstants.DELETED -> "已删除";
|
||||
default -> "未知";
|
||||
};
|
||||
}
|
||||
@@ -301,12 +333,17 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
|
||||
|
||||
private LambdaQueryWrapper<CcdiProject> buildScopeWrapper(ProjectAccessScope scope) {
|
||||
LambdaQueryWrapper<CcdiProject> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.ne(CcdiProject::getStatus, CcdiProjectStatusConstants.DELETED);
|
||||
if (scope != null && !scope.isViewAllProjects()) {
|
||||
wrapper.eq(CcdiProject::getCreateBy, scope.getUsername());
|
||||
}
|
||||
return wrapper;
|
||||
}
|
||||
|
||||
private boolean isProjectAdmin(ProjectAccessScope scope) {
|
||||
return scope != null && (scope.isSuperAdmin() || scope.isProjectManager());
|
||||
}
|
||||
|
||||
private void fillProjectExtraFields(List<CcdiProjectVO> records) {
|
||||
if (records == null || records.isEmpty()) {
|
||||
return;
|
||||
@@ -329,6 +366,7 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
|
||||
boolean ownedByCurrentUser = Objects.equals(scope.getUsername(), vo.getCreateBy());
|
||||
vo.setOwnedByCurrentUser(ownedByCurrentUser);
|
||||
vo.setCanOperate(scope.isSuperAdmin() || ownedByCurrentUser);
|
||||
vo.setCanDelete(isProjectAdmin(scope) || ownedByCurrentUser);
|
||||
}
|
||||
|
||||
private String resolveOperator(String operator) {
|
||||
|
||||
@@ -40,13 +40,23 @@
|
||||
FROM ccdi_project p
|
||||
LEFT JOIN sys_user u ON p.create_by = u.user_name AND u.del_flag = '0'
|
||||
<where>
|
||||
<choose>
|
||||
<when test="queryDTO != null and queryDTO.includeDeleted == true and scope != null and scope.viewAllProjects">
|
||||
AND p.del_flag = '2'
|
||||
AND p.status = '5'
|
||||
</when>
|
||||
<otherwise>
|
||||
AND p.del_flag = '0'
|
||||
AND p.status != '5'
|
||||
</otherwise>
|
||||
</choose>
|
||||
<if test="scope != null and !scope.viewAllProjects">
|
||||
AND p.create_by = #{scope.username}
|
||||
</if>
|
||||
<if test="queryDTO.projectName != null and queryDTO.projectName != ''">
|
||||
AND p.project_name LIKE CONCAT('%', #{queryDTO.projectName}, '%')
|
||||
</if>
|
||||
<if test="queryDTO.status != null and queryDTO.status != ''">
|
||||
<if test="!(queryDTO != null and queryDTO.includeDeleted == true) and queryDTO.status != null and queryDTO.status != ''">
|
||||
AND p.status = #{queryDTO.status}
|
||||
</if>
|
||||
</where>
|
||||
@@ -64,6 +74,7 @@
|
||||
FROM ccdi_project p
|
||||
<where>
|
||||
p.status in ('1', '2')
|
||||
AND p.del_flag = '0'
|
||||
<if test="scope != null and !scope.viewAllProjects">
|
||||
AND p.create_by = #{scope.username}
|
||||
</if>
|
||||
@@ -74,6 +85,41 @@
|
||||
ORDER BY p.update_time DESC
|
||||
</select>
|
||||
|
||||
<update id="markProjectDeleted">
|
||||
update ccdi_project
|
||||
set status = '5',
|
||||
del_flag = '2',
|
||||
update_by = #{operator},
|
||||
update_time = now()
|
||||
where project_id = #{projectId}
|
||||
and del_flag = '0'
|
||||
and status != '5'
|
||||
</update>
|
||||
|
||||
<update id="restoreDeletedProject">
|
||||
update ccdi_project
|
||||
set status = '1',
|
||||
del_flag = '0',
|
||||
is_archived = 0,
|
||||
update_by = #{operator},
|
||||
update_time = now()
|
||||
where project_id = #{projectId}
|
||||
and del_flag = '2'
|
||||
and status = '5'
|
||||
</update>
|
||||
|
||||
<select id="selectDeletedProjectCount" resultType="java.lang.Long">
|
||||
select count(1)
|
||||
from ccdi_project p
|
||||
<where>
|
||||
p.del_flag = '2'
|
||||
and p.status = '5'
|
||||
<if test="scope != null and !scope.viewAllProjects">
|
||||
AND p.create_by = #{scope.username}
|
||||
</if>
|
||||
</where>
|
||||
</select>
|
||||
|
||||
<update id="updateRiskCountsByProjectId">
|
||||
update ccdi_project
|
||||
set target_count = #{targetCount},
|
||||
|
||||
@@ -1,28 +1,20 @@
|
||||
package com.ruoyi.ccdi.project.controller;
|
||||
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
|
||||
import com.ruoyi.ccdi.project.service.ICcdiProjectService;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockedStatic;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.web.bind.annotation.DeleteMapping;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertSame;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.mockStatic;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class CcdiProjectControllerTest {
|
||||
@@ -35,16 +27,6 @@ class CcdiProjectControllerTest {
|
||||
|
||||
@Test
|
||||
void shouldArchiveProject() throws Exception {
|
||||
try (MockedStatic<com.ruoyi.common.utils.SecurityUtils> mocked = mockStatic(com.ruoyi.common.utils.SecurityUtils.class)) {
|
||||
mocked.when(com.ruoyi.common.utils.SecurityUtils::getUsername).thenReturn("tester");
|
||||
|
||||
AjaxResult result = controller.archiveProject(40L);
|
||||
|
||||
assertEquals(200, result.get("code"));
|
||||
assertEquals("项目归档成功", result.get("msg"));
|
||||
verify(projectService).archiveProject(40L, "tester");
|
||||
}
|
||||
|
||||
Method method = CcdiProjectController.class.getMethod("archiveProject", Long.class);
|
||||
PostMapping postMapping = method.getAnnotation(PostMapping.class);
|
||||
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
||||
@@ -59,24 +41,32 @@ class CcdiProjectControllerTest {
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldImportFromHistoryAndReturnCreatedProject() {
|
||||
CcdiProjectImportHistoryDTO dto = new CcdiProjectImportHistoryDTO();
|
||||
dto.setProjectName("新建项目");
|
||||
void shouldRestoreProject() throws Exception {
|
||||
Method method = CcdiProjectController.class.getMethod("restoreProject", Long.class);
|
||||
PostMapping postMapping = method.getAnnotation(PostMapping.class);
|
||||
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
||||
Operation operation = method.getAnnotation(Operation.class);
|
||||
|
||||
CcdiProjectVO project = new CcdiProjectVO();
|
||||
project.setProjectId(88L);
|
||||
project.setProjectName("新建项目");
|
||||
when(projectService.importFromHistory(eq(dto), eq("tester"))).thenReturn(project);
|
||||
assertNotNull(postMapping);
|
||||
assertEquals("/{projectId}/restore", postMapping.value()[0]);
|
||||
assertNotNull(preAuthorize);
|
||||
assertEquals("@ss.hasPermi('ccdi:project:edit')", preAuthorize.value());
|
||||
assertNotNull(operation);
|
||||
assertEquals("恢复项目", operation.summary());
|
||||
}
|
||||
|
||||
try (MockedStatic<com.ruoyi.common.utils.SecurityUtils> mocked = mockStatic(com.ruoyi.common.utils.SecurityUtils.class)) {
|
||||
mocked.when(com.ruoyi.common.utils.SecurityUtils::getUsername).thenReturn("tester");
|
||||
@Test
|
||||
void shouldAllowProjectListUsersToReachBusinessDeleteCheck() throws Exception {
|
||||
Method method = CcdiProjectController.class.getMethod("deleteProject", Long.class);
|
||||
DeleteMapping deleteMapping = method.getAnnotation(DeleteMapping.class);
|
||||
PreAuthorize preAuthorize = method.getAnnotation(PreAuthorize.class);
|
||||
Operation operation = method.getAnnotation(Operation.class);
|
||||
|
||||
AjaxResult result = controller.importFromHistory(dto);
|
||||
|
||||
assertEquals(200, result.get("code"));
|
||||
assertEquals("项目创建成功", result.get("msg"));
|
||||
assertSame(project, result.get("data"));
|
||||
verify(projectService).importFromHistory(dto, "tester");
|
||||
}
|
||||
assertNotNull(deleteMapping);
|
||||
assertEquals("/{projectId}", deleteMapping.value()[0]);
|
||||
assertNotNull(preAuthorize);
|
||||
assertEquals("@ss.hasPermi('ccdi:project:list')", preAuthorize.value());
|
||||
assertNotNull(operation);
|
||||
assertEquals("删除项目", operation.summary());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,6 +4,7 @@ import ch.qos.logback.classic.Logger;
|
||||
import ch.qos.logback.classic.spi.ILoggingEvent;
|
||||
import ch.qos.logback.core.read.ListAppender;
|
||||
import com.ruoyi.ccdi.project.domain.CcdiProject;
|
||||
import com.ruoyi.ccdi.project.domain.ProjectAccessScope;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO;
|
||||
@@ -83,6 +84,48 @@ class CcdiProjectServiceImplTest {
|
||||
assertEquals(5L, counts.getStatus4());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCountDeletedProjectsSeparatelyForManagerScope() {
|
||||
when(projectAccessService.buildCurrentScope()).thenReturn(
|
||||
new ProjectAccessScope("manager", true, false, true)
|
||||
);
|
||||
when(projectMapper.selectCount(any())).thenReturn(10L, 3L, 4L, 2L, 1L, 5L);
|
||||
when(projectMapper.selectDeletedProjectCount(any())).thenReturn(6L);
|
||||
|
||||
CcdiProjectStatusCountsVO counts = service.getStatusCounts();
|
||||
|
||||
assertEquals(6L, counts.getStatus5());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMarkProjectDeletedWithoutCallingBaseDelete() {
|
||||
when(projectMapper.markProjectDeleted(40L, "tester")).thenReturn(1);
|
||||
|
||||
boolean result = service.deleteProject(40L, "tester");
|
||||
|
||||
assertTrue(result);
|
||||
verify(projectAccessService).assertCanDelete(40L);
|
||||
verify(projectMapper).markProjectDeleted(40L, "tester");
|
||||
verify(projectMapper, never()).deleteById(40L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRestoreDeletedProjectToCompleted() {
|
||||
when(projectMapper.restoreDeletedProject(40L, "tester")).thenReturn(1);
|
||||
|
||||
service.restoreProject(40L, "tester");
|
||||
|
||||
verify(projectAccessService).assertCanManageDeletedProjects();
|
||||
verify(projectMapper).restoreDeletedProject(40L, "tester");
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectRestoreWhenProjectIsNotDeleted() {
|
||||
when(projectMapper.restoreDeletedProject(41L, "tester")).thenReturn(0);
|
||||
|
||||
assertThrows(ServiceException.class, () -> service.restoreProject(41L, "tester"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnLatestFailedTagTaskOnFailedProjectDetail() {
|
||||
Date endTime = new Date();
|
||||
@@ -127,6 +170,19 @@ class CcdiProjectServiceImplTest {
|
||||
() -> service.updateProjectStatus(99L, "3", "system"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectUpdatingDeletedProjectToTaggingOrCompleted() {
|
||||
CcdiProject deleted = new CcdiProject();
|
||||
deleted.setProjectId(99L);
|
||||
deleted.setStatus("5");
|
||||
when(projectMapper.selectById(99L)).thenReturn(deleted);
|
||||
|
||||
assertThrows(ServiceException.class,
|
||||
() -> service.updateProjectStatus(99L, "3", "system"));
|
||||
assertThrows(ServiceException.class,
|
||||
() -> service.updateProjectStatus(99L, "1", "system"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectWritingWhenProjectIsTagging() {
|
||||
CcdiProject tagging = new CcdiProject();
|
||||
|
||||
@@ -18,6 +18,8 @@ class CcdiProjectStatusSqlTest {
|
||||
String migrationSql = Files.readString(repoRoot.resolve("sql/migration/2026-03-18-add-project-tagging-status.sql"));
|
||||
String tagFailedMigrationSql =
|
||||
Files.readString(repoRoot.resolve("sql/migration/2026-05-27-add-project-tag-failed-status.sql"));
|
||||
String deletedMigrationSql =
|
||||
Files.readString(repoRoot.resolve("sql/migration/2026-07-02-add-project-deleted-status.sql"));
|
||||
|
||||
assertTrue(initSql.contains("打标中"));
|
||||
assertTrue(initSql.contains("'3'"));
|
||||
@@ -34,5 +36,14 @@ class CcdiProjectStatusSqlTest {
|
||||
assertTrue(tagFailedMigrationSql.contains("'4'"));
|
||||
assertTrue(tagFailedMigrationSql.contains("latest_task.status = 'FAILED'"));
|
||||
assertTrue(tagFailedMigrationSql.contains("project.status IN ('0', '3')"));
|
||||
|
||||
assertTrue(initSql.contains("已删除"));
|
||||
assertTrue(initSql.contains("'5'"));
|
||||
assertTrue(prodInitSql.contains("已删除"));
|
||||
assertTrue(prodInitSql.contains("'5','ccdi_project_status'"));
|
||||
assertTrue(deletedMigrationSql.contains("ccdi_project_status"));
|
||||
assertTrue(deletedMigrationSql.contains("已删除"));
|
||||
assertTrue(deletedMigrationSql.contains("'5'"));
|
||||
assertTrue(deletedMigrationSql.contains("utf8mb4_general_ci"));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user