Commit c452b0729d5ee255b34aafd4d91b1ab4b068591f

Authored by YunaiV
1 parent 7c397bbf

fix(pms): 串行化个人排序更新并统一项目删除加锁顺序

yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectService.java
... ... @@ -45,6 +45,16 @@ public interface PmsProjectService {
45 45 PmsProjectDO getProject(Long id);
46 46  
47 47 /**
  48 + * 获得项目,并锁定项目行
  49 + *
  50 + * 需在事务中调用,锁保持至事务结束
  51 + *
  52 + * @param id 项目编号
  53 + * @return 项目
  54 + */
  55 + PmsProjectDO getProjectForUpdate(Long id);
  56 +
  57 + /**
48 58 * 获得项目详情并记录访问时间
49 59 *
50 60 * @param id 项目编号
... ...
yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectServiceImpl.java
... ... @@ -118,6 +118,11 @@ public class PmsProjectServiceImpl implements PmsProjectService {
118 118 }
119 119  
120 120 @Override
  121 + public PmsProjectDO getProjectForUpdate(Long id) {
  122 + return projectMapper.selectOneForUpdate(PmsProjectDO::getId, id);
  123 + }
  124 +
  125 + @Override
121 126 public PmsProjectDO getProjectAndUpdateVisitTime(Long id, Long userId) {
122 127 // 1. 校验当前用户可以查看项目
123 128 PmsProjectDO project = projectMemberService.validateProjectReadable(id, userId);
... ... @@ -307,8 +312,11 @@ public class PmsProjectServiceImpl implements PmsProjectService {
307 312 @Override
308 313 @Transactional(rollbackFor = Exception.class)
309 314 public void deleteProject(Long id, Long userId) {
310   - // 1.1 校验项目存在
311   - PmsProjectDO project = validateProjectExists(id);
  315 + // 1.1 锁定项目并校验存在,与个人排序保持一致的加锁顺序(https://t.zsxq.com/V48Hl)
  316 + PmsProjectDO project = getProjectForUpdate(id);
  317 + if (project == null) {
  318 + throw exception(PROJECT_NOT_EXISTS);
  319 + }
312 320 // 1.2 校验项目位于回收站
313 321 if (ObjectUtil.notEqual(PmsProjectStatusEnum.RECYCLED.getStatus(), project.getStatus())) {
314 322 throw exception(PROJECT_STATUS_INVALID);
... ...
yudao-module-pms/src/main/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemUserSortServiceImpl.java
1 1 package cn.iocoder.yudao.module.pms.service.pm.workitem;
2 2  
3 3 import cn.hutool.core.collection.CollUtil;
  4 +import cn.iocoder.yudao.module.pms.dal.dataobject.pm.project.PmsProjectDO;
4 5 import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemDO;
5 6 import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemUserSortDO;
6 7 import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemUserSortMapper;
  8 +import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectStatusEnum;
  9 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectService;
7 10 import jakarta.annotation.Resource;
  11 +import org.springframework.context.annotation.Lazy;
8 12 import org.springframework.stereotype.Service;
9 13 import org.springframework.transaction.annotation.Transactional;
10 14 import org.springframework.validation.annotation.Validated;
... ... @@ -13,7 +17,10 @@ import java.util.Comparator;
13 17 import java.util.List;
14 18 import java.util.Map;
15 19  
  20 +import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
16 21 import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertMap;
  22 +import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS;
  23 +import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_STATUS_INVALID;
17 24  
18 25 /**
19 26 * PMS 工作项个人排序 Service 实现类
... ... @@ -27,13 +34,27 @@ public class PmsWorkItemUserSortServiceImpl implements PmsWorkItemUserSortServic
27 34 @Resource
28 35 private PmsWorkItemUserSortMapper workItemUserSortMapper;
29 36  
  37 + @Lazy // 延迟加载,避免循环依赖
  38 + @Resource
  39 + private PmsProjectService projectService;
  40 +
30 41 @Override
31 42 @Transactional(rollbackFor = Exception.class)
32 43 public void updateWorkItemUserSort(Long projectId, List<Long> workItemIds, Long userId) {
33   - // 1. 删除原有个人排序
  44 + // 1.1 锁定项目,避免并发更新个人排序产生重复记录(https://t.zsxq.com/V48Hl)
  45 + PmsProjectDO project = projectService.getProjectForUpdate(projectId);
  46 + // 1.2 校验锁定后的项目状态,避免项目并发回收、删除后继续写入个人排序
  47 + if (project == null) {
  48 + throw exception(PROJECT_NOT_EXISTS);
  49 + }
  50 + if (!PmsProjectStatusEnum.ACTIVE.getStatus().equals(project.getStatus())) {
  51 + throw exception(PROJECT_STATUS_INVALID);
  52 + }
  53 +
  54 + // 2. 删除原有个人排序
34 55 workItemUserSortMapper.deleteByProjectIdAndUserId(projectId, userId);
35 56  
36   - // 2. 按请求顺序保存个人排序
  57 + // 3. 按请求顺序保存个人排序
37 58 for (int index = 0; index < workItemIds.size(); index++) {
38 59 workItemUserSortMapper.insert(new PmsWorkItemUserSortDO().setProjectId(projectId)
39 60 .setWorkItemId(workItemIds.get(index)).setUserId(userId).setSort(index + 1));
... ...
yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/project/PmsProjectServiceImplTest.java
... ... @@ -33,6 +33,7 @@ import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServic
33 33 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomLongId;
34 34 import static cn.iocoder.yudao.framework.test.core.util.RandomUtils.randomPojo;
35 35 import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_ADMIN_REQUIRED;
  36 +import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS;
36 37 import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_OWNER_REQUIRED;
37 38 import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_STATUS_INVALID;
38 39 import static java.util.Arrays.asList;
... ... @@ -237,6 +238,35 @@ public class PmsProjectServiceImplTest extends BaseDbUnitTest {
237 238 }
238 239  
239 240 @Test
  241 + public void testDeleteProject_success() {
  242 + // mock 数据
  243 + Long userId = randomLongId();
  244 + PmsProjectDO project = randomProjectDO(PmsProjectStatusEnum.RECYCLED.getStatus());
  245 + projectMapper.insert(project);
  246 + when(projectMemberService.hasProjectOwnerPermission(project.getId(), userId)).thenReturn(true);
  247 +
  248 + // 调用
  249 + projectService.deleteProject(project.getId(), userId);
  250 +
  251 + // 断言
  252 + assertNull(projectMapper.selectById(project.getId()));
  253 + verify(workItemWorkLogService).deleteWorkItemWorkLogListByProjectId(project.getId());
  254 + verify(workItemService).deleteWorkItemListByProjectId(project.getId());
  255 + verify(iterationService).deleteIterationListByProjectId(project.getId());
  256 + verify(projectMemberService).deleteProjectMemberListByProjectId(project.getId());
  257 + }
  258 +
  259 + @Test
  260 + public void testDeleteProject_projectNotExists() {
  261 + // 准备参数
  262 + Long projectId = randomLongId();
  263 + Long userId = randomLongId();
  264 +
  265 + // 调用,并断言异常
  266 + assertServiceException(() -> projectService.deleteProject(projectId, userId), PROJECT_NOT_EXISTS);
  267 + }
  268 +
  269 + @Test
240 270 public void testDeleteProject_statusInvalid() {
241 271 // mock 数据
242 272 Long userId = randomLongId();
... ...
yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemServiceImplTest.java
... ... @@ -24,6 +24,7 @@ import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemMemberD
24 24 import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemStatusDO;
25 25 import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemUserSortDO;
26 26 import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemWorkLogDO;
  27 +import cn.iocoder.yudao.module.pms.dal.mysql.pm.project.PmsProjectMapper;
27 28 import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemBoardMapper;
28 29 import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemCommentMapper;
29 30 import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemMapper;
... ... @@ -39,9 +40,14 @@ import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemPriorityEnum;
39 40 import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemStatusTypeEnum;
40 41 import cn.iocoder.yudao.module.pms.enums.pm.workitem.PmsWorkItemTypeEnum;
41 42 import cn.iocoder.yudao.module.pms.service.pm.iteration.PmsIterationService;
  43 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectAnnouncementService;
  44 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectFavoriteService;
  45 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectGroupService;
42 46 import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectMemberService;
  47 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectServiceImpl;
43 48 import cn.iocoder.yudao.module.system.api.notify.NotifyMessageSendApi;
44 49 import cn.iocoder.yudao.module.system.api.notify.dto.NotifySendSingleToUserReqDTO;
  50 +import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
45 51 import cn.iocoder.yudao.module.system.api.user.AdminUserApi;
46 52 import cn.iocoder.yudao.module.system.api.user.dto.AdminUserRespDTO;
47 53 import jakarta.annotation.Resource;
... ... @@ -88,7 +94,8 @@ import static org.mockito.Mockito.when;
88 94 *
89 95 * @author 芋道源码
90 96 */
91   -@Import({PmsWorkItemServiceImpl.class, PmsWorkItemUserSortServiceImpl.class, PmsWorkItemStatusServiceImpl.class,
  97 +@Import({PmsWorkItemServiceImpl.class, PmsWorkItemUserSortServiceImpl.class, PmsProjectServiceImpl.class,
  98 + PmsWorkItemStatusServiceImpl.class,
92 99 PmsWorkItemBoardServiceImpl.class,
93 100 PmsWorkItemCommentServiceImpl.class, PmsWorkItemWorkLogServiceImpl.class})
94 101 public class PmsWorkItemServiceImplTest extends BaseDbUnitTest {
... ... @@ -110,10 +117,18 @@ public class PmsWorkItemServiceImplTest extends BaseDbUnitTest {
110 117 private PmsWorkItemWorkLogMapper workItemWorkLogMapper;
111 118 @Resource
112 119 private PmsWorkItemUserSortMapper workItemUserSortMapper;
  120 + @Resource
  121 + private PmsProjectMapper projectMapper;
113 122  
114 123 @MockitoBean
115 124 private PmsProjectMemberService projectMemberService;
116 125 @MockitoBean
  126 + private PmsProjectGroupService projectGroupService;
  127 + @MockitoBean
  128 + private PmsProjectFavoriteService projectFavoriteService;
  129 + @MockitoBean
  130 + private PmsProjectAnnouncementService projectAnnouncementService;
  131 + @MockitoBean
117 132 private PmsIterationService iterationService;
118 133 @MockitoBean
119 134 private PmsWorkItemLabelService workItemLabelService;
... ... @@ -126,6 +141,8 @@ public class PmsWorkItemServiceImplTest extends BaseDbUnitTest {
126 141 private NotifyMessageSendApi notifyMessageSendApi;
127 142 @MockitoBean
128 143 private AdminUserApi adminUserApi;
  144 + @MockitoBean
  145 + private PermissionApi permissionApi;
129 146  
130 147 @Test
131 148 public void testCreateWorkItem_success() {
... ... @@ -510,6 +527,8 @@ public class PmsWorkItemServiceImplTest extends BaseDbUnitTest {
510 527 // mock 数据
511 528 Long userId = randomLongId();
512 529 Long projectId = randomLongId();
  530 + PmsProjectDO project = randomProjectDO(projectId, PmsProjectTypeEnum.AGILE.getType());
  531 + projectMapper.insert(project);
513 532 PmsWorkItemStatusDO taskStatus = randomStatusDO(projectId, PmsWorkItemTypeEnum.TASK.getType(),
514 533 PmsWorkItemStatusTypeEnum.PENDING.getType(), true, 1);
515 534 workItemStatusMapper.insert(taskStatus);
... ... @@ -521,7 +540,7 @@ public class PmsWorkItemServiceImplTest extends BaseDbUnitTest {
521 540 PmsWorkItemDO second = randomWorkItemDO(projectId, PmsWorkItemTypeEnum.DEFECT.getType(), defectStatus, 2);
522 541 workItemMapper.insert(second);
523 542 when(projectMemberService.validateProjectWritable(projectId, userId))
524   - .thenReturn(randomProjectDO(projectId, PmsProjectTypeEnum.AGILE.getType()));
  543 + .thenReturn(project);
525 544 // 准备参数
526 545 PmsWorkItemPlanningSortReqVO reqVO = new PmsWorkItemPlanningSortReqVO().setProjectId(projectId)
527 546 .setWorkItemIds(Arrays.asList(second.getId(), first.getId()));
... ...
yudao-module-pms/src/test/java/cn/iocoder/yudao/module/pms/service/pm/workitem/PmsWorkItemUserSortServiceImplTest.java 0 → 100644
  1 +package cn.iocoder.yudao.module.pms.service.pm.workitem;
  2 +
  3 +import cn.iocoder.yudao.framework.test.core.ut.BaseDbUnitTest;
  4 +import cn.iocoder.yudao.module.pms.dal.dataobject.pm.project.PmsProjectDO;
  5 +import cn.iocoder.yudao.module.pms.dal.dataobject.pm.workitem.PmsWorkItemUserSortDO;
  6 +import cn.iocoder.yudao.module.pms.dal.mysql.pm.project.PmsProjectMapper;
  7 +import cn.iocoder.yudao.module.pms.dal.mysql.pm.workitem.PmsWorkItemUserSortMapper;
  8 +import cn.iocoder.yudao.module.pms.enums.pm.project.PmsProjectStatusEnum;
  9 +import cn.iocoder.yudao.module.pms.service.pm.iteration.PmsIterationService;
  10 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectAnnouncementService;
  11 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectFavoriteService;
  12 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectGroupService;
  13 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectMemberService;
  14 +import cn.iocoder.yudao.module.pms.service.pm.project.PmsProjectServiceImpl;
  15 +import cn.iocoder.yudao.module.system.api.permission.PermissionApi;
  16 +import jakarta.annotation.Resource;
  17 +import org.junit.jupiter.api.Test;
  18 +import org.springframework.context.annotation.Import;
  19 +import org.springframework.test.context.bean.override.mockito.MockitoBean;
  20 +
  21 +import java.util.ArrayList;
  22 +import java.util.List;
  23 +import java.util.concurrent.CountDownLatch;
  24 +import java.util.concurrent.ExecutorService;
  25 +import java.util.concurrent.Executors;
  26 +import java.util.concurrent.Future;
  27 +import java.util.concurrent.TimeUnit;
  28 +
  29 +import static cn.iocoder.yudao.framework.common.util.collection.CollectionUtils.convertList;
  30 +import static cn.iocoder.yudao.framework.test.core.util.AssertUtils.assertServiceException;
  31 +import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_NOT_EXISTS;
  32 +import static cn.iocoder.yudao.module.pms.enums.ErrorCodeConstants.PROJECT_STATUS_INVALID;
  33 +import static org.junit.jupiter.api.Assertions.assertEquals;
  34 +
  35 +/**
  36 + * {@link PmsWorkItemUserSortServiceImpl} 的单元测试类
  37 + *
  38 + * @author 芋道源码
  39 + */
  40 +@Import({PmsWorkItemUserSortServiceImpl.class, PmsProjectServiceImpl.class})
  41 +public class PmsWorkItemUserSortServiceImplTest extends BaseDbUnitTest {
  42 +
  43 + @Resource
  44 + private PmsWorkItemUserSortServiceImpl workItemUserSortService;
  45 +
  46 + @Resource
  47 + private PmsWorkItemUserSortMapper workItemUserSortMapper;
  48 + @Resource
  49 + private PmsProjectMapper projectMapper;
  50 +
  51 + @MockitoBean
  52 + private PmsProjectMemberService projectMemberService;
  53 + @MockitoBean
  54 + private PmsProjectGroupService projectGroupService;
  55 + @MockitoBean
  56 + private PmsProjectFavoriteService projectFavoriteService;
  57 + @MockitoBean
  58 + private PmsProjectAnnouncementService projectAnnouncementService;
  59 + @MockitoBean
  60 + private PmsIterationService iterationService;
  61 + @MockitoBean
  62 + private PmsWorkItemService workItemService;
  63 + @MockitoBean
  64 + private PmsWorkItemStatusService workItemStatusService;
  65 + @MockitoBean
  66 + private PmsWorkItemWorkLogService workItemWorkLogService;
  67 + @MockitoBean
  68 + private PermissionApi permissionApi;
  69 +
  70 + @Test
  71 + public void testUpdateWorkItemUserSort_success() {
  72 + // mock 数据
  73 + PmsProjectDO project = new PmsProjectDO().setName("测试项目").setType(1).setLevel(1);
  74 + projectMapper.insert(project);
  75 + workItemUserSortService.updateWorkItemUserSort(project.getId(), List.of(50L, 51L), 238L);
  76 + workItemUserSortService.updateWorkItemUserSort(project.getId(), List.of(50L, 51L), 239L);
  77 +
  78 + // 调用:连续调整个人排序,并移除不再位于待规划区域的工作项
  79 + workItemUserSortService.updateWorkItemUserSort(project.getId(), List.of(51L, 50L), 238L);
  80 + workItemUserSortService.updateWorkItemUserSort(project.getId(), List.of(50L), 238L);
  81 +
  82 + // 断言:仅保留当前顺序,不影响其他用户的个人排序
  83 + List<PmsWorkItemUserSortDO> userSorts = workItemUserSortMapper
  84 + .selectListByProjectIdAndUserId(project.getId(), 238L);
  85 + assertEquals(List.of(50L), convertList(userSorts, PmsWorkItemUserSortDO::getWorkItemId));
  86 + assertEquals(1, userSorts.get(0).getSort());
  87 + assertEquals(List.of(50L, 51L), convertList(workItemUserSortMapper
  88 + .selectListByProjectIdAndUserId(project.getId(), 239L), PmsWorkItemUserSortDO::getWorkItemId));
  89 + }
  90 +
  91 + @Test
  92 + public void testUpdateWorkItemUserSort_projectNotExists() {
  93 + // 调用,并断言异常
  94 + assertServiceException(() -> workItemUserSortService.updateWorkItemUserSort(999L,
  95 + List.of(50L), 238L), PROJECT_NOT_EXISTS);
  96 + assertEquals(0L, workItemUserSortMapper.selectCount());
  97 + }
  98 +
  99 + @Test
  100 + public void testUpdateWorkItemUserSort_projectDeleted() {
  101 + // mock 数据
  102 + PmsProjectDO project = new PmsProjectDO().setName("测试项目").setType(1).setLevel(1);
  103 + projectMapper.insert(project);
  104 + projectMapper.deleteById(project.getId());
  105 +
  106 + // 调用,并断言异常
  107 + assertServiceException(() -> workItemUserSortService.updateWorkItemUserSort(project.getId(),
  108 + List.of(50L), 238L), PROJECT_NOT_EXISTS);
  109 + assertEquals(0L, workItemUserSortMapper.selectCount());
  110 + }
  111 +
  112 + @Test
  113 + public void testUpdateWorkItemUserSort_projectStatusInvalid() {
  114 + // mock 数据
  115 + PmsProjectDO project = new PmsProjectDO().setName("测试项目").setType(1).setLevel(1)
  116 + .setStatus(PmsProjectStatusEnum.RECYCLED.getStatus());
  117 + projectMapper.insert(project);
  118 + workItemUserSortMapper.insert(new PmsWorkItemUserSortDO().setProjectId(project.getId())
  119 + .setWorkItemId(50L).setUserId(238L).setSort(1));
  120 +
  121 + // 调用,并断言异常
  122 + assertServiceException(() -> workItemUserSortService.updateWorkItemUserSort(project.getId(),
  123 + List.of(51L), 238L), PROJECT_STATUS_INVALID);
  124 + // 断言:拒绝更新时保留原有个人排序
  125 + assertEquals(List.of(50L), convertList(workItemUserSortMapper
  126 + .selectListByProjectIdAndUserId(project.getId(), 238L), PmsWorkItemUserSortDO::getWorkItemId));
  127 + }
  128 +
  129 + @Test
  130 + public void testUpdateWorkItemUserSort_concurrent() throws Exception {
  131 + // mock 数据
  132 + PmsProjectDO project = new PmsProjectDO().setName("测试项目").setType(1).setLevel(1);
  133 + projectMapper.insert(project);
  134 + // 准备参数
  135 + CountDownLatch startLatch = new CountDownLatch(1);
  136 + ExecutorService executor = Executors.newFixedThreadPool(4);
  137 + List<Future<?>> futures = new ArrayList<>();
  138 +
  139 + // 调用:同一项目、同一用户并发保存个人排序
  140 + try {
  141 + for (int index = 0; index < 4; index++) {
  142 + futures.add(executor.submit(() -> {
  143 + startLatch.await();
  144 + for (int count = 0; count < 5; count++) {
  145 + workItemUserSortService.updateWorkItemUserSort(project.getId(), List.of(51L, 50L), 238L);
  146 + }
  147 + return null;
  148 + }));
  149 + }
  150 + startLatch.countDown();
  151 + for (Future<?> future : futures) {
  152 + future.get(20, TimeUnit.SECONDS);
  153 + }
  154 + } finally {
  155 + executor.shutdownNow();
  156 + executor.awaitTermination(10, TimeUnit.SECONDS);
  157 + }
  158 +
  159 + // 断言:没有重复的有效记录,顺序与最后一次保存一致
  160 + List<PmsWorkItemUserSortDO> userSorts = workItemUserSortMapper
  161 + .selectListByProjectIdAndUserId(project.getId(), 238L);
  162 + assertEquals(List.of(51L, 50L), convertList(userSorts, PmsWorkItemUserSortDO::getWorkItemId));
  163 + assertEquals(List.of(1, 2), convertList(userSorts, PmsWorkItemUserSortDO::getSort));
  164 + }
  165 +
  166 +}
... ...
yudao-module-pms/src/test/resources/sql/clean.sql
... ... @@ -5,6 +5,7 @@ DELETE FROM &quot;pms_work_item_comment&quot;;
5 5 DELETE FROM "pms_work_item_label";
6 6 DELETE FROM "pms_work_item_work_log";
7 7 DELETE FROM "pms_work_item_member";
  8 +DELETE FROM "pms_work_item_user_sort";
8 9 DELETE FROM "pms_work_item";
9 10 DELETE FROM "pms_work_item_status";
10 11 DELETE FROM "pms_iteration";
... ...