feat(项目管理产品需求重构): 完善产品中的需求规格功能开发

需求规格纳入计划接口开发:
1. 限制仅已评审状态的需求规格才能纳入计划
2. 将需求规格文件上传至forge
3. 在对应项目中创建计划
4. 更新需规状态,记录计划id
5. 记录需规纳入计划操作

Signed-off-by: OTTO <731554297@qq.com>
This commit is contained in:
OTTO 2024-12-03 11:02:34 +08:00
parent f50d4ef1a8
commit f12fac99e7
13 changed files with 164 additions and 8 deletions

View File

@ -107,4 +107,14 @@ public interface RemoteFileService {
@RequestParam("type") String type,
@RequestParam("hierarchy") String hierarchy,
@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
/**
* 上传本地文件到Forge
*
* @param fileIdentifiers 文件标识列表逗号分隔
* @return Forge文件标识列表
*/
@PostMapping(value = "/common/uploadFileToForge/{fileIdentifiers}")
R<List<String>> uploadFileToForge(@PathVariable("fileIdentifiers") String fileIdentifiers,
@RequestHeader(SecurityConstants.FROM_SOURCE) String source);
}

View File

@ -70,6 +70,11 @@ public class RemoteFileFallbackFactory implements FallbackFactory<RemoteFileServ
public R<String> packagedFile(HashMap<String, String> packagedStructure, String zipFileName, String type, String hierarchy, String source) {
return R.fail("打包文件失败:" + throwable.getMessage());
}
@Override
public R<List<String>> uploadFileToForge(String fileIdentifiers, String source) {
return R.fail("上传本地文件到Forge失败:" + throwable.getMessage());
}
};
}
}

View File

@ -210,4 +210,17 @@ public class CommonController extends BaseController {
sysFileInfo.setFilePath("");
return R.ok(sysFileInfo);
}
/**
* 上传本地文件到Forge
*
* @param fileIdentifiers 文件标识列表逗号分隔
* @return Forge文件标识列表
*/
@InnerAuth
@PostMapping(value = "/uploadFileToForge/{fileIdentifiers}")
@ApiOperation(value = "上传本地文件到Forge", hidden = true)
R<List<String>> uploadFileToForge(@PathVariable("fileIdentifiers") String fileIdentifiers) {
return R.ok(sysFileInfoService.uploadFileToForge(fileIdentifiers));
}
}

View File

@ -171,4 +171,6 @@ public interface ISysFileInfoService {
* @return 压缩包文件标识
*/
String packagedFile(HashMap<String, String> packagedStructure, String zipFileName, String type, String hierarchy);
List<String> uploadFileToForge(String fileIdentifiers);
}

View File

@ -496,6 +496,30 @@ public class SysFileInfoServiceImpl implements ISysFileInfoService {
return fileIdentifier;
}
@Override
public List<String> uploadFileToForge(String fileIdentifiers) {
List<SysFileInfo> fileInfoList = getFileListByIdentifier(fileIdentifiers);
try {
List<String> forgeFileIdentifierList = new ArrayList<>();
for (SysFileInfo fileInfo : fileInfoList) {
String filePath = localFilePath + fileInfo.getFilePath();
File file = new File(filePath);
if (!file.exists()) {
throw new ServiceException("该文件不存在");
}
// 拷贝文件到打包目录
FileInputStream fileInputStream = new FileInputStream(file);
MultipartFile multipartFile = FileUtils.createMultipartFile(fileInputStream, fileInfo.getFileOriginName());
SysFileInfo sysFileInfo = upload(multipartFile, "pms-gitlink", null);
forgeFileIdentifierList.add(sysFileInfo.getFileIdentifier());
}
return forgeFileIdentifierList;
} catch (IOException e) {
logger.error("上传本地文件到Forge失败{}", e.getMessage());
throw new ServiceException("上传本地文件到Forge失败");
}
}
private void fileDownload(SysFileInfo sysFileInfo, HttpServletResponse response) {
try {
String fileName = sysFileInfo.getFileOriginName();

View File

@ -82,7 +82,7 @@ public class ProductReqSpecsController extends BaseController {
*/
@RequiresPermissions("pms:pmsProductRequirement:edit")
@Log(title = "需求规格", businessType = BusinessType.UPDATE)
@PutMapping
@PutMapping("/change")
@ApiOperation(value = "变更需求规格")
public AjaxResult change(@RequestBody @Validated ProductReqSpecsChangeVo productReqSpecsChangeVo) {
return toAjax(pmsProductRequirementService.changeReqSpecs(productReqSpecsChangeVo));
@ -109,4 +109,15 @@ public class ProductReqSpecsController extends BaseController {
public AjaxResult review(@RequestBody @Validated ProductReqReviewVo productReqReviewVo) {
return toAjax(pmsProductRequirementService.reviewReqSpecs(productReqReviewVo));
}
/**
* 需求规格纳入计划
*/
@RequiresPermissions("pms:pmsProductRequirement:edit")
@Log(title = "需求规格", businessType = BusinessType.UPDATE)
@PutMapping("/activePlan")
@ApiOperation(value = "需求规格纳入计划")
public AjaxResult activePlan(@RequestBody @Validated ProductReqSpecsActivePlanVo productReqSpecsActivePlanVo) {
return toAjax(pmsProductRequirementService.activePlan(productReqSpecsActivePlanVo));
}
}

View File

@ -11,6 +11,7 @@ import lombok.Getter;
public enum ProductReqOperationType {
CONTENT_MODIFY("contentModify", "内容修改"),
REVIEW_USER_REQ("reviewUserReq", "用户需求评审"),
ACTIVE_PLAN("activePlan", "纳入计划"),
REVIEW_REQ_SPECS("reviewUserReq", "需求规格评审");
private final String key;
private final String name;

View File

@ -16,7 +16,7 @@ import java.util.Date;
* @date 2023-10-10
*/
@Data
@ApiModel("用户需求评审对象")
@ApiModel("需求评审对象")
public class ProductReqReviewVo {
@ApiModelProperty(value = "需求Id", required = true)

View File

@ -0,0 +1,52 @@
package com.microservices.pms.product.domain.vo;
import com.microservices.pms.product.domain.PmsProductReqOperationRecord;
import com.microservices.pms.product.domain.PmsProductRequirement;
import com.microservices.pms.product.domain.enums.ProductReqOperationType;
import com.microservices.pms.project.domain.vo.PmsProjectIssuesInputVo;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import javax.validation.constraints.NotNull;
import java.util.List;
/**
* 产品需求对象 pms_product_requirement
*
* @author otto
* @date 2023-10-10
*/
@Data
@ApiModel("需求规格纳入计划对象")
public class ProductReqSpecsActivePlanVo {
@ApiModelProperty(value = "需求Id", required = true)
@NotNull
private Long id;
@ApiModelProperty(value = "项目Id", required = true)
@NotNull
private Long projectId;
public PmsProjectIssuesInputVo toPmsProjectIssuesInputVo(PmsProductRequirement reqSpecs, List<String> forgeFileIdentifierList) {
PmsProjectIssuesInputVo issue = new PmsProjectIssuesInputVo();
issue.setSubject(reqSpecs.getTitle());
issue.setPmProjectId(projectId);
issue.setStatusId(1);
issue.setPmIssueType(1);
issue.setPriorityId(2);
issue.setDescription(reqSpecs.getContent());
issue.setAttachmentIds(forgeFileIdentifierList);
return issue;
}
public PmsProductReqOperationRecord toPmsProductReqOperationRecord(PmsProductRequirement pmsProductRequirement) {
PmsProductReqOperationRecord record = new PmsProductReqOperationRecord();
record.setProductRequirementId(pmsProductRequirement.getId());
record.setType(ProductReqOperationType.ACTIVE_PLAN.getKey());
record.setOperation("激活");
record.setOperator(pmsProductRequirement.getCreateBy());
record.setOperatingTime(pmsProductRequirement.getCreateTime());
return record;
}
}

View File

@ -47,11 +47,13 @@ public class ProductReqSpecsChangeVo {
}
public boolean isReqChange(PmsProductRequirement oldPmsProductRequirement) {
if (StringUtils.isNotEmpty(this.getContent()) || StringUtils.isNotEmpty(this.getFileIdentifiers()) || StringUtils.isNotEmpty(this.getTitle())) {
return !oldPmsProductRequirement.getTitle().equals(this.title)
|| !oldPmsProductRequirement.getContent().equals(this.content)
|| !oldPmsProductRequirement.getFileIdentifiers().equals(this.fileIdentifiers);
if (StringUtils.isNotEmpty(this.getContent()) && !this.content.equals(oldPmsProductRequirement.getContent())) {
return true;
}
return false;
if (StringUtils.isNotEmpty(this.getTitle()) && !this.title.equals(oldPmsProductRequirement.getTitle())) {
return true;
}
return StringUtils.isNotEmpty(this.getFileIdentifiers()) && !this.getFileIdentifiers().equals(oldPmsProductRequirement.getFileIdentifiers());
}
}

View File

@ -162,4 +162,6 @@ public interface IPmsProductRequirementService
int reviewReqSpecs(ProductReqReviewVo productReqReviewVo);
int changeReqSpecs(ProductReqSpecsChangeVo productReqSpecsChangeVo);
int activePlan(ProductReqSpecsActivePlanVo productReqSpecsActivePlanVo);
}

View File

@ -1,6 +1,7 @@
package com.microservices.pms.product.service.impl;
import com.alibaba.fastjson2.JSONObject;
import com.microservices.common.core.constant.SecurityConstants;
import com.microservices.common.core.exception.NotFoundException;
import com.microservices.common.core.exception.ServiceException;
import com.microservices.common.core.utils.*;
@ -16,11 +17,14 @@ import com.microservices.pms.product.domain.vo.*;
import com.microservices.pms.product.mapper.PmsProductRequirementMapper;
import com.microservices.pms.product.service.*;
import com.microservices.pms.project.domain.PmsProject;
import com.microservices.pms.project.domain.vo.PmsProjectIssuesInputVo;
import com.microservices.pms.project.service.IPmsProjectService;
import com.microservices.pms.project.service.PmsProjectIssuesService;
import com.microservices.pms.utils.PmsConstants;
import com.microservices.pms.utils.PmsUtils;
import com.microservices.system.api.RemoteFileService;
import com.microservices.system.api.domain.SysUser;
import com.microservices.system.api.utils.FeignUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.stereotype.Service;
@ -65,6 +69,8 @@ public class PmsProductRequirementServiceImpl implements IPmsProductRequirementS
@Autowired
@Lazy
private IPmsJournalsService pmsJournalsService;
@Autowired
private RemoteFileService remoteFileService;
/**
* 查询产品需求
@ -560,6 +566,34 @@ public class PmsProductRequirementServiceImpl implements IPmsProductRequirementS
return success;
}
@Override
public int activePlan(ProductReqSpecsActivePlanVo productReqSpecsActivePlanVo) {
// 通过id检查需修改的产品需求是否存在
PmsProductRequirement reqSpecs = selectPmsProductRequirementById(productReqSpecsActivePlanVo.getId(), true);
if (!ProductReqStatus.REVIEWED_REQ_SPECS.getKey().equals(reqSpecs.getStatus())) {
throw new ServiceException("仅已评审状态的需求规格才能纳入计划");
}
PmsProject project = pmsProjectService.selectAndCheckPmsProjectById(productReqSpecsActivePlanVo.getProjectId());
List<String> forgeFileIdentifierList = null;
if (StringUtils.isNotEmpty(reqSpecs.getFileIdentifiers())) {
// 需求规格文件上传至forge
forgeFileIdentifierList = FeignUtils.getReturnData(
remoteFileService.uploadFileToForge(reqSpecs.getFileIdentifiers(), SecurityConstants.INNER)
);
}
PmsProjectIssuesInputVo pmsProjectIssuesInputVo = productReqSpecsActivePlanVo.toPmsProjectIssuesInputVo(reqSpecs, forgeFileIdentifierList);
JSONObject projectIssue = pmsProjectIssuesService.insertPmsProjectIssues(pmsProjectIssuesInputVo);
reqSpecs.setPmsProjectId(project.getId());
reqSpecs.setProjectReqId(projectIssue.getLong("id"));
reqSpecs.setStatus(ProductReqStatus.PLANNED_REQ_SPECS_CHANGING.getKey());
int success = updateRequirement(reqSpecs, reqSpecs.getPmsProductIdentifier());
pmsProductReqOperationRecordService.insertPmsProductReqOperationRecord(
productReqSpecsActivePlanVo.toPmsProductReqOperationRecord(reqSpecs)
);
return success;
}
private ProductReqSpecsDetailVo toProductReqSpecsDetailVo(PmsProductRequirement pmsProductRequirement) {
ProductReqSpecsDataVo productReqSpecsDataVo = toProductReqSpecsDataVo(pmsProductRequirement, null);
ProductReqSpecsDetailVo productReqSpecsDetailVo = productReqSpecsDataVo.toProductReqSpecsDetailVo();

View File

@ -271,7 +271,7 @@
requirement_assignee_gitlink_ids = #{requirementAssigneeGitlinkIds},
pms_project_id = #{pmsProjectId},
<if test="fromId != null and fromId != ''">from_id = #{fromId},</if>
<if test="projectReqId != null and projectReqId != ''">project_req_id = #{project_req_id},</if>
<if test="projectReqId != null and projectReqId != ''">project_req_id = #{projectReqId},</if>
<if test="type != null and type != ''">type = #{type},</if>
<if test="identifier != null and identifier != ''">identifier = #{identifier},</if>
<if test="sourceUser != null and sourceUser != ''">source_user = #{sourceUser},</if>