From 679aa5f0c1cb1c17e15f46afd71b3b71e832cf9b Mon Sep 17 00:00:00 2001 From: guoqibing Date: Tue, 23 Dec 2025 10:17:39 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BA=A7=E5=93=81=E8=A7=92=E8=89=B2=E7=AD=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pom.xml | 11 ++ .../zentao/controller/ZtStoryController.java | 44 +++++ .../controller/ZtStoryUserController.java | 16 ++ .../zentao/controller/ZtUserController.java | 8 +- .../com/sa/zentao/dao/PerformanceDTO.java | 22 +++ .../java/com/sa/zentao/dao/ZtProjectDTO.java | 3 + .../java/com/sa/zentao/dao/ZtStoryDTO.java | 31 +++- .../com/sa/zentao/dao/ZtStoryUserDTO.java | 16 +- .../java/com/sa/zentao/dao/ZtTaskDTO.java | 4 + .../java/com/sa/zentao/entity/ZtStory.java | 20 ++- .../com/sa/zentao/entity/ZtStoryUser.java | 4 + .../com/sa/zentao/enums/ActionStatus.java | 6 + .../com/sa/zentao/enums/StoryStageEnums.java | 2 + .../com/sa/zentao/mapper/ZtStoryMapper.java | 4 + src/main/java/com/sa/zentao/qo/StoryQo.java | 4 + .../java/com/sa/zentao/qo/ZtProjectQo.java | 6 +- .../sa/zentao/service/IZtStoryService.java | 13 +- .../zentao/service/IZtStoryUserService.java | 3 + .../zentao/service/impl/IZtCountService.java | 149 ++++++++++++++-- .../service/impl/ZtProjectServiceImpl.java | 7 +- .../service/impl/ZtReleaseServiceImpl.java | 6 +- .../service/impl/ZtStoryServiceImpl.java | 161 ++++++++++++++++-- .../service/impl/ZtStoryUserServiceImpl.java | 31 +++- .../service/impl/ZtTaskServiceImpl.java | 71 ++++---- .../com/sa/zentao/utils/KanBanConstant.java | 4 + src/main/resources/mapper/ZtStoryMapper.xml | 47 ++++- .../resources/mapper/ZtStoryUserMapper.xml | 12 +- .../templates/scope/产品经理考核.xlsx | Bin 12629 -> 12632 bytes 28 files changed, 620 insertions(+), 85 deletions(-) diff --git a/pom.xml b/pom.xml index f997e28..647e22c 100644 --- a/pom.xml +++ b/pom.xml @@ -18,6 +18,17 @@ 3.3.0 + + org.springframework.boot + spring-boot-starter-validation + + + + + cn.hutool + hutool-all + 4.6.3 + com.itextpdf diff --git a/src/main/java/com/sa/zentao/controller/ZtStoryController.java b/src/main/java/com/sa/zentao/controller/ZtStoryController.java index b5b24b3..3d7588d 100644 --- a/src/main/java/com/sa/zentao/controller/ZtStoryController.java +++ b/src/main/java/com/sa/zentao/controller/ZtStoryController.java @@ -8,11 +8,13 @@ import com.alibaba.excel.metadata.data.WriteCellData; import com.alibaba.excel.write.metadata.WriteSheet; import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.github.pagehelper.PageInfo; import com.sa.zentao.dao.*; import com.sa.zentao.entity.ZtProject; import com.sa.zentao.entity.ZtProjectproduct; import com.sa.zentao.entity.ZtStoryFeedback; +import com.sa.zentao.entity.ZtStoryUser; import com.sa.zentao.enums.StoryStageEnums; import com.sa.zentao.enums.StoryStatusEnums; import com.sa.zentao.enums.UserStoryEnums; @@ -26,6 +28,7 @@ import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.util.CollectionUtils; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -182,6 +185,27 @@ public class ZtStoryController { } + /** + * 测试提交验收 + * @param qo + * @return + */ + @RequestMapping(value = "/testSubmitVerified", method = RequestMethod.POST, produces = "application/json; charset=UTF-8") + public Result testSubmitVerified(@RequestBody ZtStoryDTO qo){ + ztStoryService.testSubmitVerified(qo); + return Result.success(); + } + /** + * 产品验收通过 + * @param qo + * @return + */ + @RequestMapping(value = "/storyProductUserYs", method = RequestMethod.POST, produces = "application/json; charset=UTF-8") + public Result storyProductUserYs(@RequestBody ZtStoryDTO qo){ + ztStoryService.storyProductUserYs(qo); + return Result.success(); + } + @RequestMapping(value = "/storyListByProject", method = RequestMethod.POST, produces = "application/json; charset=UTF-8") public Result storyListByProject(@RequestBody ZtProjectQo qo){ @@ -336,6 +360,26 @@ public class ZtStoryController { List list = ztStoryService.execListByProject(dto); return Result.success(list); } + /** + * 指派产品人 + * @param dto + * @return + */ + @RequestMapping(value = "/assignedToProductUser", method = RequestMethod.POST, produces = "application/json; charset=UTF-8") + public Result assignedToProductUser(@RequestBody @Validated ZtStoryDTO dto){ + this.ztStoryService.assignedToProductUser(dto); + return Result.success(); + } + /** + * 指派测试人 + * @param dto + * @return + */ + @RequestMapping(value = "/assignedToTestUser", method = RequestMethod.POST, produces = "application/json; charset=UTF-8") + public Result assignedToTestUser(@RequestBody @Validated ZtStoryDTO dto){ + this.ztStoryService.assignedToTestUser(dto); + return Result.success(); + } diff --git a/src/main/java/com/sa/zentao/controller/ZtStoryUserController.java b/src/main/java/com/sa/zentao/controller/ZtStoryUserController.java index 3b798b9..686b97a 100644 --- a/src/main/java/com/sa/zentao/controller/ZtStoryUserController.java +++ b/src/main/java/com/sa/zentao/controller/ZtStoryUserController.java @@ -7,10 +7,14 @@ import com.alibaba.excel.write.metadata.WriteSheet; import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.github.pagehelper.PageInfo; +import com.sa.zentao.conf.RiskUserThreadLocal; import com.sa.zentao.dao.*; import com.sa.zentao.entity.ZtStory; import com.sa.zentao.entity.ZtStoryUser; +import com.sa.zentao.enums.ActionStatus; +import com.sa.zentao.enums.ActionType; import com.sa.zentao.enums.UserStoryEnums; import com.sa.zentao.qo.StoryQo; import com.sa.zentao.qo.ZtProjectQo; @@ -19,6 +23,7 @@ import lombok.extern.slf4j.Slf4j; import org.apache.commons.lang3.StringUtils; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.util.CollectionUtils; +import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; import java.io.IOException; @@ -56,6 +61,17 @@ public class ZtStoryUserController { return Result.success(); } + /** + * 指派产品人 + * @param dto + * @return + */ + @RequestMapping(value = "/assignedToProductUser", method = RequestMethod.POST, produces = "application/json; charset=UTF-8") + public Result assignedToProductUser(@RequestBody @Validated ZtStoryUserDTO dto){ + this.storyUserService.assignedToProductUser(dto); + return Result.success(); + } + @RequestMapping(value = "/addStory", method = RequestMethod.POST, produces = "application/json; charset=UTF-8") public Result addStory(@RequestBody ZtStoryUserDTO dto){ diff --git a/src/main/java/com/sa/zentao/controller/ZtUserController.java b/src/main/java/com/sa/zentao/controller/ZtUserController.java index bdeaf2c..dfcc160 100644 --- a/src/main/java/com/sa/zentao/controller/ZtUserController.java +++ b/src/main/java/com/sa/zentao/controller/ZtUserController.java @@ -135,11 +135,11 @@ public class ZtUserController { } eq.in(ZtUser::getAccount,execution.stream().map(o->o.getAccount()).collect(Collectors.toList())); }else{ - this.userMapper.selectOne(new QueryWrapper().lambda().eq(ZtUser::getAccount,RiskUserThreadLocal.get().getName())); + this.userMapper.selectOne(new QueryWrapper().lambda() + .eq(ZtUser::getAccount,RiskUserThreadLocal.get().getName())); } - - return Result.success(userService.list(eq - )); + eq.eq(dto.getUserType()!=null,ZtUser::getUserType,dto.getUserType()); + return Result.success(userService.list(eq)); } @RequestMapping(value = "/storyReviewList", method = RequestMethod.POST, produces = "application/json; charset=UTF-8") diff --git a/src/main/java/com/sa/zentao/dao/PerformanceDTO.java b/src/main/java/com/sa/zentao/dao/PerformanceDTO.java index 55434c6..13411e5 100644 --- a/src/main/java/com/sa/zentao/dao/PerformanceDTO.java +++ b/src/main/java/com/sa/zentao/dao/PerformanceDTO.java @@ -160,4 +160,26 @@ public class PerformanceDTO implements Serializable { private BigDecimal professionalSkillEnhancementScore=BigDecimal.ZERO; //问题管理得分 private BigDecimal developFeedbackStory=BigDecimal.valueOf(10); + + /** + * 产品经理 + */ + // 产品缺陷率 + private BigDecimal productBugRate=BigDecimal.ZERO; + //项目准时率 需求发布准时率 研发需求/用户需求 + private BigDecimal productProjectOnTimeRateScore=BigDecimal.ZERO; + //主动性责任感 + private BigDecimal productResponsibilityScore=BigDecimal.valueOf(5); + //需求会议 + private BigDecimal productMeetScore=BigDecimal.valueOf(10); + //问题响应 + private BigDecimal productProblemResponse=BigDecimal.valueOf(15); + //需求准确性 + private BigDecimal accurateDemand=BigDecimal.valueOf(20);; + //产品规划 + private BigDecimal productPlanning=BigDecimal.valueOf(10);; + //本阶段计划发布数量 + private BigDecimal productPlanStoryNum; + //本阶段准时发布研发数量 + private BigDecimal productPlanStoryOnTimeNum; } diff --git a/src/main/java/com/sa/zentao/dao/ZtProjectDTO.java b/src/main/java/com/sa/zentao/dao/ZtProjectDTO.java index 0dc7b63..9b165a0 100644 --- a/src/main/java/com/sa/zentao/dao/ZtProjectDTO.java +++ b/src/main/java/com/sa/zentao/dao/ZtProjectDTO.java @@ -3,6 +3,7 @@ package com.sa.zentao.dao; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; +import com.sa.zentao.enums.UserType; import lombok.Data; import lombok.EqualsAndHashCode; @@ -205,6 +206,8 @@ public class ZtProjectDTO implements Serializable { private String deleted; + private UserType userType; + private List children; private List storyIds; diff --git a/src/main/java/com/sa/zentao/dao/ZtStoryDTO.java b/src/main/java/com/sa/zentao/dao/ZtStoryDTO.java index 1c7f84d..973ad71 100644 --- a/src/main/java/com/sa/zentao/dao/ZtStoryDTO.java +++ b/src/main/java/com/sa/zentao/dao/ZtStoryDTO.java @@ -8,6 +8,7 @@ import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; import com.sa.zentao.entity.ZtStoryCase; import com.sa.zentao.entity.ZtStoryreview; +import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; @@ -31,6 +32,7 @@ public class ZtStoryDTO implements Serializable { private static final long serialVersionUID = 1L; + @NotNull(message = "请录入ID") @ExcelProperty(value = "ID",index =0) private Integer id; @ExcelProperty(value = "研发需求名称",index =1) @@ -48,6 +50,8 @@ public class ZtStoryDTO implements Serializable { private Date planEndDate; @ExcelProperty(value = "阶段",index =6) private String stage; + @ExcelIgnore + private String stageName; @ExcelProperty(value = "指派给",index =7) private String assignedToName; @@ -152,8 +156,32 @@ public class ZtStoryDTO implements Serializable { private Integer frombug; @ExcelIgnore private Integer feedback; + /** + * 产品人 + */ + @ExcelIgnore + private String productUser; + /** + * 产品人 + */ + @ExcelIgnore + private String productUserName; + /** + * 产品人 + */ + @ExcelIgnore + private String testUser; + /** + * 产品人 + */ + @ExcelIgnore + private String testUserName; - + /** + * 1是否内部验收 0不需要 + */ + @ExcelIgnore + private Integer innerYsFlag; @ExcelIgnore private String keywords; @@ -217,7 +245,6 @@ public class ZtStoryDTO implements Serializable { private Date planYsDate; - @ExcelIgnore private Date activateddate; @ExcelIgnore diff --git a/src/main/java/com/sa/zentao/dao/ZtStoryUserDTO.java b/src/main/java/com/sa/zentao/dao/ZtStoryUserDTO.java index 2f0b99b..0ce81ab 100644 --- a/src/main/java/com/sa/zentao/dao/ZtStoryUserDTO.java +++ b/src/main/java/com/sa/zentao/dao/ZtStoryUserDTO.java @@ -6,8 +6,11 @@ import com.alibaba.excel.metadata.data.WriteCellData; import com.baomidou.mybatisplus.annotation.IdType; import com.baomidou.mybatisplus.annotation.TableField; import com.baomidou.mybatisplus.annotation.TableId; +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; import lombok.Data; import lombok.EqualsAndHashCode; +import lombok.NonNull; import java.io.Serializable; import java.time.LocalDate; @@ -31,6 +34,7 @@ public class ZtStoryUserDTO implements Serializable { // @TableId(value = "id", type = IdType.AUTO) // @ExcelProperty + @NotNull(message = "ID不能为空") @ExcelProperty(value = "ID",index =0) private Integer id; @@ -130,6 +134,10 @@ public class ZtStoryUserDTO implements Serializable { @ExcelIgnore private String color; @ExcelIgnore + private String productUserName; + @ExcelIgnore + private String productUserColor; + @ExcelIgnore private String stage; @ExcelIgnore @@ -147,7 +155,7 @@ public class ZtStoryUserDTO implements Serializable { private String openedby; - + @NotBlank(message = "指派??") @ExcelIgnore private String assignedto; @ExcelIgnore @@ -273,4 +281,10 @@ public class ZtStoryUserDTO implements Serializable { //1.需要 2.不需要 @ExcelIgnore private Integer needImprove; + + /** + * 产品用户 + */ + @ExcelIgnore + private String productUser; } diff --git a/src/main/java/com/sa/zentao/dao/ZtTaskDTO.java b/src/main/java/com/sa/zentao/dao/ZtTaskDTO.java index e2cb23d..8a29081 100644 --- a/src/main/java/com/sa/zentao/dao/ZtTaskDTO.java +++ b/src/main/java/com/sa/zentao/dao/ZtTaskDTO.java @@ -212,4 +212,8 @@ public class ZtTaskDTO implements Serializable { * 交付物 */ private String deliverContent; + /** + * 1提交 0不提交 + */ + private Integer innerYsFlag; } diff --git a/src/main/java/com/sa/zentao/entity/ZtStory.java b/src/main/java/com/sa/zentao/entity/ZtStory.java index 0455a44..effffcf 100644 --- a/src/main/java/com/sa/zentao/entity/ZtStory.java +++ b/src/main/java/com/sa/zentao/entity/ZtStory.java @@ -190,7 +190,12 @@ public class ZtStory implements Serializable { private Date developedDate; //结束日期 测试结束 private Date endDate; - + //内部验收时间 + @TableField(exist = false) + private Date innerYsTime; + //测试提交验收时间 + @TableField(exist = false) + private Date testSubmitYsTime; private Integer project; //1通过 2不通过 private Integer ysFlag; @@ -223,5 +228,16 @@ public class ZtStory implements Serializable { // private Date develDate; // //测试完成时间 // private Date testedDate; - + /** + * 内部验收标识 1需要内部验收 0不需要 + */ + private Integer innerYsFlag; + /** + * 产品人员 + */ + private String productUser; + /** + * 测试人员 + */ + private String testUser; } diff --git a/src/main/java/com/sa/zentao/entity/ZtStoryUser.java b/src/main/java/com/sa/zentao/entity/ZtStoryUser.java index e746de6..a5c83bf 100644 --- a/src/main/java/com/sa/zentao/entity/ZtStoryUser.java +++ b/src/main/java/com/sa/zentao/entity/ZtStoryUser.java @@ -225,4 +225,8 @@ public class ZtStoryUser implements Serializable { private String deliverRemark; private String oldStatus; + /** + * 产品用户 + */ + private String productUser; } diff --git a/src/main/java/com/sa/zentao/enums/ActionStatus.java b/src/main/java/com/sa/zentao/enums/ActionStatus.java index 701aa8a..c285fe9 100644 --- a/src/main/java/com/sa/zentao/enums/ActionStatus.java +++ b/src/main/java/com/sa/zentao/enums/ActionStatus.java @@ -62,6 +62,12 @@ public enum ActionStatus { XGMM(101, "xgmm","修改密码"), CZMM(102, "czmm","重置密码"), CZINFO(103, "czmm","重置"), + + //测试提交验收 + TESTSUBMITYS(200, "testSubmitYs","测试提交验收"), + PRODUCTYS(201, "productYs","产品验收"), + ASSIGNTOPRODUCTUSER(202, "assignProductUser","指派产品人"), + ASSIGNTOTESTUSER(203, "assignTestUser","指派测试人"), ; @EnumValue diff --git a/src/main/java/com/sa/zentao/enums/StoryStageEnums.java b/src/main/java/com/sa/zentao/enums/StoryStageEnums.java index 5268e39..b3b7a38 100644 --- a/src/main/java/com/sa/zentao/enums/StoryStageEnums.java +++ b/src/main/java/com/sa/zentao/enums/StoryStageEnums.java @@ -16,6 +16,8 @@ public enum StoryStageEnums { developed(10, "developed","研发完毕"), testing(5, "testing","测试中"), tested(6, "tested","测试完毕"), + productWaitVerified(9, "productWaitVerified","产品验收中"), + productVerified(10, "productVerified","产品已验收"), released(7, "released","已发布"), verified(8, "verified","已验收"), ; diff --git a/src/main/java/com/sa/zentao/mapper/ZtStoryMapper.java b/src/main/java/com/sa/zentao/mapper/ZtStoryMapper.java index 8b0ab39..347c065 100644 --- a/src/main/java/com/sa/zentao/mapper/ZtStoryMapper.java +++ b/src/main/java/com/sa/zentao/mapper/ZtStoryMapper.java @@ -10,6 +10,7 @@ import org.apache.ibatis.annotations.Param; import java.util.Date; import java.util.List; +import java.util.Map; /** *

@@ -59,4 +60,7 @@ public interface ZtStoryMapper extends BaseMapper { * @return */ List searchAll(@Param("keyword") String keyword,@Param("pIds")List pIds); + + Map storyReleaseOnTimeByProducts(@Param("pIds") List pids, + @Param("startTime") Date firstDayOfMonth,@Param("endTime") Date lastDayOfMonth); } diff --git a/src/main/java/com/sa/zentao/qo/StoryQo.java b/src/main/java/com/sa/zentao/qo/StoryQo.java index 497ddeb..22300c6 100644 --- a/src/main/java/com/sa/zentao/qo/StoryQo.java +++ b/src/main/java/com/sa/zentao/qo/StoryQo.java @@ -22,6 +22,10 @@ public class StoryQo extends BaseQo { private Integer productId; private String openedby; private String status; + /** + * 用户产品 + */ + private String productUser; private List productIds; private List statusList; diff --git a/src/main/java/com/sa/zentao/qo/ZtProjectQo.java b/src/main/java/com/sa/zentao/qo/ZtProjectQo.java index 1df72ac..40ce2d9 100644 --- a/src/main/java/com/sa/zentao/qo/ZtProjectQo.java +++ b/src/main/java/com/sa/zentao/qo/ZtProjectQo.java @@ -37,7 +37,11 @@ public class ZtProjectQo extends BaseQo { private String severity; private String productName; - + /** + * 产品人 + */ + private String productUser; + private String testUser; private Integer productId; private List productIds; diff --git a/src/main/java/com/sa/zentao/service/IZtStoryService.java b/src/main/java/com/sa/zentao/service/IZtStoryService.java index 010a171..ec9b0c9 100644 --- a/src/main/java/com/sa/zentao/service/IZtStoryService.java +++ b/src/main/java/com/sa/zentao/service/IZtStoryService.java @@ -57,7 +57,7 @@ public interface IZtStoryService extends IService { void waitStory(Integer story); void startStory(Integer story); //开发完成 - void finishStory(Integer story,String finishBy); + void develFinishStory(Integer story,String finishBy); //测试完毕 void testedStory(Integer story,String finishBy); //测试中 @@ -105,4 +105,15 @@ public interface IZtStoryService extends IService { List getStoryListByDatePidsProject(List pids, Integer id, Date firstDayOfMonth, Date lastDayOfMonth); PageInfo searchByName(SearchQo qo); + + void testSubmitVerified(ZtStoryDTO qo); + + void storyProductUserYs(ZtStoryDTO qo); + + Map storyReleaseOnTimeByProducts(@Param("pids") List pids, @Param("startTime")Date firstDayOfMonth, + @Param("endTime")Date lastDayOfMonth); + + void assignedToProductUser(ZtStoryDTO dto); + + void assignedToTestUser(ZtStoryDTO dto); } diff --git a/src/main/java/com/sa/zentao/service/IZtStoryUserService.java b/src/main/java/com/sa/zentao/service/IZtStoryUserService.java index 8974572..2c7a6e3 100644 --- a/src/main/java/com/sa/zentao/service/IZtStoryUserService.java +++ b/src/main/java/com/sa/zentao/service/IZtStoryUserService.java @@ -54,4 +54,7 @@ public interface IZtStoryUserService extends IService { void confirmStory(ZtStoryUserDTO dto); void needMeetOrDesign(ZtStoryUserDTO dto); + + void assignedToProductUser(ZtStoryUserDTO dto); + } diff --git a/src/main/java/com/sa/zentao/service/impl/IZtCountService.java b/src/main/java/com/sa/zentao/service/impl/IZtCountService.java index 0609081..b1dad68 100644 --- a/src/main/java/com/sa/zentao/service/impl/IZtCountService.java +++ b/src/main/java/com/sa/zentao/service/impl/IZtCountService.java @@ -1,5 +1,6 @@ package com.sa.zentao.service.impl; +import cn.hutool.core.util.ObjectUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.ExcelWriter; import com.alibaba.excel.annotation.ExcelIgnore; @@ -242,7 +243,10 @@ public class IZtCountService { .map(o -> o.getId() + "").collect(Collectors.joining(","))); result.setTestingCount(ztStory.stream().filter(o -> "active".equals(o.getStatus())) - .filter(o -> Arrays.asList("testing", "tested").contains(o.getStage())).map(o -> o.getId() + "").collect(Collectors.joining(","))); + .filter(o -> Arrays.asList("testing", "tested" + ,StoryStageEnums.productWaitVerified.getValue() + ,StoryStageEnums.productVerified.getValue() + ).contains(o.getStage())).map(o -> o.getId() + "").collect(Collectors.joining(","))); // developing 研发中 developed 研发完毕 testing 测试中 tested测试完毕 result.setJxzCount(ztStory.stream().filter(o -> "active".equals(o.getStatus())) @@ -350,7 +354,10 @@ public class IZtCountService { //0825 计划中和研发中合并 result.setJxzCount(ztStory.stream().filter(o -> "active".equals(o.getStatus())) - .filter(o -> Arrays.asList("wait","tested", "testing", "developed", "developing").contains(o.getStage()) + .filter(o -> Arrays.asList("wait","tested", "testing", "developed", "developing" + ,StoryStageEnums.productWaitVerified.getValue() + ,StoryStageEnums.productVerified.getValue()).contains(o.getStage() + ) ).map(o -> o.getId() + "").collect(Collectors.joining(","))); result.setCswbCount(ztStory.stream().filter(o -> "active".equals(o.getStatus())) @@ -380,7 +387,7 @@ public class IZtCountService { result = setFeedback(result, feedbacks); - } else if (userType == UserType.XMGLY || userType == UserType.XMZL) { + } else if (userType == UserType.XMGLY || userType == UserType.XMZL||userType == UserType.CPJL) { List pList = this.productService.listByIds(pIds); @@ -412,7 +419,9 @@ public class IZtCountService { .map(o -> o.getId() + "").collect(Collectors.joining(","))); result.setTestingCount(ztStory.stream().filter(o -> "active".equals(o.getStatus())) - .filter(o -> Arrays.asList("testing", "tested").contains(o.getStage())).map(o -> o.getId() + "").collect(Collectors.joining(","))); + .filter(o -> Arrays.asList("testing", "tested",StoryStageEnums.productVerified.getValue() + ,StoryStageEnums.productWaitVerified.getValue() + ).contains(o.getStage())).map(o -> o.getId() + "").collect(Collectors.joining(","))); result.setJxzCount(ztStory.stream().filter(o -> "active".equals(o.getStatus())) .filter(o -> Arrays.asList("developing", "developed").contains(o.getStage())).map(o -> o.getId() + "").collect(Collectors.joining(","))); @@ -543,6 +552,10 @@ public class IZtCountService { continue; } performanceDTO=JSON.parseObject(ztMonthScore.getScopeJson(),PerformanceDTO.class); + if (ztUser.getAccount().equals("weidongxia")) { + generatorCPJLExcel(qo, performanceDTO.getUserName(), performanceDTO, qo.getDate(), perList); + list.add(performanceDTO.getUserName() + "产品经理考核.xlsx"); + } else if (ztUser.getAccount().equals("liyuyan")) { generatorXMZLExcel(performanceDTO.getUserName(), performanceDTO, qo.getDate()); list.add(performanceDTO.getUserName() + "项目助理考核.xlsx"); @@ -564,6 +577,9 @@ public class IZtCountService { } else if (ztUser.getUserType() == UserType.XMJL || ztUser.getUserType() == UserType.XMGLY || ztUser.getUserType() == UserType.GSGC) { generatorXMJLExcel(qo, performanceDTO.getUserName(), performanceDTO, qo.getDate(), perList); list.add(performanceDTO.getUserName() + "项目经理考核.xlsx"); + }else if (ztUser.getUserType() == UserType.CPJL ) { + generatorCPJLExcel(qo, performanceDTO.getUserName(), performanceDTO, qo.getDate(), perList); + list.add(performanceDTO.getUserName() + "产品经理考核.xlsx"); } } String dir = System.getProperty("user.dir") + "/"; @@ -1167,6 +1183,69 @@ public class IZtCountService { return dto; } + PerformanceDTO buildCPJLScore(ZtUser u,Map userMap,ZtProjectQo qo, List approvalList, Date firstDayOfMonth, Date lastDayOfMonth, List taskList, Date d,List pids) { + + /** + * 产品经理 + */ + PerformanceDTO dto = new PerformanceDTO(); + dto.setUserName(u.getNickname()); + dto.setAccount(u.getAccount()); + //项目准时率 Σ (当月上线准时上线的需求量) / Σ (当月规划上线的需求总量) * 100≥95% + +// dto.setProjectOnTimeRate(); + //产品缺陷率 +// dto.setProductBugRate(); + Map map=this.storyService.storyReleaseOnTimeByProducts(pids,firstDayOfMonth,lastDayOfMonth); + Integer planReleaseStoryNum = Integer.valueOf(map.getOrDefault("planReleaseStoryNum", 0).toString()); + Integer storyOnTimeNum = Integer.valueOf(map.getOrDefault("onTimeNum", 0).toString()); + dto.setProductPlanStoryNum(BigDecimal.valueOf(planReleaseStoryNum)); + dto.setProductPlanStoryOnTimeNum(BigDecimal.valueOf(storyOnTimeNum)); + int storyOnTimeRate = ObjectUtil.equal(planReleaseStoryNum, 0) ? 0 : + (BigDecimal.valueOf(storyOnTimeNum*100).divide(BigDecimal.valueOf(planReleaseStoryNum), 0, BigDecimal.ROUND_UP)).intValue(); + int productProjectOnTimeRateScore=storyOnTimeRate==0?0: (storyOnTimeRate>=95)? 20: + (storyOnTimeRate>=90&&storyOnTimeRate<95)?(20-(95-storyOnTimeRate)): + ((90-storyOnTimeRate)*2+5>20)? 0:(20-((90-storyOnTimeRate)*2+5)); + dto.setProductProjectOnTimeRateScore(BigDecimal.valueOf(productProjectOnTimeRateScore)); + + + + List allBugList = this.bugService.list(new QueryWrapper().lambda().select(SFunctionColums.bugColumes()) + .eq(ZtBug::getOpenedby, u.getAccount()) + .in(ZtBug::getBugType,"releaseBug","prod") + .ge(ZtBug::getOpeneddate,firstDayOfMonth) + .le(ZtBug::getOpeneddate,lastDayOfMonth) + .in(ZtBug::getProduct,pids)); + allBugList = this.bugService.getNormalBugList(allBugList); + //开发分配工时 + ZtCountQo ztCountQo = new ZtCountQo(); + BeanUtils.copyProperties(qo, ztCountQo); + List ztProducts = this.productService.listByIds(pids); + List perList = this.programCount(ztCountQo,ztProducts.stream().map(o->o.getProgram()).collect(Collectors.toList())); + dto = buildTaskManage(dto, perList, userMap,firstDayOfMonth,lastDayOfMonth,pids); + //开发任务分配工时 + BigDecimal allocationTime = dto.getAllocationTime(); + double bugRate=(allocationTime.doubleValue()==0||allBugList.size()==0)?0:BigDecimal.valueOf(allBugList.size()*1000).divide(allocationTime,2,BigDecimal.ROUND_HALF_UP).doubleValue(); + dto.setBugDensity(BigDecimal.valueOf(bugRate)); + if(bugRate<=5){ + dto.setBugScore(BigDecimal.valueOf(20)); + }else{ +// long deductBugScore = Math.min(seriousBug * 10L + slightBug * 3L, 20L); +// dto.setBugScore(BigDecimal.valueOf(20L-deductBugScore)); + //严重10 分 轻微3分一个 + int bugScore = 0; + for (ZtBug b : allBugList) { + bugScore += b.getSeverity() != 1 ? 3 : 10; + } + dto.setBugScore(bugScore>20?BigDecimal.ZERO:BigDecimal.valueOf((20 - bugScore))); + } + //线上重大 + dto.setSeriousBug(BigDecimal.valueOf(allBugList.stream().filter(o -> Arrays.asList(1).contains(o.getSeverity())).count())); + //线上轻微 + dto.setSlightBug( BigDecimal.valueOf(allBugList.stream().filter(o -> Arrays.asList(4, 2, 3).contains(o.getSeverity())).count())); + return dto; + } + //满分30 private PerformanceDTO getMeetScore(ZtUser u, Date start, Date end, PerformanceDTO dto) { List pIds = this.projectService.authProductList(); @@ -2197,7 +2276,52 @@ public class IZtCountService { writeXlsx(name, "templates/scope/项目经理考核.xlsx", name + "项目经理考核.xlsx", dataMap); } + void generatorCPJLExcel(ZtCountQo qo, String name, PerformanceDTO performanceDTO, Date d, List perList) { + Map userMap = userService.userMapByIds(null); + Map dataMap = new HashMap<>(); + dataMap.put("name", name); + dataMap.put("date", DateUtils.formatDate(d, "yyyy-MM")); + //产品规划 + dataMap.put("productPlanning",performanceDTO.getProductPlanning()==null?"0":performanceDTO.getProductPlanning().toString()); + //需求准确性 + dataMap.put("accurateDemand", performanceDTO.getAccurateDemand()==null?"0": performanceDTO.getAccurateDemand().toString()); + //项目准时率 + dataMap.put("productProjectOnTimeRateScore",performanceDTO.getProductProjectOnTimeRateScore()==null?"0": performanceDTO.getProductProjectOnTimeRateScore().multiply(BigDecimal.valueOf(100)).toString()); + //线上Bug得分 缺陷 + dataMap.put("bugScope", performanceDTO.getBugScore()==null?"0": performanceDTO.getBugScore().toString()); + //开发分配工时 + dataMap.put("allocationTime", performanceDTO.getBugScore()==null?"0": performanceDTO.getAllocationTime().toString()); + //轻微 + dataMap.put("slightBug", performanceDTO.getSlightBug()==null?"0": performanceDTO.getSlightBug().toString()); + dataMap.put("seriousBug", performanceDTO.getSeriousBug()==null?"0": performanceDTO.getSeriousBug().toString()); + //问题响应 + dataMap.put("productProblemResponse", performanceDTO.getProductProblemResponse()==null?"0": performanceDTO.getProductProblemResponse().toString()); + //会议管理 + dataMap.put("productMeetScore", performanceDTO.getProductMeetScore()==null?"0": performanceDTO.getProductMeetScore().toString()); + //主动性责任感 + dataMap.put("productResponsibilityScore", performanceDTO.getProductResponsibilityScore()==null?"0": performanceDTO.getProductResponsibilityScore().toString()); + dataMap.put("productPlanStoryNum", performanceDTO.getProductPlanStoryNum()==null?"0": performanceDTO.getProductPlanStoryNum().toString()); + dataMap.put("productPlanStoryOnTimeNum", performanceDTO.getProductPlanStoryOnTimeNum()==null?"0": performanceDTO.getProductPlanStoryOnTimeNum().toString()); + dataMap.put("allocationTime", performanceDTO.getAllocationTime()==null?"0": performanceDTO.getAllocationTime().toString()); + dataMap.put("slightBug", performanceDTO.getSlightBug()==null?"0": performanceDTO.getSlightBug().toString()); + dataMap.put("seriousBug", performanceDTO.getSeriousBug()==null?"0": performanceDTO.getSeriousBug().toString()); + dataMap.put("bugDensity", performanceDTO.getBugDensity()==null?"0": performanceDTO.getBugDensity().toString()); + + + dataMap.put("总分", devlopTotal( + dataMap.get("productPlanning"), + dataMap.get("accurateDemand"), + dataMap.get("productProjectOnTimeRateScore"), + dataMap.get("bugScope"), + dataMap.get("productProblemResponse"), + dataMap.get("productMeetScore"), + dataMap.get("productResponsibilityScore") + )); + + writeXlsx(name, "templates/scope/产品经理考核.xlsx", name + "产品经理考核.xlsx", dataMap); + + } private String versionPlanScore(ZtCountQo qo, List perList, Map userMap) { return "20"; // BigDecimal allocationTime =BigDecimal.ZERO; @@ -2280,7 +2404,7 @@ public class IZtCountService { //线上bug List bList = this.bugService.list(new QueryWrapper().lambda().in(ZtBug::getProject, projectIds) .ge(ZtBug::getOpeneddate, firstDayOfMonth).le(ZtBug::getOpeneddate, lastDayOfMonth) - .eq(ZtBug::getBugType, "prod") + .in(ZtBug::getBugType, "prod","releaseBug") ); if (CollectionUtils.isEmpty(bList)) { result.setBugScore(BigDecimal.valueOf(20)); @@ -2303,9 +2427,9 @@ public class IZtCountService { List slightBugList = bList.stream().filter(o -> slights.contains(o.getSeverity())).collect(Collectors.toList()); result.setSlightBug(BigDecimal.valueOf(slightBugList.size())); - BigDecimal bugRate = BigDecimal.valueOf(bList.size()).divide(BigDecimalUtils.isZero(allocationTime) ? BigDecimal.valueOf(1) : allocationTime, 2, BigDecimal.ROUND_HALF_UP); + BigDecimal bugRate = BigDecimal.valueOf(bList.size()).divide(BigDecimalUtils.isZero(allocationTime) ? BigDecimal.valueOf(1) : allocationTime, 3, BigDecimal.ROUND_HALF_UP); result.setBugDensity(bugRate); - int i = bugRate.multiply(BigDecimal.valueOf(100)).intValue(); + int i = bugRate.multiply(BigDecimal.valueOf(1000)).intValue(); if (i <= 5) { result.setBugScore(BigDecimal.valueOf(20)); return result; @@ -2367,10 +2491,6 @@ public class IZtCountService { } private PerformanceDTO buildTaskManage(PerformanceDTO result, List perList, Map userMap,Date startDate,Date endDate,List pids) { - - - - //多个项目组人员 List multipUser = this.getmultipleDepartProjectTeam(startDate, endDate); //分配总工时 @@ -2588,7 +2708,9 @@ public class IZtCountService { //获取测试需求 List cswcStoryList = this.storyService.list(new QueryWrapper().lambda().in(ZtStory::getProduct, pids) .in(ZtStory::getAssignedTo, Arrays.asList(u.getAccount())).ge(ZtStory::getReleaseddate, firstDayOfMonth).le(ZtStory::getReleaseddate, lastDayOfMonth)); - + if (name.equals("weidongxia")) { + result = buildCPJLScore(u, userMap , qo,approvalList, firstDayOfMonth, lastDayOfMonth, taskList, d,pids); + } else if (name.equals("liyuyan")) { result = buildXMZLScore(u, approvalList, firstDayOfMonth, lastDayOfMonth, taskList, d); } else if ("liushengqing".equals(name)) { @@ -2616,6 +2738,9 @@ public class IZtCountService { result.setProfessionalSkillEnhancementScore(BigDecimal.valueOf(0)); } else if (u.getUserType() == UserType.XMZL) { result = buildXMZLScore(u, approvalList, firstDayOfMonth, lastDayOfMonth, taskList, d); + } else if (u.getUserType() == UserType.CPJL) { + //产品经理 + result = buildCPJLScore(u, userMap , qo,approvalList, firstDayOfMonth, lastDayOfMonth, taskList, d,pids); } return result; diff --git a/src/main/java/com/sa/zentao/service/impl/ZtProjectServiceImpl.java b/src/main/java/com/sa/zentao/service/impl/ZtProjectServiceImpl.java index 2267ddc..aa4d873 100644 --- a/src/main/java/com/sa/zentao/service/impl/ZtProjectServiceImpl.java +++ b/src/main/java/com/sa/zentao/service/impl/ZtProjectServiceImpl.java @@ -260,7 +260,6 @@ public class ZtProjectServiceImpl extends ServiceImpl allTaskList =this.taskService.taskListByEIdsAndDate(firstDayOfMonth,lastDayOfMonth,products.stream().map(o->o.getId()).collect(Collectors.toList())); - if (CollectionUtils.isEmpty(allTaskList)) { - return new PageInfo<>(); - } +// if (CollectionUtils.isEmpty(allTaskList)) { +// return new PageInfo<>(); +// } List teams = this.teamService.list(new QueryWrapper().lambda().eq(ZtTeam::getType, "execution") diff --git a/src/main/java/com/sa/zentao/service/impl/ZtReleaseServiceImpl.java b/src/main/java/com/sa/zentao/service/impl/ZtReleaseServiceImpl.java index 1cfd7c7..d071a2d 100644 --- a/src/main/java/com/sa/zentao/service/impl/ZtReleaseServiceImpl.java +++ b/src/main/java/com/sa/zentao/service/impl/ZtReleaseServiceImpl.java @@ -112,7 +112,7 @@ public class ZtReleaseServiceImpl extends ServiceImpl sList = this.storyService.list(new QueryWrapper() .lambda().eq(ZtStory::getStatus, "active") .in(ZtStory::getId, list.stream().map(o -> o.getStory()).distinct().collect(Collectors.toList())) - .eq(ZtStory::getStage, StoryStageEnums.tested.getValue())); + .eq(ZtStory::getStage, StoryStageEnums.productVerified.getValue())); if (!CollectionUtils.isEmpty(sList)) { //没有被关联的需求 测试完成的 List releaseStoryList = this.releaseDetailsService.list(new QueryWrapper().lambda() @@ -368,7 +368,7 @@ public class ZtReleaseServiceImpl extends ServiceImpl ztStories = CollectionUtils.isEmpty(storyList)?new ArrayList<>():storyService.listByIds(storyList); List bugs =CollectionUtils.isEmpty(bugList)?new ArrayList<>(): bugService.listByIds(bugList); - long notTested = ztStories.stream().filter(o -> !o.getStage().equals("tested")).count(); + long notTested = ztStories.stream().filter(o -> !o.getStage().equals(StoryStageEnums.productVerified.getValue())).count(); if(notTested>0){ throw new BusinessException("当前需求存在未测试完成的请检查"); } @@ -378,7 +378,7 @@ public class ZtReleaseServiceImpl extends ServiceImpl impl if (ztUser != null) { d.setUserStoryCreateUser(ztUser.getNickname()); } + ztUser = userMap.get(d.getProductUser()); + if (ztUser != null) { + d.setProductUserName(ztUser.getNickname()); + } List ztProjectList = executionMapByStory.get(d.getId()); if (!CollectionUtils.isEmpty(ztProjectList)) { // d.setExecution(ztProject.getId()); @@ -808,6 +813,8 @@ public class ZtStoryServiceImpl extends ServiceImpl impl for (ZtStoryDTO d : list) { d.setRevieweUser(d.getReviewedby().replaceAll(",", "")); d.setViews(rMap.get(d.getId())); + StoryStageEnums stageEnums = StoryStageEnums.transfer(d.getStage()); + d.setStageName(stageEnums==null?null:stageEnums.getDesc()); List ztTasks = taskCountMap.get(d.getId()); ZtStoryCaseDTO ztStoryCaseDTO = caseMap.get(d.getId()); if (ztStoryCaseDTO != null) { @@ -849,6 +856,14 @@ public class ZtStoryServiceImpl extends ServiceImpl impl if (ztUser != null) { d.setYsUserName(ztUser.getNickname()); } + ztUser = userMap.get(d.getProductUser()); + if (ztUser != null) { + d.setProductUserName(ztUser.getNickname()); + } + ztUser = userMap.get(d.getTestUser()); + if (ztUser != null) { + d.setTestUserName(ztUser.getNickname()); + } } } @@ -956,6 +971,105 @@ public class ZtStoryServiceImpl extends ServiceImpl impl return searchDTOPageInfo; } + @Override + @Transactional + public void testSubmitVerified(ZtStoryDTO qo) { + ZtStory ztStory = this.baseMapper.selectById(qo.getId()); + if(ztStory==null){ + throw new BusinessException("未查询到需求"); + } + if(!StoryStatusEnums.active.getValue().equals(ztStory.getStatus())){ + throw new BusinessException("需求状态不对"); + } + if(!StoryStageEnums.tested.getValue().equals(ztStory.getStage())){ + throw new BusinessException("需求状态不对"); + } + if(ObjectUtil.equal(ztStory.getInnerYsFlag(),1)){ + throw new BusinessException("无需内部验收,不需要提交"); + } + //产品验收中 + ztStory.setStage(StoryStageEnums.productWaitVerified.getValue()); + ztStory.setTestSubmitYsTime(new Date()); + ztStory.setLasteditedby(RiskUserThreadLocal.get().getName()); + ztStory.setLastediteddate(new Date()); + this.baseMapper.updateById(ztStory); + + List ztProjectstories = this.projectstoryService.projectListByStory(Arrays.asList(ztStory.getId()), ProjectTypeEnums.execution); + //更改看板 + + if(!CollectionUtils.isEmpty(ztProjectstories)){ + for(ZtProjectstory ztProjectstory:ztProjectstories){ + kanbanlaneService.changeStatus(ztProjectstory.getProject(),ztStory.getId(),"story",StoryStageEnums.productWaitVerified.getValue()); + } + } + actionService.addAction(ActionType.XQ, ActionStatus.TESTSUBMITYS, ztStory.getId(), ztStory.getProduct() + "", null, null, + RiskUserThreadLocal.get().getName(), "", ""); + } + + @Override + public void storyProductUserYs(ZtStoryDTO qo) { + ZtStory ztStory = this.baseMapper.selectById(qo.getId()); + if(ztStory==null){ + throw new BusinessException("未查询到需求"); + } + if(!StoryStatusEnums.active.getValue().equals(ztStory.getStatus())){ + throw new BusinessException("需求状态不对"); + } + if(!StoryStageEnums.productWaitVerified.getValue().equals(ztStory.getStage())){ + throw new BusinessException("需求状态不对"); + } + //产品验收中 + ztStory.setStage(StoryStageEnums.productWaitVerified.getValue()); + ztStory.setLasteditedby(RiskUserThreadLocal.get().getName()); + ztStory.setLastediteddate(new Date()); + ztStory.setInnerYsTime(new Date()); + this.baseMapper.updateById(ztStory); + + List ztProjectstories = this.projectstoryService.projectListByStory(Arrays.asList(ztStory.getId()), ProjectTypeEnums.execution); + //更改看板 + + if(!CollectionUtils.isEmpty(ztProjectstories)){ + for(ZtProjectstory ztProjectstory:ztProjectstories){ + kanbanlaneService.changeStatus(ztProjectstory.getProject(),ztStory.getId(),"story",StoryStageEnums.productVerified.getValue()); + } + } + actionService.addAction(ActionType.XQ, ActionStatus.PRODUCTYS, ztStory.getId(), ztStory.getProduct() + "", null, null, + RiskUserThreadLocal.get().getName(), "", ""); + } + + @Override + public Map storyReleaseOnTimeByProducts(List pids, Date firstDayOfMonth, Date lastDayOfMonth) { + return this.baseMapper.storyReleaseOnTimeByProducts(pids,firstDayOfMonth,lastDayOfMonth); + } + + @Override + public void assignedToProductUser(ZtStoryDTO dto) { + ZtStory ztStory = this.baseMapper.selectById(dto.getId()); + if(ztStory==null){ + throw new BusinessException("未查询到数据"); + } + ztStory.setProductUser(dto.getAssignedTo()); + ztStory.setLastediteddate(new Date()); + ztStory.setLasteditedby(RiskUserThreadLocal.get().getName()); + this.baseMapper.updateById(ztStory); + actionService.addAction(ActionType.XQ, ActionStatus.ASSIGNTOPRODUCTUSER, ztStory.getId(), ztStory.getProduct() + "", null, null, + RiskUserThreadLocal.get().getName(), "", ""); + } + + @Override + public void assignedToTestUser(ZtStoryDTO dto) { + ZtStory ztStory = this.baseMapper.selectById(dto.getId()); + if(ztStory==null){ + throw new BusinessException("未查询到数据"); + } + ztStory.setTestUser(dto.getAssignedTo()); + ztStory.setLastediteddate(new Date()); + ztStory.setLasteditedby(RiskUserThreadLocal.get().getName()); + this.baseMapper.updateById(ztStory); + actionService.addAction(ActionType.XQ, ActionStatus.ASSIGNTOTESTUSER, ztStory.getId(), ztStory.getProduct() + "", null, null, + RiskUserThreadLocal.get().getName(), "", ""); + } + private List searchBug(SearchQo qo) { return this.baseMapper.searchBug(qo.getKeyword()); } @@ -1153,9 +1267,14 @@ public class ZtStoryServiceImpl extends ServiceImpl impl } } + /** + * 开发完成 + * @param id + * @param finishBy + */ @Override @Transactional - public void finishStory(Integer id, String finishBy) { + public void develFinishStory(Integer id, String finishBy) { if (id == null || id == 0) { return; @@ -1207,7 +1326,7 @@ public class ZtStoryServiceImpl extends ServiceImpl impl throw new BusinessException("未查询到数据-需求"); } - if (!Arrays.asList("wait", "projected", "developing", "developed", "testing").contains(ztStory.getStage())) { + if (!Arrays.asList("wait", "projected", "developing", "developed", "testing",StoryStageEnums.productWaitVerified.getValue(),StoryStageEnums.productVerified.getValue()).contains(ztStory.getStage())) { return; } @@ -1242,6 +1361,13 @@ public class ZtStoryServiceImpl extends ServiceImpl impl actionService.addAction(ActionType.XQ, ActionStatus.CSWC, ztStory.getId(), ztStory.getProduct() + "", ztStory.getProject(), null, RiskUserThreadLocal.get().getName(), null, ""); } + //不需要内部验收 产品已验收 + if(!ObjectUtil.equal(ztStory.getInnerYsFlag(),1)){ + newStatus = StoryStageEnums.productVerified.getValue(); + ztStory.setStage(newStatus); + ztStory.setInnerYsTime(new Date()); + } + if (ztStory.getStartDate() == null) { ztStory.setStartDate(new Date()); } @@ -1253,7 +1379,7 @@ public class ZtStoryServiceImpl extends ServiceImpl impl for (Integer execId : execIds) { if (execId != null) { - this.kanbanlaneService.changeStatus(execId, ztStory.getId(), "story", "tested"); + this.kanbanlaneService.changeStatus(execId, ztStory.getId(), "story", newStatus); if (!oldStatus.equalsIgnoreCase(newStatus) && ztStory.getProduct() != 0) { ProductStoryStatus oldPStatus = null; @@ -1270,7 +1396,6 @@ public class ZtStoryServiceImpl extends ServiceImpl impl } - //发布 @Override @Transactional @@ -1280,22 +1405,25 @@ public class ZtStoryServiceImpl extends ServiceImpl impl } ZtStory ztStory = this.baseMapper.selectById(story); + if (ztStory == null) { + throw new BusinessException("未查询到数据-需求"); + } if (ztStory.getFeedback() != null && ztStory.getFeedback() != 0) { this.storyFeedbackService.feedbackFinished(ztStory.getFeedback(), null); } - if (ztStory == null) { - throw new BusinessException("未查询到数据-需求"); - } - if (!Arrays.asList("wait", "projected", "developing", "developed", "testing", "tested").contains(ztStory.getStage())) { + + if (!Arrays.asList("wait", "projected", "developing", "developed", "testing", "tested", + StoryStageEnums.productWaitVerified.getValue() + ,StoryStageEnums.productVerified.getValue()).contains(ztStory.getStage())) { return; } if ("closed".equals(ztStory.getStatus())) { throw new BusinessException("当前已关闭"); } - if (!"tested".equals(ztStory.getStage())) { + if (!StoryStageEnums.productVerified.getValue().equals(ztStory.getStage())) { throw new BusinessException("当前无法更改发布状态"); } //这个需求被多少执行关联 @@ -1397,7 +1525,7 @@ public class ZtStoryServiceImpl extends ServiceImpl impl if (!CollectionUtils.isEmpty(testedList)) { testedStory(story, finishBy); } else { - this.finishStory(story, finishBy); + this.develFinishStory(story, finishBy); } } @@ -1438,7 +1566,7 @@ public class ZtStoryServiceImpl extends ServiceImpl impl .eq(ZtTask::getType, TaskType.devel.getCode()) .in(ZtTask::getStatus, Arrays.asList("done"))); if (!CollectionUtils.isEmpty(develedList)) { - this.finishStory(story, finishBy); + this.develFinishStory(story, finishBy); return; } return; @@ -1486,7 +1614,7 @@ public class ZtStoryServiceImpl extends ServiceImpl impl if (done < 1) { } else { - this.finishStory(story, finishBy); + this.develFinishStory(story, finishBy); } } } else { @@ -2150,6 +2278,14 @@ public class ZtStoryServiceImpl extends ServiceImpl impl if (ztUser != null) { d.setYsUserName(ztUser.getNickname()); } + ztUser = userMap.get(d.getProductUser()); + if (ztUser != null) { + d.setProductUserName(ztUser.getNickname()); + } + ztUser = userMap.get(d.getTestUser()); + if (ztUser != null) { + d.setTestUserName(ztUser.getNickname()); + } ZtStoryspec storyspec = this.storyspecService.getOne(new QueryWrapper().lambda().eq(ZtStoryspec::getStory, d.getId())); if (storyspec != null) { @@ -2449,6 +2585,7 @@ public class ZtStoryServiceImpl extends ServiceImpl impl ProductStoryStatus oldPStatus = null; if (status.equalsIgnoreCase("developing") || status.equalsIgnoreCase("testing") || status.equalsIgnoreCase("tested") || status.equalsIgnoreCase("developed") + || status.equalsIgnoreCase(StoryStageEnums.productWaitVerified.getValue())|| status.equalsIgnoreCase(StoryStageEnums.productVerified.getValue()) ) { oldPStatus = ProductStoryStatus.JH; diff --git a/src/main/java/com/sa/zentao/service/impl/ZtStoryUserServiceImpl.java b/src/main/java/com/sa/zentao/service/impl/ZtStoryUserServiceImpl.java index 9530596..f91bf89 100644 --- a/src/main/java/com/sa/zentao/service/impl/ZtStoryUserServiceImpl.java +++ b/src/main/java/com/sa/zentao/service/impl/ZtStoryUserServiceImpl.java @@ -252,10 +252,10 @@ public class ZtStoryUserServiceImpl extends ServiceImpl> getKanban(Integer productId) { List ztStoryUsers = this.baseMapper.selectList(new QueryWrapper() - .lambda().select(ZtStoryUser::getId,ZtStoryUser::getPri,ZtStoryUser::getTitle,ZtStoryUser::getStage,ZtStoryUser::getStatus,ZtStoryUser::getOpenedby,ZtStoryUser::getOpeneddate,ZtStoryUser::getLastediteddate) + .lambda().select(ZtStoryUser::getId,ZtStoryUser::getPri,ZtStoryUser::getTitle,ZtStoryUser::getStage,ZtStoryUser::getStatus,ZtStoryUser::getOpenedby,ZtStoryUser::getOpeneddate,ZtStoryUser::getLastediteddate,ZtStoryUser::getProductUser) .eq(ZtStoryUser::getProduct, productId).orderByDesc(ZtStoryUser::getId)); Map> map = new HashMap<>(); - Map stringZtUserMap = this.userService.userMapByIds(ztStoryUsers.stream().map(o -> o.getOpenedby()).collect(Collectors.toList())); + Map stringZtUserMap = this.userService.userMapByIds(null); List ztStoryUserDTOS = BeanCopyUtil.copyListProperties(ztStoryUsers, ZtStoryUserDTO::new, new BeanCopyUtilCallBack() { @Override @@ -265,6 +265,11 @@ public class ZtStoryUserServiceImpl extends ServiceImpl ztStories = storyUserMap.get(d.getId()); if (!CollectionUtils.isEmpty(ztStories)) { d.setStoryList(ztStories.stream().map(o ->o.getId()+":"+o.getTitle()).collect(Collectors.joining(","))); @@ -377,7 +387,6 @@ public class ZtStoryUserServiceImpl extends ServiceImpl> storyUserMap = getStoryUserMap(Arrays.asList(dto)); List ztStories = storyUserMap.get(d.getId()); if (!CollectionUtils.isEmpty(ztStories)) { @@ -867,6 +880,18 @@ public class ZtStoryUserServiceImpl extends ServiceImpl> getStoryUserMap(List list) { List ids = list.stream().map(o -> o.getId()).collect(Collectors.toList()); diff --git a/src/main/java/com/sa/zentao/service/impl/ZtTaskServiceImpl.java b/src/main/java/com/sa/zentao/service/impl/ZtTaskServiceImpl.java index 73e9738..4355f5b 100644 --- a/src/main/java/com/sa/zentao/service/impl/ZtTaskServiceImpl.java +++ b/src/main/java/com/sa/zentao/service/impl/ZtTaskServiceImpl.java @@ -1,5 +1,6 @@ package com.sa.zentao.service.impl; +import cn.hutool.core.util.ObjectUtil; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; @@ -518,7 +519,7 @@ public class ZtTaskServiceImpl extends ServiceImpl impleme List userIds = list.stream().map(o -> o.getFinishedby()).collect(Collectors.toList()); userIds.addAll(list.stream().map(o -> o.getAssignedTo()).collect(Collectors.toList())); Map userMap = this.userService.userMapByIds(null); - Date d=new Date(); + Date d = new Date(); for (ZtTaskDTO task : list) { ZtUser ztUser = userMap.get(task.getFinishedby()); if (ztUser != null) { @@ -531,16 +532,16 @@ public class ZtTaskServiceImpl extends ServiceImpl impleme if (task.getDeadline() != null) { task.setDeadline(DateUtils.getDayLast(task.getDeadline())); } - if(task.getFinishedDate()!=null){ - if(task.getFinishedDate().getTime()>DateUtils.getDayLast(task.getDeadline()).getTime()){ + if (task.getFinishedDate() != null) { + if (task.getFinishedDate().getTime() > DateUtils.getDayLast(task.getDeadline()).getTime()) { task.setDelayFlag(1); - }else{ + } else { task.setDelayFlag(2); } - }else{ - if(d.getTime()>DateUtils.getDayLast(task.getDeadline()).getTime()){ + } else { + if (d.getTime() > DateUtils.getDayLast(task.getDeadline()).getTime()) { task.setDelayFlag(1); - }else{ + } else { task.setDelayFlag(2); } } @@ -577,7 +578,8 @@ public class ZtTaskServiceImpl extends ServiceImpl impleme if (story != null && story != 0) { ZtStory ztStory = this.storyService.getById(story); // wait 初始化 projected 已立项 developing 研发中 developed 研发完毕 testing 测试中 tested - if (!Arrays.asList("wait", "projected", "developing", "developed", "testing", "tested").contains(ztStory.getStage())) { + if (!Arrays.asList("wait", "projected", "developing", "developed", "testing", "tested",StoryStageEnums.productWaitVerified.getValue() + ,StoryStageEnums.productVerified.getValue()).contains(ztStory.getStage())) { throw new BusinessException("当前状态无法添加任务"); } if (ztStory.getStatus().equals("closed")) { @@ -1033,17 +1035,17 @@ public class ZtTaskServiceImpl extends ServiceImpl impleme if("test".equals(ztTask.getType())&&!StringUtils.isEmpty(ztTask.getDeliverContent())){ actionService.addAction(ActionType.RW, ActionStatus.TJJFW, dto.getId(), projectproduct == null ? null : projectproduct.getProduct().toString(), projectproduct == null ? null : projectproduct.getProject(), ztTask.getExecution(), StringUtils.isEmpty(finishBy) ? RiskUserThreadLocal.get().getName() : finishBy, dto.getDeliverContent(), null); - if(ztTask.getStory()!=null&&ztTask.getStory()!=0){ - //更新需求交付物 + if (ztTask.getStory() != null && ztTask.getStory() != 0) { ZtStory ztStory = this.storyService.getById(ztTask.getStory()); if(ztStory!=null){ - ztStory.setDeliverContent(ztTask.getDeliverContent()); + ztStory.setDeliverContent(dto.getDeliverContent()); + ztStory.setLasteditedby(RiskUserThreadLocal.get().getName()); + ztStory.setLastediteddate(new Date()); this.storyService.updateById(ztStory); } - actionService.addAction(ActionType.XQ, ActionStatus.TJJFW, dto.getId(), projectproduct == null ? null : projectproduct.getProduct().toString(), projectproduct == null ? null : projectproduct.getProject(), ztTask.getExecution(), + actionService.addAction(ActionType.XQ, ActionStatus.TJJFW, ztTask.getStory(), projectproduct == null ? null : projectproduct.getProduct().toString(), projectproduct == null ? null : projectproduct.getProject(), ztTask.getExecution(), StringUtils.isEmpty(finishBy) ? RiskUserThreadLocal.get().getName() : finishBy, dto.getDeliverContent(), null); } - } if(!StringUtils.isEmpty(dto.getRemark())){ actionService.addAction(ActionType.RW, ActionStatus.TJBZ, dto.getId(), projectproduct == null ? null : projectproduct.getProduct().toString(), projectproduct == null ? null : projectproduct.getProject(), ztTask.getExecution(), StringUtils.isEmpty(finishBy) ? RiskUserThreadLocal.get().getName() : finishBy, dto.getRemark(), null); @@ -1077,9 +1079,13 @@ public class ZtTaskServiceImpl extends ServiceImpl impleme this.storyService.taskFinishChangeStatus(ztTask.getStory(), finishBy, TaskType.transferType(type), false); } if (ztTask.getFeedback() != null && ztTask.getFeedback() != 0) { - this.storyFeedbackService.feedbackFinished(ztTask.getFeedback(),ztTask.getFeedbackRemark()); + this.storyFeedbackService.feedbackFinished(ztTask.getFeedback(), ztTask.getFeedbackRemark()); + } + if(ztTask.getStory()!=null&&ztTask.getStory()!=0&& ObjectUtil.equal(dto.getInnerYsFlag(),1)){ + ZtStoryDTO qo=new ZtStoryDTO(); + qo.setId(ztTask.getStory()); + this.storyService.testSubmitVerified(qo); } - } @Override @@ -1096,9 +1102,9 @@ public class ZtTaskServiceImpl extends ServiceImpl impleme ztTask.setLastediteddate(new Date()); this.baseMapper.updateById(ztTask); - if(ztTask.getDeadline()!=null&&ztTask.getStory()!=null&&ztTask.getStory()!=0){ + if (ztTask.getDeadline() != null && ztTask.getStory() != null && ztTask.getStory() != 0) { ZtStory ztStory = this.storyService.getById(ztTask.getStory()); - if(ztTask.getType().equals("devel")&&ztStory.getDevelPlanEndTime()!=null){ + if (ztTask.getType().equals("devel") && ztStory.getDevelPlanEndTime() != null) { //查找最大时间那个 ztStory.setDevelPlanEndTime(ztTask.getDeadline()); List ztTasks = this.baseMapper.selectList(new QueryWrapper().lambda() @@ -1109,10 +1115,10 @@ public class ZtTaskServiceImpl extends ServiceImpl impleme .in(ZtTask::getStatus, "pause", "done", "wait", "reviewing", "doing", "draft") .eq(ZtTask::getStory, ztTask.getStory()).orderByDesc(ZtTask::getDeadline)); this.storyService.update(new UpdateWrapper().lambda() - .eq(ZtStory::getId, ztTask.getStory()) - .set(ZtStory::getDevelPlanEndTime,CollectionUtils.isEmpty(ztTasks)?null:DateUtils.getDayLast(ztTasks.get(0).getDeadline()))); + .eq(ZtStory::getId, ztTask.getStory()) + .set(ZtStory::getDevelPlanEndTime, CollectionUtils.isEmpty(ztTasks) ? null : DateUtils.getDayLast(ztTasks.get(0).getDeadline()))); } - if(ztTask.getType().equals("test")&&ztStory.getTestPlanEndTime()!=null){ + if (ztTask.getType().equals("test") && ztStory.getTestPlanEndTime() != null) { //查找最大时间那个 ztStory.setDevelPlanEndTime(ztTask.getDeadline()); List ztTasks = this.baseMapper.selectList(new QueryWrapper().lambda() @@ -1124,12 +1130,11 @@ public class ZtTaskServiceImpl extends ServiceImpl impleme .eq(ZtTask::getStory, ztTask.getStory()).orderByDesc(ZtTask::getDeadline)); this.storyService.update(new UpdateWrapper().lambda() .eq(ZtStory::getId, ztTask.getStory()) - .set(ZtStory::getTestPlanEndTime,CollectionUtils.isEmpty(ztTasks)?null:DateUtils.getDayLast(ztTasks.get(0).getDeadline()))); + .set(ZtStory::getTestPlanEndTime, CollectionUtils.isEmpty(ztTasks) ? null : DateUtils.getDayLast(ztTasks.get(0).getDeadline()))); } } - ZtProjectproduct projectproduct = projectproductService.getOne(new QueryWrapper().lambda().eq(ZtProjectproduct::getProject, ztTask.getProject())); //添加action @@ -1175,9 +1180,9 @@ public class ZtTaskServiceImpl extends ServiceImpl impleme //添加action actionService.addAction(ActionType.RW, ActionStatus.QX, dto.getId(), projectproduct == null ? null : projectproduct.getProduct().toString(), ztTask.getProject(), ztTask.getExecution(), RiskUserThreadLocal.get().getName(), dto.getDesc(), null); - if(ztTask.getDeadline()!=null&&ztTask.getStory()!=null&&ztTask.getStory()!=0){ + if (ztTask.getDeadline() != null && ztTask.getStory() != null && ztTask.getStory() != 0) { ZtStory ztStory = this.storyService.getById(ztTask.getStory()); - if(ztTask.getType().equals("devel")&&ztStory.getDevelPlanEndTime()!=null){ + if (ztTask.getType().equals("devel") && ztStory.getDevelPlanEndTime() != null) { //查找最大时间那个 ztStory.setDevelPlanEndTime(ztTask.getDeadline()); List ztTasks = this.baseMapper.selectList(new QueryWrapper().lambda() @@ -1189,9 +1194,9 @@ public class ZtTaskServiceImpl extends ServiceImpl impleme .eq(ZtTask::getStory, ztTask.getStory()).orderByDesc(ZtTask::getDeadline)); this.storyService.update(new UpdateWrapper().lambda() .eq(ZtStory::getId, ztTask.getStory()) - .set(ZtStory::getDevelPlanEndTime,CollectionUtils.isEmpty(ztTasks)?null:DateUtils.getDayLast(ztTasks.get(0).getDeadline()))); + .set(ZtStory::getDevelPlanEndTime, CollectionUtils.isEmpty(ztTasks) ? null : DateUtils.getDayLast(ztTasks.get(0).getDeadline()))); } - if(ztTask.getType().equals("test")&&ztStory.getTestPlanEndTime()!=null){ + if (ztTask.getType().equals("test") && ztStory.getTestPlanEndTime() != null) { //查找最大时间那个 ztStory.setDevelPlanEndTime(ztTask.getDeadline()); List ztTasks = this.baseMapper.selectList(new QueryWrapper().lambda() @@ -1203,7 +1208,7 @@ public class ZtTaskServiceImpl extends ServiceImpl impleme .eq(ZtTask::getStory, ztTask.getStory()).orderByDesc(ZtTask::getDeadline)); this.storyService.update(new UpdateWrapper().lambda() .eq(ZtStory::getId, ztTask.getStory()) - .set(ZtStory::getTestPlanEndTime,CollectionUtils.isEmpty(ztTasks)?null:DateUtils.getDayLast(ztTasks.get(0).getDeadline()))); + .set(ZtStory::getTestPlanEndTime, CollectionUtils.isEmpty(ztTasks) ? null : DateUtils.getDayLast(ztTasks.get(0).getDeadline()))); } } if (ztTask.getExecution() != null && ztTask.getExecution() != 0) { @@ -1251,7 +1256,7 @@ public class ZtTaskServiceImpl extends ServiceImpl impleme List list = dto.getList(); long nullDeadlineCount = list.stream().filter(o -> o.getDeadline() == null || o.getEstStarted() == null).count(); - if(nullDeadlineCount>0){ + if (nullDeadlineCount > 0) { throw new BusinessException("请录入开始结束日期"); } @@ -1298,13 +1303,13 @@ public class ZtTaskServiceImpl extends ServiceImpl impleme } saveList.add(ztTask); - if(ztTask.getDeadline()!=null&&ztTask.getStory()!=null&&ztTask.getStory()!=0){ + if (ztTask.getDeadline() != null && ztTask.getStory() != null && ztTask.getStory() != 0) { ZtStory ztStory = this.storyService.getById(ztTask.getStory()); - if(ztTask.getType().equals("devel")&&(ztStory.getDevelPlanEndTime()==null||ztTask.getDeadline().getTime()>=ztStory.getDevelPlanEndTime().getTime() - )){ + if (ztTask.getType().equals("devel") && (ztStory.getDevelPlanEndTime() == null || ztTask.getDeadline().getTime() >= ztStory.getDevelPlanEndTime().getTime() + )) { ztStory.setDevelPlanEndTime(ztTask.getDeadline()); } - if(ztTask.getType().equals("test")&&(ztStory.getTestPlanEndTime()==null||ztTask.getDeadline().getTime()>=ztStory.getTestPlanEndTime().getTime())){ + if (ztTask.getType().equals("test") && (ztStory.getTestPlanEndTime() == null || ztTask.getDeadline().getTime() >= ztStory.getTestPlanEndTime().getTime())) { ztStory.setTestPlanEndTime(ztTask.getDeadline()); } this.storyService.updateById(ztStory); @@ -1319,7 +1324,7 @@ public class ZtTaskServiceImpl extends ServiceImpl impleme this.storyService.taskFinishChangeStatus(ztTask.getStory(), null, TaskType.transferType(ztTask.getType()), false); taskSendZpMessage(ztTask.getId(), null, ztTask.getAssignedTo()); } else { - taskSendPsMessage(ztTask.getId(), ztProject.getPm(),ztTask.getOpenedby()); + taskSendPsMessage(ztTask.getId(), ztProject.getPm(), ztTask.getOpenedby()); } } diff --git a/src/main/java/com/sa/zentao/utils/KanBanConstant.java b/src/main/java/com/sa/zentao/utils/KanBanConstant.java index 21af141..d6b14fc 100644 --- a/src/main/java/com/sa/zentao/utils/KanBanConstant.java +++ b/src/main/java/com/sa/zentao/utils/KanBanConstant.java @@ -22,6 +22,8 @@ public class KanBanConstant { put("test","测试"); put("testing","进行中"); put("tested","完成"); + put("productWaitVerified","产品验收中"); + put("productVerified","产品已验收"); put("released","已发布"); put("verified","已验收"); put("closed","已关闭"); @@ -70,6 +72,8 @@ public class KanBanConstant { put("story-test","test"); put("story-testing","test"); put("story-tested","test"); + put("story-productWaitVerified","productWaitVerified"); + put("story-productVerified","productVerified"); put("story-verified","verified"); put("story-released","released"); put("story-closed","closed"); diff --git a/src/main/resources/mapper/ZtStoryMapper.xml b/src/main/resources/mapper/ZtStoryMapper.xml index 8a25740..0a455ad 100644 --- a/src/main/resources/mapper/ZtStoryMapper.xml +++ b/src/main/resources/mapper/ZtStoryMapper.xml @@ -136,6 +136,8 @@ s.ys_user, s.task_count, s.ys_date, + s.inner_ys_flag, + s.product_user, su.title userStoryName, su.id userStoryId, ps.title parentName, @@ -160,6 +162,10 @@ and s.assignedTo= #{qo.userName} + + and s.product_user= #{qo.userName} + + and s.openedBy= #{qo.userName} @@ -226,6 +232,9 @@ and s.title like concat('%', #{qo.title}, '%') + + and s.product_user like concat('%', #{qo.productUser}, '%') + and s.module = #{qo.module} @@ -364,6 +373,9 @@ s.ys_date, s.devel_plan_end_time, s.test_plan_end_time, + s.inner_ys_flag, + s.product_user, + s.test_user, pt.name productName from zt_story s @@ -429,7 +441,12 @@ and s.title like concat('%', #{qo.name}, '%') - + + and s.product_user like concat('%', #{qo.productUser}, '%') + + + and s.assignedTo like concat('%', #{qo.testUser}, '%') + and pt.name like concat('%', #{qo.productName}, '%') @@ -474,7 +491,12 @@ and s.status ='reviewing' and v.result = '' - + + and s.product_user = #{qo.userName} + + + and s.assignedTo = #{qo.userName} + and s.status = 'active' @@ -1317,4 +1339,25 @@ and (spc.spec like concat('%', #{keyword}, '%') or spc.verify like concat('%', #{keyword}, '%') or st.title like concat('%', #{keyword}, '%') ) order by id desc ) + + + diff --git a/src/main/resources/mapper/ZtStoryUserMapper.xml b/src/main/resources/mapper/ZtStoryUserMapper.xml index de8f051..4e0e5b4 100644 --- a/src/main/resources/mapper/ZtStoryUserMapper.xml +++ b/src/main/resources/mapper/ZtStoryUserMapper.xml @@ -140,7 +140,7 @@ s.deliver_remark, s.old_status, s.ys_user, - + s.product_user, pt.name productName from zt_story_user s LEFT JOIN zt_product pt on s.product = pt.id WHERE 1=1 @@ -188,9 +188,9 @@ and pt.name like concat('%', #{qo.productName}, '%') - - - + + and s.product_user like concat('%', #{qo.productUser}, '%') + @@ -199,10 +199,12 @@ and s.status != 'closed' + + and s.product_user = #{qo.userName} + and s.openedby= #{qo.userName} - and s.id = #{qo.id} diff --git a/src/main/resources/templates/scope/产品经理考核.xlsx b/src/main/resources/templates/scope/产品经理考核.xlsx index 2d386fe9d4d80cd2c647667969c2b5d1d67d3e43..815aeb41eb2710eeb09d848bfbc3264d284a08f8 100644 GIT binary patch delta 8037 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 De@;tR delta 8107 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 LzcmSQ{$u?Q7Tj|)