fix(产品需求、项目需求): 按照会议所说进行修改。

This commit is contained in:
dk
2026-05-18 16:44:29 +08:00
parent 50b84a57bb
commit 75886d7af5
42 changed files with 506 additions and 92 deletions

View File

@@ -19,4 +19,7 @@ public class DictDataRespDTO {
@Schema(description = "状态", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
private Integer status; // 参见 CommonStatusEnum 枚举
@Schema(description = "标识", example = "system")
private String sign;
}

View File

@@ -59,11 +59,12 @@ public interface ErrorCodeConstants {
ErrorCode REQUIREMENT_MODULE_HAS_NON_TERMINAL_REQUIREMENTS = new ErrorCode(1_008_002_012, "模块下存在非终态需求,不可删除");
ErrorCode REQUIREMENT_MODULE_HAS_CHILDREN = new ErrorCode(1_008_002_015, "存在子模块,请先删除子模块");
ErrorCode REQUIREMENT_MODULE_HAS_REQUIREMENTS = new ErrorCode(1_008_002_016, "模块下存在需求,请先删除需求");
ErrorCode PROJECT_REQUIREMENT_MODULE_ROOT_NOT_EXISTS = new ErrorCode(1_008_002_018, "实现项目下不存在根模块,请先创建项目模块");
ErrorCode PROJECT_REQUIREMENT_MODULE_ROOT_NOT_EXISTS = new ErrorCode(1_008_002_018, "关联项目下不存在根模块,请先创建项目模块");
ErrorCode REQUIREMENT_DISPATCHED_NOT_ALLOW_SPLIT = new ErrorCode(1_008_002_019, "产品需求已分流生成项目需求,不允许再在产品端拆分");
ErrorCode REQUIREMENT_NOT_DISPATCHED = new ErrorCode(1_008_002_020, "该产品需求尚未分流到实现项目");
ErrorCode REQUIREMENT_NOT_DISPATCHED = new ErrorCode(1_008_002_020, "该产品需求尚未分流到关联项目");
ErrorCode REQUIREMENT_DISPATCHED_PROJECT_REQUIREMENT_NOT_FOUND = new ErrorCode(1_008_002_021, "未找到该产品需求对应的项目需求");
ErrorCode REQUIREMENT_NOT_PROJECT_MEMBER = new ErrorCode(1_008_002_022, "您不是该项目的成员,无权访问");
ErrorCode REQUIREMENT_HANDLER_NOT_PROJECT_MEMBER = new ErrorCode(1_008_002_023, "当前需求负责人不是所选分流项目的成员,请重新选择");
// ========== 项目管理 1-008-002-000 ==========
ErrorCode PROJECT_NOT_EXISTS = new ErrorCode(1_008_002_000, "项目不存在");
@@ -189,4 +190,5 @@ public interface ErrorCodeConstants {
ErrorCode PROJECT_REQUIREMENT_MODULE_HAS_CHILDREN = new ErrorCode(1_008_007_015, "存在子模块,请先删除子模块");
ErrorCode PROJECT_REQUIREMENT_MODULE_HAS_REQUIREMENTS = new ErrorCode(1_008_007_016, "模块下存在项目需求,请先删除需求");
ErrorCode PROJECT_REQUIREMENT_CHILD_NOT_ALLOW_CANCEL = new ErrorCode(1_008_007_017, "只有不存在子需求,或子需求都处于已取消和已拒绝状态时,父需求才允许取消");
ErrorCode PROJECT_REQUIREMENT_SYNCED_FROM_PRODUCT_NOT_ALLOW_CANCEL = new ErrorCode(1_008_007_019, "\u7531\u4ea7\u54c1\u9700\u6c42\u6d41\u8f6c\u751f\u6210\u7684\u9879\u76ee\u9700\u6c42\u4e0d\u5141\u8bb8\u53d6\u6d88");
}

View File

@@ -101,6 +101,13 @@ public class ProductRequirementController {
return success(requirementService.getAllowedTransitions(requirementId, productId));
}
@PostMapping("/allowed-transitions/batch")
@Operation(summary = "批量获取需求可执行的状态动作列表")
public CommonResult<List<ProductRequirementAllowedTransitionBatchRespVO>> getAllowedTransitionsBatch(
@Valid @RequestBody ProductRequirementBatchReqVO reqVO) {
return success(requirementService.getAllowedTransitionsBatch(reqVO));
}
@GetMapping("/has-dispatched")
@Operation(summary = "判断产品需求是否已分流生成项目需求")
@Parameter(name = "requirementId", description = "需求编号", required = true, example = "1024")
@@ -111,6 +118,13 @@ public class ProductRequirementController {
return success(requirementService.hasDispatchedProjectRequirement(requirementId, productId));
}
@PostMapping("/has-dispatched/batch")
@Operation(summary = "批量判断产品需求是否已分流生成项目需求")
public CommonResult<List<ProductRequirementHasDispatchedBatchRespVO>> hasDispatchedProjectRequirementBatch(
@Valid @RequestBody ProductRequirementBatchReqVO reqVO) {
return success(requirementService.hasDispatchedProjectRequirementBatch(reqVO));
}
@GetMapping("/lifecycle")
@Operation(summary = "获取需求生命周期信息")
@Parameter(name = "requirementId", description = "需求编号", required = true, example = "1024")

View File

@@ -0,0 +1,18 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - 产品需求批量可执行动作 Response VO")
@Data
public class ProductRequirementAllowedTransitionBatchRespVO {
@Schema(description = "需求编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long requirementId;
@Schema(description = "可执行动作列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ProductRequirementStatusTransitionRespVO> transitions;
}

View File

@@ -0,0 +1,22 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - 产品需求批量查询 Request VO")
@Data
public class ProductRequirementBatchReqVO {
@Schema(description = "产品编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "产品编号不能为空")
private Long productId;
@Schema(description = "需求编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2, 3]")
@NotEmpty(message = "需求编号列表不能为空")
private List<Long> requirementIds;
}

View File

@@ -13,7 +13,7 @@ public class ProductRequirementDispatchedProjectLinkRespVO {
@Schema(description = "项目需求ID", example = "10086")
private Long projectRequirementId;
@Schema(description = "实现项目ID", example = "8888")
@Schema(description = "关联项目ID", example = "8888")
private Long projectId;
}

View File

@@ -0,0 +1,15 @@
package com.njcn.rdms.module.project.controller.admin.product.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
@Schema(description = "管理后台 - 产品需求批量分流状态 Response VO")
@Data
public class ProductRequirementHasDispatchedBatchRespVO {
@Schema(description = "需求编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long requirementId;
@Schema(description = "是否已分流生成项目需求", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
private Boolean hasDispatched;
}

View File

@@ -4,6 +4,7 @@ import com.njcn.rdms.module.project.dal.dataobject.attachment.AttachmentItem;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@@ -65,8 +66,8 @@ public class ProductRequirementRespVO {
@Schema(description = "提出人用户姓名", example = "张三")
private String proposerNickname;
@Schema(description = "所需工时", example = "8")
private Double workHours;
@Schema(description = "预期完成时间", example = "2026-05-31")
private LocalDate expectedTime;
@Schema(description = "当前处理人用户ID", example = "1024")
private Long currentHandlerUserId;

View File

@@ -8,6 +8,7 @@ import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDate;
import java.util.List;
/**
@@ -55,9 +56,8 @@ public class ProductRequirementSaveReqVO {
@Schema(description = "提出人姓名", example = "张三")
private String proposerNickname;
@Schema(description = "所需工时", example = "8")
@NotNull(message = "所需工时不能为空")
private Double workHours;
@Schema(description = "预期完成时间", example = "2026-05-31")
private LocalDate expectedTime;
@Schema(description = "当前处理人用户ID", example = "1024")
private Long currentHandlerUserId;

View File

@@ -8,6 +8,7 @@ import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDate;
import java.util.List;
/**
@@ -56,9 +57,8 @@ public class ProductRequirementSplitReqVO {
@Schema(description = "提出人姓名", example = "张三")
private String proposerNickname;
@Schema(description = "所需工时", example = "8")
@NotNull(message = "所需工时不能为空")
private Double workHours;
@Schema(description = "预期完成时间", example = "2026-05-31")
private LocalDate expectedTime;
@Schema(description = "当前处理人用户ID", example = "1024")
private Long currentHandlerUserId;

View File

@@ -30,6 +30,6 @@ public class ProductRequirementStatusActionReqVO {
@Schema(description = "状态变更原因", example = "评审通过,进入分流阶段")
private String reason;
@Schema(description = "实现项目编号dispatch动作时可选", example = "1024")
@Schema(description = "关联项目编号dispatch动作时可选", example = "1024")
private Long implementProjectId;
}

View File

@@ -8,6 +8,7 @@ import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDate;
import java.util.List;
/**
@@ -56,9 +57,8 @@ public class ProductRequirementUpdateReqVO {
@Schema(description = "提出人姓名", example = "张三")
private String proposerNickname;
@Schema(description = "所需工时", example = "8")
@NotNull(message = "所需工时不能为空")
private Double workHours;
@Schema(description = "预期完成时间", example = "2026-05-31")
private LocalDate expectedTime;
@Schema(description = "当前处理人用户ID", example = "1024")
private Long currentHandlerUserId;

View File

@@ -2,6 +2,8 @@ package com.njcn.rdms.module.project.controller.admin.project;
import com.njcn.rdms.framework.common.pojo.CommonResult;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementAllowedTransitionBatchRespVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementBatchReqVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementCloseReqVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementDeleteReqVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementLifecycleRespVO;
@@ -112,6 +114,13 @@ public class ProjectRequirementController {
return success(requirementService.getAllowedTransitions(requirementId, projectId));
}
@PostMapping("/allowed-transitions/batch")
@Operation(summary = "批量获取需求可执行的状态动作列表")
public CommonResult<List<ProjectRequirementAllowedTransitionBatchRespVO>> getAllowedTransitionsBatch(
@Valid @RequestBody ProjectRequirementBatchReqVO reqVO) {
return success(requirementService.getAllowedTransitionsBatch(reqVO));
}
@GetMapping("/lifecycle")
@Operation(summary = "获取需求生命周期信息")
@Parameter(name = "requirementId", description = "需求编号", required = true, example = "1024")

View File

@@ -0,0 +1,18 @@
package com.njcn.rdms.module.project.controller.admin.project.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - 项目需求批量可执行动作 Response VO")
@Data
public class ProjectRequirementAllowedTransitionBatchRespVO {
@Schema(description = "需求编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
private Long requirementId;
@Schema(description = "可执行动作列表", requiredMode = Schema.RequiredMode.REQUIRED)
private List<ProjectRequirementStatusTransitionRespVO> transitions;
}

View File

@@ -0,0 +1,22 @@
package com.njcn.rdms.module.project.controller.admin.project.vo.requirement;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotEmpty;
import jakarta.validation.constraints.NotNull;
import lombok.Data;
import java.util.List;
@Schema(description = "管理后台 - 项目需求批量查询 Request VO")
@Data
public class ProjectRequirementBatchReqVO {
@Schema(description = "项目编号", requiredMode = Schema.RequiredMode.REQUIRED, example = "1024")
@NotNull(message = "项目编号不能为空")
private Long projectId;
@Schema(description = "需求编号列表", requiredMode = Schema.RequiredMode.REQUIRED, example = "[1, 2, 3]")
@NotEmpty(message = "需求编号列表不能为空")
private List<Long> requirementIds;
}

View File

@@ -4,6 +4,7 @@ import com.njcn.rdms.module.project.dal.dataobject.attachment.AttachmentItem;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.util.List;
@@ -68,8 +69,8 @@ public class ProjectRequirementRespVO {
@Schema(description = "提出人姓名", example = "张三")
private String proposerNickname;
@Schema(description = "所需工时", example = "8")
private Double workHours;
@Schema(description = "预期完成时间", example = "2026-05-31")
private LocalDate expectedTime;
@Schema(description = "当前处理人用户ID", example = "1024")
private Long currentHandlerUserId;

View File

@@ -8,6 +8,7 @@ import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDate;
import java.util.List;
/**
@@ -55,9 +56,8 @@ public class ProjectRequirementSaveReqVO {
@Schema(description = "提出人姓名", example = "张三")
private String proposerNickname;
@Schema(description = "所需工时", example = "8")
@NotNull(message = "所需工时不能为空")
private Double workHours;
@Schema(description = "预期完成时间", example = "2026-05-31")
private LocalDate expectedTime;
@Schema(description = "当前处理人用户ID", example = "1024")
private Long currentHandlerUserId;

View File

@@ -8,6 +8,7 @@ import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDate;
import java.util.List;
/**
@@ -56,9 +57,8 @@ public class ProjectRequirementSplitReqVO {
@Schema(description = "提出人姓名", example = "张三")
private String proposerNickname;
@Schema(description = "所需工时", example = "8")
@NotNull(message = "所需工时不能为空")
private Double workHours;
@Schema(description = "预期完成时间", example = "2026-05-31")
private LocalDate expectedTime;
@Schema(description = "当前处理人用户ID", example = "1024")
private Long currentHandlerUserId;

View File

@@ -8,6 +8,7 @@ import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.time.LocalDate;
import java.util.List;
/**
@@ -56,9 +57,8 @@ public class ProjectRequirementUpdateReqVO {
@Schema(description = "提出人姓名", example = "张三")
private String proposerNickname;
@Schema(description = "所需工时", example = "8")
@NotNull(message = "所需工时不能为空")
private Double workHours;
@Schema(description = "预期完成时间", example = "2026-05-31")
private LocalDate expectedTime;
@Schema(description = "当前处理人用户ID", example = "1024")
private Long currentHandlerUserId;

View File

@@ -9,6 +9,7 @@ import com.njcn.rdms.module.project.dal.dataobject.attachment.AttachmentItem;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDate;
import java.util.List;
/**
@@ -81,9 +82,9 @@ public class ProductRequirementDO extends BaseDO {
*/
private String proposerNickname;
/**
* 所需工时
* 预期完成时间
*/
private Double workHours;
private LocalDate expectedTime;
/**
* 当前处理人用户ID
*/
@@ -101,7 +102,7 @@ public class ProductRequirementDO extends BaseDO {
*/
private Integer sort;
/**
* 闄勪欢鍒楄〃锛圝SON锛夈€傚厓绱?{@link AttachmentItem}锛歩d / url / name / size / contentType銆?
* 附件列表
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private List<AttachmentItem> attachments;

View File

@@ -9,6 +9,7 @@ import com.njcn.rdms.module.project.dal.dataobject.attachment.AttachmentItem;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.time.LocalDate;
import java.util.List;
/**
@@ -93,15 +94,15 @@ public class ProjectRequirementDO extends BaseDO {
*/
private String currentHandlerUserNickname;
/**
* 预估工时
* 预期完成时间
*/
private Double workHours;
private LocalDate expectedTime;
/**
* 排序值
*/
private Integer sort;
/**
* 闄勪欢鍒楄〃锛圝SON锛夈€傚厓绱?{@link AttachmentItem}锛歩d / url / name / size / contentType銆?
* 附件项列表
*/
@TableField(typeHandler = JacksonTypeHandler.class)
private List<AttachmentItem> attachments;

View File

@@ -112,7 +112,7 @@ public interface ProductRequirementMapper extends BaseMapperX<ProductRequirement
}
/**
* 带并发控制的状态更新(支持同时更新实现项目ID
* 带并发控制的状态更新(支持同时更新关联项目ID
*/
default int updateStatusByIdAndStatusWithProject(Long id, String fromStatus, String toStatus, String lastStatusReason, Long implementProjectId) {
ProductRequirementDO update = new ProductRequirementDO();

View File

@@ -61,6 +61,13 @@ public interface ObjectStatusTransitionMapper extends BaseMapperX<ObjectStatusTr
.eq(ObjectStatusTransitionDO::getStatus, 0));
}
default List<ObjectStatusTransitionDO> selectListByObjectTypeAndFromStatuses(String objectType, List<String> fromStatusCodes) {
return selectList(new LambdaQueryWrapperX<ObjectStatusTransitionDO>()
.eq(ObjectStatusTransitionDO::getObjectType, objectType)
.in(ObjectStatusTransitionDO::getFromStatusCode, fromStatusCodes)
.eq(ObjectStatusTransitionDO::getStatus, 0));
}
/**
* 统计某状态编码在流转配置中的引用次数。
*/

View File

@@ -88,6 +88,14 @@ public interface ProductRequirementService {
*/
List<ProductRequirementStatusTransitionRespVO> getAllowedTransitions(Long requirementId, Long productId);
/**
* 批量获取需求当前可执行的状态动作列表
*
* @param reqVO 批量查询请求
* @return 按需求编号标识的可执行动作列表
*/
List<ProductRequirementAllowedTransitionBatchRespVO> getAllowedTransitionsBatch(ProductRequirementBatchReqVO reqVO);
/**
* 判断需求是否已分流并生成项目需求
*
@@ -97,6 +105,14 @@ public interface ProductRequirementService {
*/
boolean hasDispatchedProjectRequirement(Long requirementId, Long productId);
/**
* 批量判断需求是否已分流并生成项目需求
*
* @param reqVO 批量查询请求
* @return 按需求编号标识的分流状态
*/
List<ProductRequirementHasDispatchedBatchRespVO> hasDispatchedProjectRequirementBatch(ProductRequirementBatchReqVO reqVO);
/**
* 获取需求生命周期信息(当前状态 + 可执行动作)
*
@@ -157,7 +173,7 @@ public interface ProductRequirementService {
* 获取产品需求分流后对应的项目需求跳转链接
*
* @param productRequirementId 产品需求编号
* @return 项目需求ID和实现项目ID
* @return 项目需求ID和关联项目ID
*/
ProductRequirementDispatchedProjectLinkRespVO getDispatchedProjectLink(Long productRequirementId);

View File

@@ -9,6 +9,7 @@ import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.module.project.controller.admin.product.vo.requirement.*;
import com.njcn.rdms.module.project.constant.ProjectObjectConstants;
import com.njcn.rdms.module.project.dal.dataobject.audit.BizAuditLogDO;
import com.njcn.rdms.module.project.dal.dataobject.member.UserObjectRoleDO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductRequirementDO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductRequirementModuleDO;
import com.njcn.rdms.module.project.dal.dataobject.product.ProductRequirementStatusLogDO;
@@ -143,11 +144,11 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
requirement.setStatusCode(initialStatus);
requirement.setProposerId(createReqVO.getProposerId());
requirement.setProposerNickname(normalizeNullableText(createReqVO.getProposerNickname()));
requirement.setWorkHours(createReqVO.getWorkHours());
requirement.setExpectedTime(createReqVO.getExpectedTime());
requirement.setCurrentHandlerUserId(createReqVO.getCurrentHandlerUserId());
requirement.setCurrentHandlerUserNickname(normalizeNullableText(createReqVO.getCurrentHandlerUserNickname()));
requirement.setImplementProjectId(createReqVO.getImplementProjectId());
requirement.setSort(createReqVO.getSort() != null ? createReqVO.getSort() : 0);
requirement.setSort(createReqVO.getSort());
requirement.setAttachments(createReqVO.getAttachments());
requirementMapper.insert(requirement);
@@ -182,11 +183,11 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
requirement.setPriority(updateReqVO.getPriority());
requirement.setProposerId(updateReqVO.getProposerId());
requirement.setProposerNickname(normalizeNullableText(updateReqVO.getProposerNickname()));
requirement.setWorkHours(updateReqVO.getWorkHours());
requirement.setExpectedTime(updateReqVO.getExpectedTime());
requirement.setCurrentHandlerUserId(updateReqVO.getCurrentHandlerUserId());
requirement.setCurrentHandlerUserNickname(normalizeNullableText(updateReqVO.getCurrentHandlerUserNickname()));
requirement.setImplementProjectId(updateReqVO.getImplementProjectId());
requirement.setSort(updateReqVO.getSort() != null ? updateReqVO.getSort() : 0);
requirement.setSort(updateReqVO.getSort());
requirement.setAttachments(updateReqVO.getAttachments());
requirementMapper.updateById(requirement);
@@ -262,7 +263,7 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
// 第三步:查询根需求详情并按排序值升序、创建时间倒排
List<ProductRequirementDO> rootRequirements = requirementMapper.selectBatchIds(rootIds);
rootRequirements.sort((a, b) -> {
int sortCompare = Integer.compare(a.getSort() != null ? a.getSort() : 0, b.getSort() != null ? b.getSort() : 0);
int sortCompare = Comparator.nullsLast(Integer::compareTo).compare(a.getSort(), b.getSort());
if (sortCompare != 0) {
return sortCompare;
}
@@ -496,11 +497,19 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
if (ACTION_CLOSE.equals(actionCode)) {
closeAllAcceptedChildren(reqVO.getId(), reason);
}
// dispatch动作且选择了实现项目时,自动创建对应的项目需求
// dispatch动作且选择了关联项目时,校验负责人是否在项目中,然后自动创建对应的项目需求
if (ACTION_DISPATCH.equals(actionCode) && implementProjectId != null) {
// 校验负责人是否为目标项目的成员
if (requirement.getCurrentHandlerUserId() != null) {
List<UserObjectRoleDO> userObjectRoleDOS = userObjectRoleMapper.selectActiveListByObjectAndUserId(
ProjectObjectConstants.OBJECT_TYPE, implementProjectId, requirement.getCurrentHandlerUserId());
if (userObjectRoleDOS.isEmpty()) {
throw exception(ErrorCodeConstants.REQUIREMENT_HANDLER_NOT_PROJECT_MEMBER);
}
}
createProjectRequirementFromProduct(requirement, implementProjectId);
}
// 带并发控制的状态更新(支持同时更新实现项目ID
// 带并发控制的状态更新(支持同时更新关联项目ID
int updateCount = requirementMapper.updateStatusByIdAndStatusWithProject(requirement.getId(), fromStatus, toStatus, reason, implementProjectId);
if (updateCount != 1) {
throw exception(ErrorCodeConstants.REQUIREMENT_STATUS_CONCURRENT_MODIFIED);
@@ -613,11 +622,11 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
childRequirement.setStatusCode(initialStatus);
childRequirement.setProposerId(reqVO.getProposerId());
childRequirement.setProposerNickname(normalizeNullableText(reqVO.getProposerNickname()));
childRequirement.setWorkHours(reqVO.getWorkHours());
childRequirement.setExpectedTime(reqVO.getExpectedTime());
childRequirement.setCurrentHandlerUserId(reqVO.getCurrentHandlerUserId());
childRequirement.setCurrentHandlerUserNickname(normalizeNullableText(reqVO.getCurrentHandlerUserNickname()));
childRequirement.setImplementProjectId(reqVO.getImplementProjectId());
childRequirement.setSort(reqVO.getSort() != null ? reqVO.getSort() : 0);
childRequirement.setSort(reqVO.getSort());
childRequirement.setAttachments(reqVO.getAttachments());
requirementMapper.insert(childRequirement);
@@ -721,16 +730,38 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
// 取消动作不满足前置条件时,不再返回给前端展示按钮
.filter(transition -> shouldExposeTransition(requirement, transition))
.map(transition -> {
ProductRequirementStatusTransitionRespVO vo = new ProductRequirementStatusTransitionRespVO();
vo.setActionCode(transition.getActionCode());
vo.setActionName(transition.getActionName());
vo.setToStatusCode(transition.getToStatusCode());
// 查询目标状态名称
ObjectStatusModelDO statusModel = statusModelMapper
.selectByObjectTypeAndStatusCode(REQUIREMENT_OBJECT_TYPE, transition.getToStatusCode());
vo.setToStatusName(statusModel != null ? statusModel.getStatusName() : transition.getToStatusCode());
vo.setNeedReason(transition.getNeedReason());
return vo;
ProductRequirementStatusTransitionRespVO vo = new ProductRequirementStatusTransitionRespVO();
vo.setActionCode(transition.getActionCode());
vo.setActionName(transition.getActionName());
vo.setToStatusCode(transition.getToStatusCode());
// 查询目标状态名称
ObjectStatusModelDO statusModel = statusModelMapper
.selectByObjectTypeAndStatusCode(REQUIREMENT_OBJECT_TYPE, transition.getToStatusCode());
vo.setToStatusName(statusModel != null ? statusModel.getStatusName() : transition.getToStatusCode());
vo.setNeedReason(transition.getNeedReason());
return vo;
}).collect(Collectors.toList());
}
@Override
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#reqVO.productId",
permission = PRODUCT_QUERY_PERMISSION)
public List<ProductRequirementAllowedTransitionBatchRespVO> getAllowedTransitionsBatch(ProductRequirementBatchReqVO reqVO) {
List<ProductRequirementDO> requirements = getBatchRequirements(reqVO.getRequirementIds(), reqVO.getProductId());
Set<Long> dispatchedRequirementIds = getDispatchedProductRequirementIds(requirements);
Map<String, List<ObjectStatusTransitionDO>> transitionsByStatus = getTransitionsByStatus(requirements);
Map<String, ObjectStatusModelDO> statusModelMap = getStatusModelMap();
return requirements.stream().map(requirement -> {
ProductRequirementAllowedTransitionBatchRespVO respVO = new ProductRequirementAllowedTransitionBatchRespVO();
respVO.setRequirementId(requirement.getId());
if (dispatchedRequirementIds.contains(requirement.getId())) {
respVO.setTransitions(Collections.emptyList());
} else {
respVO.setTransitions(buildAllowedTransitions(requirement,
transitionsByStatus.getOrDefault(requirement.getStatusCode(), Collections.emptyList()), statusModelMap));
}
return respVO;
}).collect(Collectors.toList());
}
@@ -742,13 +773,27 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
return hasDispatchedProjectRequirement(requirement);
}
@Override
@CheckObjectPermission(objectType = PRODUCT_OBJECT_TYPE, objectId = "#reqVO.productId",
permission = PRODUCT_QUERY_PERMISSION)
public List<ProductRequirementHasDispatchedBatchRespVO> hasDispatchedProjectRequirementBatch(ProductRequirementBatchReqVO reqVO) {
List<ProductRequirementDO> requirements = getBatchRequirements(reqVO.getRequirementIds(), reqVO.getProductId());
Set<Long> dispatchedRequirementIds = getDispatchedProductRequirementIds(requirements);
return requirements.stream().map(requirement -> {
ProductRequirementHasDispatchedBatchRespVO respVO = new ProductRequirementHasDispatchedBatchRespVO();
respVO.setRequirementId(requirement.getId());
respVO.setHasDispatched(dispatchedRequirementIds.contains(requirement.getId()));
return respVO;
}).collect(Collectors.toList());
}
/**
* 该方法作用和getAllowedTransitions()类似,是用来获取当前状态下可以进行的动作
* @deprecated 产品需求页面最开始用来下拉框改状态时使用的,已经弃用
*
* @param requirementId 需求编号
* @param productId 产品编号
* @return ProductRequirementLifecycleRespVO
* @deprecated 产品需求页面最开始用来下拉框改状态时使用的,已经弃用
*/
@Override
@Deprecated
@@ -778,7 +823,7 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
@Override
public ProductRequirementDispatchedProjectLinkRespVO getDispatchedProjectLink(Long productRequirementId) {
// 校验产品需求是否存在,以及是否已分流到具体的实现项目
// 校验产品需求是否存在,以及是否已分流到具体的关联项目
ProductRequirementDO requirement = validateRequirementExists(productRequirementId);
if (requirement.getImplementProjectId() == null) {
throw exception(ErrorCodeConstants.REQUIREMENT_NOT_DISPATCHED);
@@ -823,6 +868,78 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
return !projectRequirements.isEmpty();
}
private List<ProductRequirementDO> getBatchRequirements(List<Long> requirementIds, Long productId) {
if (requirementIds == null || requirementIds.isEmpty()) {
return Collections.emptyList();
}
if (requirementIds.stream().anyMatch(Objects::isNull)) {
throw invalidParamException("需求编号不能为空");
}
List<Long> distinctIds = requirementIds.stream().distinct().collect(Collectors.toList());
Map<Long, ProductRequirementDO> requirementMap = requirementMapper.selectByIds(distinctIds).stream()
.collect(Collectors.toMap(ProductRequirementDO::getId, Function.identity()));
if (requirementMap.size() != distinctIds.size()) {
throw exception(ErrorCodeConstants.REQUIREMENT_NOT_EXISTS);
}
List<ProductRequirementDO> requirements = distinctIds.stream()
.map(requirementMap::get)
.collect(Collectors.toList());
for (ProductRequirementDO requirement : requirements) {
if (!Objects.equals(requirement.getProductId(), productId)) {
throw invalidParamException("需求不属于当前产品");
}
}
return requirements;
}
private Set<Long> getDispatchedProductRequirementIds(List<ProductRequirementDO> requirements) {
List<Long> candidateIds = requirements.stream()
.filter(requirement -> requirement.getImplementProjectId() != null)
.map(ProductRequirementDO::getId)
.collect(Collectors.toList());
if (candidateIds.isEmpty()) {
return Collections.emptySet();
}
return projectRequirementMapper.selectList(new LambdaQueryWrapperX<ProjectRequirementDO>()
.eq(ProjectRequirementDO::getSourceType, "product_requirement")
.in(ProjectRequirementDO::getProductRequirementId, candidateIds))
.stream()
.map(ProjectRequirementDO::getProductRequirementId)
.collect(Collectors.toSet());
}
private Map<String, List<ObjectStatusTransitionDO>> getTransitionsByStatus(List<ProductRequirementDO> requirements) {
List<String> statusCodes = requirements.stream()
.map(ProductRequirementDO::getStatusCode)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (statusCodes.isEmpty()) {
return Collections.emptyMap();
}
return statusTransitionMapper.selectListByObjectTypeAndFromStatuses(REQUIREMENT_OBJECT_TYPE, statusCodes)
.stream()
.collect(Collectors.groupingBy(ObjectStatusTransitionDO::getFromStatusCode));
}
private List<ProductRequirementStatusTransitionRespVO> buildAllowedTransitions(
ProductRequirementDO requirement,
List<ObjectStatusTransitionDO> transitions,
Map<String, ObjectStatusModelDO> statusModelMap) {
return transitions.stream()
.filter(transition -> shouldExposeTransition(requirement, transition))
.map(transition -> {
ProductRequirementStatusTransitionRespVO vo = new ProductRequirementStatusTransitionRespVO();
vo.setActionCode(transition.getActionCode());
vo.setActionName(transition.getActionName());
vo.setToStatusCode(transition.getToStatusCode());
ObjectStatusModelDO statusModel = statusModelMap.get(transition.getToStatusCode());
vo.setToStatusName(statusModel != null ? statusModel.getStatusName() : transition.getToStatusCode());
vo.setNeedReason(transition.getNeedReason());
return vo;
}).collect(Collectors.toList());
}
/**
* 已分流并生成项目需求后,产品需求端不再允许继续拆分。
*/
@@ -1265,7 +1382,7 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
target.setLastStatusReason(source.getLastStatusReason());
target.setProposerId(source.getProposerId());
target.setProposerNickname(source.getProposerNickname());
target.setWorkHours(source.getWorkHours());
target.setExpectedTime(source.getExpectedTime());
target.setCurrentHandlerUserId(source.getCurrentHandlerUserId());
target.setCurrentHandlerUserNickname(source.getCurrentHandlerUserNickname());
target.setImplementProjectId(source.getImplementProjectId());
@@ -1307,8 +1424,8 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
valueOf(after, ProductRequirementDO::getProposerId));
appendFieldChange(fieldChanges, "proposerNickname", valueOf(before, ProductRequirementDO::getProposerNickname),
valueOf(after, ProductRequirementDO::getProposerNickname));
appendFieldChange(fieldChanges, "workHours", valueOf(before, ProductRequirementDO::getWorkHours),
valueOf(after, ProductRequirementDO::getWorkHours));
appendFieldChange(fieldChanges, "expectedTime", valueOf(before, ProductRequirementDO::getExpectedTime),
valueOf(after, ProductRequirementDO::getExpectedTime));
appendFieldChange(fieldChanges, "currentHandlerUserId", valueOf(before, ProductRequirementDO::getCurrentHandlerUserId),
valueOf(after, ProductRequirementDO::getCurrentHandlerUserId));
appendFieldChange(fieldChanges, "currentHandlerUserNickname",
@@ -1358,10 +1475,10 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
}
/**
* dispatch动作且已选择实现项目时,自动将产品需求转化为项目需求
* dispatch动作且已选择关联项目时,自动将产品需求转化为项目需求
*
* @param productRequirement 产品需求
* @param implementProjectId 实现项目ID
* @param implementProjectId 关联项目ID
*/
@VisibleForTesting
void createProjectRequirementFromProduct(ProductRequirementDO productRequirement, Long implementProjectId) {
@@ -1374,7 +1491,7 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
return;
}
// 查询实现项目下的根模块parentId = 0
// 查询关联项目下的根模块parentId = 0
ProjectRequirementModuleDO rootModule = projectRequirementModuleMapper.selectByProjectIdAndParentId(implementProjectId, 0L);
if (rootModule == null) {
throw exception(ErrorCodeConstants.PROJECT_REQUIREMENT_MODULE_ROOT_NOT_EXISTS);
@@ -1397,9 +1514,9 @@ public class ProductRequirementServiceImpl implements ProductRequirementService
newRequirement.setPriority(productRequirement.getPriority());
newRequirement.setProposerId(productRequirement.getProposerId());
newRequirement.setProposerNickname(productRequirement.getProposerNickname());
newRequirement.setExpectedTime(productRequirement.getExpectedTime());
newRequirement.setCurrentHandlerUserId(productRequirement.getCurrentHandlerUserId());
newRequirement.setCurrentHandlerUserNickname(productRequirement.getCurrentHandlerUserNickname());
newRequirement.setWorkHours(productRequirement.getWorkHours());
newRequirement.setAttachments(productRequirement.getAttachments());
newRequirement.setCreator(productRequirement.getCreator());
newRequirement.setCreateTime(productRequirement.getCreateTime());

View File

@@ -1,6 +1,8 @@
package com.njcn.rdms.module.project.service.project;
import com.njcn.rdms.framework.common.pojo.PageResult;
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementAllowedTransitionBatchRespVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementBatchReqVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementCloseReqVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementLifecycleRespVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementModuleReqVO;
@@ -71,6 +73,11 @@ public interface ProjectRequirementService {
*/
List<ProjectRequirementStatusTransitionRespVO> getAllowedTransitions(Long requirementId, Long projectId);
/**
* 批量获取需求可执行动作列表
*/
List<ProjectRequirementAllowedTransitionBatchRespVO> getAllowedTransitionsBatch(ProjectRequirementBatchReqVO reqVO);
/**
* 获取需求生命周期信息
*/

View File

@@ -7,6 +7,8 @@ import com.njcn.rdms.framework.common.util.object.BeanUtils;
import com.njcn.rdms.framework.mybatis.core.query.LambdaQueryWrapperX;
import com.njcn.rdms.framework.security.core.util.SecurityFrameworkUtils;
import com.njcn.rdms.module.project.constant.ProjectObjectConstants;
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementAllowedTransitionBatchRespVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementBatchReqVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementCloseReqVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementLifecycleRespVO;
import com.njcn.rdms.module.project.controller.admin.project.vo.requirement.ProjectRequirementModuleReqVO;
@@ -44,15 +46,7 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.*;
import java.util.function.Function;
import java.util.stream.Collectors;
@@ -152,10 +146,10 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService
requirement.setStatusCode(initialStatus);
requirement.setProposerId(createReqVO.getProposerId());
requirement.setProposerNickname(normalizeNullableText(createReqVO.getProposerNickname()));
requirement.setWorkHours(createReqVO.getWorkHours());
requirement.setExpectedTime(createReqVO.getExpectedTime());
requirement.setCurrentHandlerUserId(createReqVO.getCurrentHandlerUserId());
requirement.setCurrentHandlerUserNickname(normalizeNullableText(createReqVO.getCurrentHandlerUserNickname()));
requirement.setSort(createReqVO.getSort() != null ? createReqVO.getSort() : 0);
requirement.setSort(createReqVO.getSort());
requirement.setAttachments(createReqVO.getAttachments());
requirementMapper.insert(requirement);
@@ -188,10 +182,10 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService
requirement.setPriority(updateReqVO.getPriority());
requirement.setProposerId(updateReqVO.getProposerId());
requirement.setProposerNickname(normalizeNullableText(updateReqVO.getProposerNickname()));
requirement.setWorkHours(updateReqVO.getWorkHours());
requirement.setExpectedTime(updateReqVO.getExpectedTime());
requirement.setCurrentHandlerUserId(updateReqVO.getCurrentHandlerUserId());
requirement.setCurrentHandlerUserNickname(normalizeNullableText(updateReqVO.getCurrentHandlerUserNickname()));
requirement.setSort(updateReqVO.getSort() != null ? updateReqVO.getSort() : 0);
requirement.setSort(updateReqVO.getSort());
requirement.setAttachments(updateReqVO.getAttachments());
requirementMapper.updateById(requirement);
@@ -259,8 +253,7 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService
List<ProjectRequirementDO> rootRequirements = requirementMapper.selectBatchIds(rootIds);
rootRequirements.sort((a, b) -> {
int sortCompare = Integer.compare(a.getSort() != null ? a.getSort() : 0,
b.getSort() != null ? b.getSort() : 0);
int sortCompare = Comparator.nullsLast(Integer::compareTo).compare(a.getSort(), b.getSort());
if (sortCompare != 0) {
return sortCompare;
}
@@ -303,6 +296,10 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService
if (ACTION_ACCEPT.equals(actionCode) || ACTION_CLOSE.equals(actionCode)) {
validateAllChildrenAllowCloseOrAccept(reqVO.getId());
}
// 产品需求流转生成的项目需求不允许在项目侧取消,避免与产品侧主状态冲突。
if (ACTION_CANCEL.equals(actionCode) && isFromProductRequirement(requirement)) {
throw exception(ErrorCodeConstants.PROJECT_REQUIREMENT_SYNCED_FROM_PRODUCT_NOT_ALLOW_CANCEL);
}
if (ACTION_CANCEL.equals(actionCode) && hasChildren(requirement.getId())) {
validateParentCancelAllowed(reqVO.getId());
}
@@ -379,10 +376,10 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService
childRequirement.setStatusCode(initialStatus);
childRequirement.setProposerId(reqVO.getProposerId());
childRequirement.setProposerNickname(normalizeNullableText(reqVO.getProposerNickname()));
childRequirement.setWorkHours(reqVO.getWorkHours());
childRequirement.setExpectedTime(reqVO.getExpectedTime());
childRequirement.setCurrentHandlerUserId(reqVO.getCurrentHandlerUserId());
childRequirement.setCurrentHandlerUserNickname(normalizeNullableText(reqVO.getCurrentHandlerUserNickname()));
childRequirement.setSort(reqVO.getSort() != null ? reqVO.getSort() : 0);
childRequirement.setSort(reqVO.getSort());
childRequirement.setAttachments(reqVO.getAttachments());
requirementMapper.insert(childRequirement);
@@ -451,6 +448,23 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService
}).collect(Collectors.toList());
}
@Override
@CheckObjectPermission(objectType = PROJECT_OBJECT_TYPE, objectId = "#reqVO.projectId",
permission = PROJECT_QUERY_PERMISSION)
public List<ProjectRequirementAllowedTransitionBatchRespVO> getAllowedTransitionsBatch(ProjectRequirementBatchReqVO reqVO) {
List<ProjectRequirementDO> requirements = getBatchRequirements(reqVO.getRequirementIds(), reqVO.getProjectId());
Map<String, List<ObjectStatusTransitionDO>> transitionsByStatus = getTransitionsByStatus(requirements);
Map<String, ObjectStatusModelDO> statusModelMap = getStatusModelMap();
return requirements.stream().map(requirement -> {
ProjectRequirementAllowedTransitionBatchRespVO respVO = new ProjectRequirementAllowedTransitionBatchRespVO();
respVO.setRequirementId(requirement.getId());
respVO.setTransitions(buildAllowedTransitions(requirement,
transitionsByStatus.getOrDefault(requirement.getStatusCode(), Collections.emptyList()), statusModelMap));
return respVO;
}).collect(Collectors.toList());
}
@Override
@CheckObjectPermission(objectType = PROJECT_OBJECT_TYPE, objectId = "#projectId",
permission = PROJECT_QUERY_PERMISSION)
@@ -654,6 +668,60 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService
(left, right) -> left, LinkedHashMap::new));
}
private List<ProjectRequirementDO> getBatchRequirements(List<Long> requirementIds, Long projectId) {
if (requirementIds == null || requirementIds.isEmpty()) {
return Collections.emptyList();
}
if (requirementIds.stream().anyMatch(Objects::isNull)) {
throw invalidParamException("需求编号不能为空");
}
List<Long> distinctIds = requirementIds.stream().distinct().collect(Collectors.toList());
Map<Long, ProjectRequirementDO> requirementMap = requirementMapper.selectByIds(distinctIds).stream()
.collect(Collectors.toMap(ProjectRequirementDO::getId, Function.identity()));
if (requirementMap.size() != distinctIds.size()) {
throw exception(ErrorCodeConstants.PROJECT_REQUIREMENT_NOT_EXISTS);
}
List<ProjectRequirementDO> requirements = distinctIds.stream()
.map(requirementMap::get)
.collect(Collectors.toList());
for (ProjectRequirementDO requirement : requirements) {
validateRequirementBelongsToProject(requirement, projectId);
}
return requirements;
}
private Map<String, List<ObjectStatusTransitionDO>> getTransitionsByStatus(List<ProjectRequirementDO> requirements) {
List<String> statusCodes = requirements.stream()
.map(ProjectRequirementDO::getStatusCode)
.filter(Objects::nonNull)
.distinct()
.collect(Collectors.toList());
if (statusCodes.isEmpty()) {
return Collections.emptyMap();
}
return statusTransitionMapper.selectListByObjectTypeAndFromStatuses(REQUIREMENT_OBJECT_TYPE, statusCodes)
.stream()
.collect(Collectors.groupingBy(ObjectStatusTransitionDO::getFromStatusCode));
}
private List<ProjectRequirementStatusTransitionRespVO> buildAllowedTransitions(
ProjectRequirementDO requirement,
List<ObjectStatusTransitionDO> transitions,
Map<String, ObjectStatusModelDO> statusModelMap) {
return transitions.stream()
.filter(transition -> shouldExposeTransition(requirement, transition))
.map(transition -> {
ProjectRequirementStatusTransitionRespVO vo = new ProjectRequirementStatusTransitionRespVO();
vo.setActionCode(transition.getActionCode());
vo.setActionName(transition.getActionName());
vo.setToStatusCode(transition.getToStatusCode());
ObjectStatusModelDO statusModel = statusModelMap.get(transition.getToStatusCode());
vo.setToStatusName(statusModel != null ? statusModel.getStatusName() : transition.getToStatusCode());
vo.setNeedReason(transition.getNeedReason());
return vo;
}).collect(Collectors.toList());
}
/**
* 判断指定模块是否为“全部需求”根模块。
*/
@@ -742,12 +810,23 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService
if (!ACTION_CANCEL.equals(transition.getActionCode())) {
return true;
}
if (isFromProductRequirement(requirement)) {
return false;
}
if (!hasChildren(requirement.getId())) {
return true;
}
return isParentCancelAllowed(requirement.getId());
}
/**
* 判断当前项目需求是否由产品需求流转生成。
*/
private boolean isFromProductRequirement(ProjectRequirementDO requirement) {
return Objects.equals(requirement.getSourceType(), SOURCE_TYPE_PRODUCT_REQUIREMENT)
&& requirement.getProductRequirementId() != null;
}
/**
* 父需求存在子需求时,只有全部子需求都已取消或已拒绝,才允许展示取消动作。
*/
@@ -1119,7 +1198,7 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService
target.setLastStatusReason(source.getLastStatusReason());
target.setProposerId(source.getProposerId());
target.setProposerNickname(source.getProposerNickname());
target.setWorkHours(source.getWorkHours());
target.setExpectedTime(source.getExpectedTime());
target.setCurrentHandlerUserId(source.getCurrentHandlerUserId());
target.setCurrentHandlerUserNickname(source.getCurrentHandlerUserNickname());
target.setSort(source.getSort());
@@ -1162,8 +1241,8 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService
valueOf(after, ProjectRequirementDO::getProposerId));
appendFieldChange(fieldChanges, "proposerNickname", valueOf(before, ProjectRequirementDO::getProposerNickname),
valueOf(after, ProjectRequirementDO::getProposerNickname));
appendFieldChange(fieldChanges, "workHours", valueOf(before, ProjectRequirementDO::getWorkHours),
valueOf(after, ProjectRequirementDO::getWorkHours));
appendFieldChange(fieldChanges, "expectedTime", valueOf(before, ProjectRequirementDO::getExpectedTime),
valueOf(after, ProjectRequirementDO::getExpectedTime));
appendFieldChange(fieldChanges, "currentHandlerUserId",
valueOf(before, ProjectRequirementDO::getCurrentHandlerUserId),
valueOf(after, ProjectRequirementDO::getCurrentHandlerUserId));
@@ -1229,7 +1308,7 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService
target.setLastStatusReason(source.getLastStatusReason());
target.setProposerId(source.getProposerId());
target.setProposerNickname(source.getProposerNickname());
target.setWorkHours(source.getWorkHours());
target.setExpectedTime(source.getExpectedTime());
target.setCurrentHandlerUserId(source.getCurrentHandlerUserId());
target.setCurrentHandlerUserNickname(source.getCurrentHandlerUserNickname());
target.setImplementProjectId(source.getImplementProjectId());
@@ -1268,8 +1347,8 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService
valueOf(after, ProductRequirementDO::getProposerId));
appendFieldChange(fieldChanges, "proposerNickname", valueOf(before, ProductRequirementDO::getProposerNickname),
valueOf(after, ProductRequirementDO::getProposerNickname));
appendFieldChange(fieldChanges, "workHours", valueOf(before, ProductRequirementDO::getWorkHours),
valueOf(after, ProductRequirementDO::getWorkHours));
appendFieldChange(fieldChanges, "expectedTime", valueOf(before, ProductRequirementDO::getExpectedTime),
valueOf(after, ProductRequirementDO::getExpectedTime));
appendFieldChange(fieldChanges, "currentHandlerUserId", valueOf(before, ProductRequirementDO::getCurrentHandlerUserId),
valueOf(after, ProductRequirementDO::getCurrentHandlerUserId));
appendFieldChange(fieldChanges, "currentHandlerUserNickname",
@@ -1313,4 +1392,4 @@ public class ProjectRequirementServiceImpl implements ProjectRequirementService
return StringUtils.hasText(value) ? value : "";
}
}
}

View File

@@ -221,6 +221,9 @@ class ProjectServiceImpl implements ProjectService {
// 8) 项目创建审计
writeBizAuditLog(project, ObjectActivityConstants.PROJECT_ACTION_CREATE, null, initialStatus,
buildProjectFieldChanges(null, project), null);
// 9) 初始化项目需求的根模块
initDefaultRequirementModule(project);
return project.getId();
}

View File

@@ -14,6 +14,9 @@ public class AdminUserRespDTO implements VO {
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "小王")
private String nickname;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer sort;
@Schema(description = "所属公司", example = "灿能")
private String company;

View File

@@ -43,6 +43,10 @@ public class DictDataRespVO {
@Schema(description = "颜色类型,default、primary、success、info、warning、danger", example = "default")
private String colorType;
@Schema(description = "标识", example = "system")
@ExcelProperty("标识")
private String sign;
@Schema(description = "css 样式", example = "btn-visible")
private String cssClass;

View File

@@ -42,6 +42,10 @@ public class DictDataSaveReqVO {
@Schema(description = "颜色类型,default、primary、success、info、warning、danger", example = "default")
private String colorType;
@Schema(description = "标识", example = "system")
@Size(max = 255, message = "标识长度不能超过255个字符")
private String sign;
@Schema(description = "css 样式", example = "btn-visible")
private String cssClass;

View File

@@ -22,6 +22,9 @@ public class DictDataSimpleRespVO {
@Schema(description = "颜色类型default、primary、success、info、warning、danger", example = "default")
private String colorType;
@Schema(description = "标识", example = "system")
private String sign;
@Schema(description = "css 样式", example = "btn-visible")
private String cssClass;

View File

@@ -184,9 +184,9 @@ public class UserController {
public void importTemplate(HttpServletResponse response) throws IOException {
// 手动创建导出 demo
List<UserImportExcelVO> list = Arrays.asList(
UserImportExcelVO.builder().username("yunai").deptId(1L).positionId(1L).email("yunai@iocoder.cn").mobile("15601691300")
UserImportExcelVO.builder().username("yunai").deptId(1L).positionId(1L).sort(10).email("yunai@iocoder.cn").mobile("15601691300")
.nickname("灿能").status(CommonStatusEnum.ENABLE.getStatus()).sex(SexEnum.MALE.getSex()).build(),
UserImportExcelVO.builder().username("yuanma").deptId(2L).positionId(2L).email("yuanma@iocoder.cn").mobile("15601701300")
UserImportExcelVO.builder().username("yuanma").deptId(2L).positionId(2L).sort(20).email("yuanma@iocoder.cn").mobile("15601701300")
.nickname("源码").status(CommonStatusEnum.DISABLE.getStatus()).sex(SexEnum.FEMALE.getSex()).build()
);
// 输出

View File

@@ -33,6 +33,9 @@ public class UserImportExcelVO {
@ExcelProperty("主岗位编号")
private Long positionId;
@ExcelProperty("显示顺序")
private Integer sort;
@ExcelProperty("用户邮箱")
private String email;

View File

@@ -48,6 +48,10 @@ public class UserRespVO {
@ExcelProperty("主岗位")
private String positionName;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@ExcelProperty("显示顺序")
private Integer sort;
@Schema(description = "用户邮箱", example = "rdms@iocoder.cn")
@ExcelProperty("用户邮箱")
private String email;

View File

@@ -58,6 +58,11 @@ public class UserSaveReqVO {
@DiffLogField(name = "主岗位", function = PostParseFunction.NAME)
private Long positionId;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
@NotNull(message = "显示顺序不能为空")
@DiffLogField(name = "显示顺序")
private Integer sort;
@Schema(description = "离职时间", example = "2026-03-19 00:00:00")
@DiffLogField(name = "离职时间")
private LocalDateTime resignedAt;

View File

@@ -17,8 +17,12 @@ public class UserSimpleRespVO {
@Schema(description = "用户昵称", requiredMode = Schema.RequiredMode.REQUIRED, example = "灿能")
private String nickname;
@Schema(description = "显示顺序", requiredMode = Schema.RequiredMode.REQUIRED, example = "10")
private Integer sort;
@Schema(description = "部门ID", example = "我是一个用户")
private Long deptId;
@Schema(description = "部门名称", example = "IT 部")
private String deptName;

View File

@@ -23,4 +23,7 @@ public class AppDictDataRespVO {
@Schema(description = "字典类型", requiredMode = Schema.RequiredMode.REQUIRED, example = "sys_common_sex")
private String dictType;
@Schema(description = "标识", example = "system")
private String sign;
}

View File

@@ -52,6 +52,11 @@ public class DictDataDO extends BaseDO {
* 对应到 element-ui 为 default、primary、success、info、warning、danger
*/
private String colorType;
/**
* 标识
*/
@TableField(updateStrategy = FieldStrategy.ALWAYS)
private String sign;
/**
* css 样式
*/

View File

@@ -52,6 +52,11 @@ public class AdminUserDO extends BaseDO {
*/
private String nickname;
/**
* 显示排序
*/
private Integer sort;
/**
* 备注
*/

View File

@@ -35,23 +35,36 @@ public interface AdminUserMapper extends BaseMapperX<AdminUserDO> {
.betweenIfPresent(AdminUserDO::getCreateTime, reqVO.getCreateTime())
.inIfPresent(AdminUserDO::getDeptId, deptIds)
.inIfPresent(AdminUserDO::getId, userIds)
.orderByAsc(AdminUserDO::getSort)
.orderByDesc(AdminUserDO::getId));
}
default List<AdminUserDO> selectListByNickname(String nickname) {
return selectList(new LambdaQueryWrapperX<AdminUserDO>().like(AdminUserDO::getNickname, nickname));
return selectList(new LambdaQueryWrapperX<AdminUserDO>()
.like(AdminUserDO::getNickname, nickname)
.orderByAsc(AdminUserDO::getSort)
.orderByDesc(AdminUserDO::getId));
}
default List<AdminUserDO> selectListByStatus(Integer status) {
return selectList(AdminUserDO::getStatus, status);
return selectList(new LambdaQueryWrapperX<AdminUserDO>()
.eq(AdminUserDO::getStatus, status)
.orderByAsc(AdminUserDO::getSort)
.orderByDesc(AdminUserDO::getId));
}
default List<AdminUserDO> selectListByDeptIds(Collection<Long> deptIds) {
return selectList(AdminUserDO::getDeptId, deptIds);
return selectList(new LambdaQueryWrapperX<AdminUserDO>()
.in(AdminUserDO::getDeptId, deptIds)
.orderByAsc(AdminUserDO::getSort)
.orderByDesc(AdminUserDO::getId));
}
default List<AdminUserDO> selectListByPositionIds(Collection<Long> positionIds) {
return selectList(AdminUserDO::getPositionId, positionIds);
return selectList(new LambdaQueryWrapperX<AdminUserDO>()
.in(AdminUserDO::getPositionId, positionIds)
.orderByAsc(AdminUserDO::getSort)
.orderByDesc(AdminUserDO::getId));
}
}

View File

@@ -58,6 +58,7 @@ public class AdminUserServiceImpl implements AdminUserService {
static final String USER_INIT_PASSWORD_KEY = "system.user.init-password";
static final String USER_REGISTER_ENABLED_KEY = "system.user.register-enabled";
private static final Integer DEFAULT_REGISTER_USER_SORT = 999;
@Resource
private AdminUserMapper userMapper;
@@ -107,6 +108,7 @@ public class AdminUserServiceImpl implements AdminUserService {
// 2. 插入用户
AdminUserDO user = BeanUtils.toBean(registerReqVO, AdminUserDO.class);
user.setStatus(CommonStatusEnum.ENABLE.getStatus()); // 默认开启
user.setSort(DEFAULT_REGISTER_USER_SORT);
user.setPassword(encodePassword(registerReqVO.getPassword())); // 加密密码
userMapper.insert(user);
return user.getId();