From 739e864699de1caee1b1c9c77e22cbc5340f416d Mon Sep 17 00:00:00 2001 From: guoqibing Date: Fri, 24 Apr 2026 14:58:24 +0800 Subject: [PATCH] =?UTF-8?q?=E9=9C=80=E6=B1=82=E6=8A=A5=E8=A1=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/ZtStoryExpandController.java | 63 ++++++++ .../ZtStoryMonthWorkloadController.java | 63 ++++++++ .../zentao/controller/ZtTaskController.java | 12 ++ .../com/sa/zentao/entity/ZtStoryExpand.java | 104 ++++++++++++ .../zentao/entity/ZtStoryMonthWorkload.java | 56 +++++++ .../sa/zentao/mapper/ZtStoryExpandMapper.java | 26 +++ .../mapper/ZtStoryMonthWorkloadMapper.java | 16 ++ src/main/java/com/sa/zentao/qo/StoryQo.java | 4 + .../java/com/sa/zentao/qo/ZtProjectQo.java | 4 + .../zentao/service/IZtStoryExpandService.java | 38 +++++ .../service/IZtStoryMonthWorkloadService.java | 40 +++++ .../zentao/service/impl/IZtCountService.java | 4 +- .../service/impl/ZtKanbanlaneServiceImpl.java | 13 +- .../impl/ZtStoryExpandServiceImpl.java | 148 ++++++++++++++++++ .../impl/ZtStoryMonthWorkloadServiceImpl.java | 97 ++++++++++++ .../service/impl/ZtTaskServiceImpl.java | 3 + .../resources/mapper/ZtStoryExpandMapper.xml | 45 ++++++ src/main/resources/mapper/ZtStoryMapper.xml | 14 +- .../mapper/ZtStoryMonthWorkloadMapper.xml | 14 ++ .../resources/mapper/ZtStoryUserMapper.xml | 7 +- 20 files changed, 760 insertions(+), 11 deletions(-) create mode 100644 src/main/java/com/sa/zentao/controller/ZtStoryExpandController.java create mode 100644 src/main/java/com/sa/zentao/controller/ZtStoryMonthWorkloadController.java create mode 100644 src/main/java/com/sa/zentao/entity/ZtStoryExpand.java create mode 100644 src/main/java/com/sa/zentao/entity/ZtStoryMonthWorkload.java create mode 100644 src/main/java/com/sa/zentao/mapper/ZtStoryExpandMapper.java create mode 100644 src/main/java/com/sa/zentao/mapper/ZtStoryMonthWorkloadMapper.java create mode 100644 src/main/java/com/sa/zentao/service/IZtStoryExpandService.java create mode 100644 src/main/java/com/sa/zentao/service/IZtStoryMonthWorkloadService.java create mode 100644 src/main/java/com/sa/zentao/service/impl/ZtStoryExpandServiceImpl.java create mode 100644 src/main/java/com/sa/zentao/service/impl/ZtStoryMonthWorkloadServiceImpl.java create mode 100644 src/main/resources/mapper/ZtStoryExpandMapper.xml create mode 100644 src/main/resources/mapper/ZtStoryMonthWorkloadMapper.xml diff --git a/src/main/java/com/sa/zentao/controller/ZtStoryExpandController.java b/src/main/java/com/sa/zentao/controller/ZtStoryExpandController.java new file mode 100644 index 0000000..dc788dc --- /dev/null +++ b/src/main/java/com/sa/zentao/controller/ZtStoryExpandController.java @@ -0,0 +1,63 @@ +package com.sa.zentao.controller; + +import com.sa.zentao.dao.Result; +import com.sa.zentao.entity.ZtStoryExpand; +import com.sa.zentao.service.IZtStoryExpandService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +/** + *

+ * 前端控制器 + *

+ * + * @author gqb + * @since 2026-03-30 + */ +@RestController +@RequestMapping("/zt-story-expand") +public class ZtStoryExpandController { + + @Autowired + private IZtStoryExpandService ztStoryExpandService; + + /** + * 打分接口 + * @param entity 需求扩展信息 + * @return 操作结果 + */ + @RequestMapping(value = "/saveScore", method = RequestMethod.POST, produces = "application/json; charset=UTF-8") + public Result saveScore(@RequestBody ZtStoryExpand entity) { + ztStoryExpandService.saveScore(entity); + return Result.success(); + } + + /** + * 按月份和项目查询(不分页) + * @param month 月份,格式:yyyy-MM + * @param projectId 产品项目 id(zt_project.id),可为空 + * @return 需求扩展信息列表 + */ + @RequestMapping(value = "/queryByMonth", method = RequestMethod.GET, produces = "application/json; charset=UTF-8") + public Result> queryByMonth(String month, Integer projectId) { + List list = ztStoryExpandService.queryByMonth(month, projectId); + return Result.success(list); + } + + /** + * 保存或更新(有数据了更新不新增) + * @param entity 需求扩展信息 + * @return 操作结果 + */ + @RequestMapping(value = "/saveOrUpdate", method = RequestMethod.POST, produces = "application/json; charset=UTF-8") + public Result saveOrUpdate(@RequestBody ZtStoryExpand entity) { + ztStoryExpandService.saveOrUpdateExpand(entity); + return Result.success(); + } + +} diff --git a/src/main/java/com/sa/zentao/controller/ZtStoryMonthWorkloadController.java b/src/main/java/com/sa/zentao/controller/ZtStoryMonthWorkloadController.java new file mode 100644 index 0000000..830c041 --- /dev/null +++ b/src/main/java/com/sa/zentao/controller/ZtStoryMonthWorkloadController.java @@ -0,0 +1,63 @@ +package com.sa.zentao.controller; + + +import com.sa.zentao.dao.Result; +import com.sa.zentao.entity.ZtStoryMonthWorkload; +import com.sa.zentao.service.IZtStoryMonthWorkloadService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RestController; +import java.math.BigDecimal; +import java.util.List; + +/** + *

+ * 前端控制器 + *

+ * + * @author gqb + * @since 2026-03-30 + */ +@RestController +@RequestMapping("/zt-story-month-workload") +public class ZtStoryMonthWorkloadController { + + @Autowired + private IZtStoryMonthWorkloadService ztStoryMonthWorkloadService; + + /** + * 保存完成度 + */ + @RequestMapping(value = "/saveCompletionDegree", method = RequestMethod.POST, produces = "application/json; charset=UTF-8") + public Result saveCompletionDegree(@RequestBody ZtStoryMonthWorkload entity) { + ztStoryMonthWorkloadService.saveCompletionDegree(entity); + return Result.success(); + } + + /** + * 获取所有不重复的月份列表 + */ + @RequestMapping(value = "/listMonths", method = RequestMethod.GET, produces = "application/json; charset=UTF-8") + public Result> listMonths() { + return Result.success(ztStoryMonthWorkloadService.listMonths()); + } + + /** + * 按 storyId + 月份查询单条记录 + */ + @RequestMapping(value = "/getByMonth", method = RequestMethod.GET, produces = "application/json; charset=UTF-8") + public Result getByMonth(Integer storyId, String month) { + return Result.success(ztStoryMonthWorkloadService.getByStoryIdAndMonth(storyId, month)); + } + + /** + * 查询某需求在指定月份之前的历史最高完成度 + */ + @RequestMapping(value = "/maxWorkloadBefore", method = RequestMethod.GET, produces = "application/json; charset=UTF-8") + public Result maxWorkloadBefore(Integer storyId, String month) { + return Result.success(ztStoryMonthWorkloadService.getMaxWorkloadBefore(storyId, month)); + } + +} diff --git a/src/main/java/com/sa/zentao/controller/ZtTaskController.java b/src/main/java/com/sa/zentao/controller/ZtTaskController.java index 259d091..aa23144 100644 --- a/src/main/java/com/sa/zentao/controller/ZtTaskController.java +++ b/src/main/java/com/sa/zentao/controller/ZtTaskController.java @@ -631,4 +631,16 @@ public class ZtTaskController { return Result.success(); } + + //更新任务deadline + @RequestMapping(value = "/updateDeadline", method = RequestMethod.GET, produces = "application/json; charset=UTF-8") + public Result updateDeadline(@RequestParam("id") Integer id, + @RequestParam("date") String date) throws ParseException { + SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); + ZtTask ztTask = new ZtTask(); + ztTask.setId(id); + ztTask.setDeadline(sdf.parse(date)); + ztTaskService.updateById(ztTask); + return Result.success(); + } } diff --git a/src/main/java/com/sa/zentao/entity/ZtStoryExpand.java b/src/main/java/com/sa/zentao/entity/ZtStoryExpand.java new file mode 100644 index 0000000..e581440 --- /dev/null +++ b/src/main/java/com/sa/zentao/entity/ZtStoryExpand.java @@ -0,0 +1,104 @@ +package com.sa.zentao.entity; + +import java.math.BigDecimal; +import java.io.Serializable; +import java.util.Date; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * + *

+ * + * @author gqb + * @since 2026-03-30 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class ZtStoryExpand implements Serializable { + + private static final long serialVersionUID = 1L; + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + private Integer storyId; + + /** + * 单元数量 + */ + private Integer numberUnits; + + /** + * 单元业务复杂度 + */ + private String unitBusinessComplexity; + + /** + * 技术复杂度系数 + */ + private String technicalComplexityCoefficient; + + /** + * AI效率系数 + */ + private String aiEfficiencyCoefficient; + + /** + * 评估工时 + */ + private BigDecimal evaluationTime; + + /** + * 工作量指数 + */ + private String workloadIndex; + + /** + * 需求状态 未开始 进行中(需求开始) 需求验收->已完成 / inProgress 进行中 finished 完成 + */ + private String requirementStatus; + + /** + * 需求完成度 + */ + private String requirementCompletionDegree; + + private Date createTime; + + private Date updateTime; + + private String createUser; + + private String updateUser; + + /** + * 需求名称(关联 zt_story.title,非数据库字段) + */ + @TableField(exist = false) + private String storyTitle; + + /** + * 创建人昵称(关联 zt_user.nickname,非数据库字段) + */ + @TableField(exist = false) + private String createUserNickname; + + /** + * 提交月份,格式 yyyy-MM(非数据库字段,用于进度提交) + */ + @TableField(exist = false) + private String month; + + /** + * 本月完成工时(关联 zt_story_month_workload.evaluation_time,非数据库字段) + */ + @TableField(exist = false) + private BigDecimal monthEvaluationTime; + + +} diff --git a/src/main/java/com/sa/zentao/entity/ZtStoryMonthWorkload.java b/src/main/java/com/sa/zentao/entity/ZtStoryMonthWorkload.java new file mode 100644 index 0000000..c2d25e3 --- /dev/null +++ b/src/main/java/com/sa/zentao/entity/ZtStoryMonthWorkload.java @@ -0,0 +1,56 @@ +package com.sa.zentao.entity; + +import java.math.BigDecimal; +import java.util.Date; +import java.io.Serializable; + +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; +import lombok.EqualsAndHashCode; + +/** + *

+ * + *

+ * + * @author gqb + * @since 2026-03-30 + */ +@Data +@EqualsAndHashCode(callSuper = false) +public class ZtStoryMonthWorkload implements Serializable { + + private static final long serialVersionUID = 1L; + @TableId(value = "id", type = IdType.AUTO) + private Integer id; + + /** + * 月度 + */ + private String time; + + /** + * 完成度 % + */ + private BigDecimal workload; + + private String createUser; + + private Date createTime; + + private Date updateTime; + + private String updateUser; + + private Integer storyId; + + /** + * 评估工时(评估工时 × 完成度比例) + */ + private BigDecimal evaluationTime; + /** + * 评估工时(评估工时 × 完成度比例) + */ + private BigDecimal workloadIndex; +} diff --git a/src/main/java/com/sa/zentao/mapper/ZtStoryExpandMapper.java b/src/main/java/com/sa/zentao/mapper/ZtStoryExpandMapper.java new file mode 100644 index 0000000..b0db850 --- /dev/null +++ b/src/main/java/com/sa/zentao/mapper/ZtStoryExpandMapper.java @@ -0,0 +1,26 @@ +package com.sa.zentao.mapper; + +import com.sa.zentao.entity.ZtStoryExpand; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import org.apache.ibatis.annotations.Param; +import java.util.List; + +/** + *

+ * Mapper 接口 + *

+ * + * @author gqb + * @since 2026-03-30 + */ +public interface ZtStoryExpandMapper extends BaseMapper { + + /** + * 按月份和项目查询,关联 zt_story 获取需求名称 + * @param month 月份,格式:yyyy-MM + * @param projectId 产品项目 id(zt_project.id),为 null 时不过滤 + * @return 需求扩展信息列表(含 storyTitle) + */ + List queryByMonthWithTitle(@Param("month") String month, @Param("projectId") Integer projectId); + +} diff --git a/src/main/java/com/sa/zentao/mapper/ZtStoryMonthWorkloadMapper.java b/src/main/java/com/sa/zentao/mapper/ZtStoryMonthWorkloadMapper.java new file mode 100644 index 0000000..e92a9b4 --- /dev/null +++ b/src/main/java/com/sa/zentao/mapper/ZtStoryMonthWorkloadMapper.java @@ -0,0 +1,16 @@ +package com.sa.zentao.mapper; + +import com.sa.zentao.entity.ZtStoryMonthWorkload; +import com.baomidou.mybatisplus.core.mapper.BaseMapper; + +/** + *

+ * Mapper 接口 + *

+ * + * @author gqb + * @since 2026-03-30 + */ +public interface ZtStoryMonthWorkloadMapper extends BaseMapper { + +} diff --git a/src/main/java/com/sa/zentao/qo/StoryQo.java b/src/main/java/com/sa/zentao/qo/StoryQo.java index 817c37d..4ed6f48 100644 --- a/src/main/java/com/sa/zentao/qo/StoryQo.java +++ b/src/main/java/com/sa/zentao/qo/StoryQo.java @@ -18,6 +18,10 @@ public class StoryQo extends BaseQo { private String assignedTo; private String userName; private String module; + /** + * 多个模块,逗号分割 + */ + private String modules; private String searchVal; private Integer productId; private String openedby; diff --git a/src/main/java/com/sa/zentao/qo/ZtProjectQo.java b/src/main/java/com/sa/zentao/qo/ZtProjectQo.java index 89dc740..1ca53ac 100644 --- a/src/main/java/com/sa/zentao/qo/ZtProjectQo.java +++ b/src/main/java/com/sa/zentao/qo/ZtProjectQo.java @@ -120,4 +120,8 @@ public class ZtProjectQo extends BaseQo { private String account; //1 延期 2不延期 private Integer delayFlag=0 ; + /** + * 验收人 + */ + private String ysUser; } diff --git a/src/main/java/com/sa/zentao/service/IZtStoryExpandService.java b/src/main/java/com/sa/zentao/service/IZtStoryExpandService.java new file mode 100644 index 0000000..59a6f49 --- /dev/null +++ b/src/main/java/com/sa/zentao/service/IZtStoryExpandService.java @@ -0,0 +1,38 @@ +package com.sa.zentao.service; + +import com.sa.zentao.entity.ZtStoryExpand; +import com.baomidou.mybatisplus.extension.service.IService; + +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author gqb + * @since 2026-03-30 + */ +public interface IZtStoryExpandService extends IService { + + /** + * 保存或更新(有数据了更新不新增) + * @param entity 需求扩展信息 + */ + void saveOrUpdateExpand(ZtStoryExpand entity); + + /** + * 按月份和项目查询(不分页,含需求名称) + * @param month 月份,格式:yyyy-MM + * @param projectId 产品项目 id,为 null 时不过滤 + * @return 需求扩展信息列表(含 storyTitle) + */ + List queryByMonth(String month, Integer projectId); + + /** + * 打分接口 + * @param entity 需求扩展信息 + */ + void saveScore(ZtStoryExpand entity); + +} diff --git a/src/main/java/com/sa/zentao/service/IZtStoryMonthWorkloadService.java b/src/main/java/com/sa/zentao/service/IZtStoryMonthWorkloadService.java new file mode 100644 index 0000000..04bfb3a --- /dev/null +++ b/src/main/java/com/sa/zentao/service/IZtStoryMonthWorkloadService.java @@ -0,0 +1,40 @@ +package com.sa.zentao.service; + +import com.sa.zentao.entity.ZtStoryMonthWorkload; +import com.baomidou.mybatisplus.extension.service.IService; +import java.math.BigDecimal; +import java.util.List; + +/** + *

+ * 服务类 + *

+ * + * @author gqb + * @since 2026-03-30 + */ +public interface IZtStoryMonthWorkloadService extends IService { + + /** + * 保存完成度 + * @param entity 月度工作量信息 + */ + void saveCompletionDegree(ZtStoryMonthWorkload entity); + + /** + * 获取所有不重复的月份列表 + * @return 月份列表,格式 yyyy-MM + */ + List listMonths(); + + /** + * 按 storyId + 月份查询单条记录 + */ + ZtStoryMonthWorkload getByStoryIdAndMonth(Integer storyId, String month); + + /** + * 查询某需求在指定月份之前(不含)的历史最高完成度 + */ + BigDecimal getMaxWorkloadBefore(Integer storyId, String month); + +} 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 f7fe31f..00fe526 100644 --- a/src/main/java/com/sa/zentao/service/impl/IZtCountService.java +++ b/src/main/java/com/sa/zentao/service/impl/IZtCountService.java @@ -1139,7 +1139,7 @@ public class IZtCountService { return BigDecimal.ZERO; } List list = this.meetingService.list(new QueryWrapper().lambda().in(ZtMeeting::getProductId, pIds) - .like(ZtMeeting::getUsers, u.getAccount()) + .and(w -> w.like(ZtMeeting::getUsers, u.getAccount()).or().like(ZtMeeting::getUsers, u.getNickname())) .ge(ZtMeeting::getMeetingDate, start).le(ZtMeeting::getMeetingDate, end)); if (CollectionUtils.isEmpty(list)) { return BigDecimal.ZERO; @@ -1257,7 +1257,7 @@ public class IZtCountService { return dto; } List list = this.meetingService.list(new QueryWrapper().lambda().in(ZtMeeting::getProductId, pIds) - .like(ZtMeeting::getUsers, u.getAccount()) + .and(w -> w.like(ZtMeeting::getUsers, u.getAccount()).or().like(ZtMeeting::getUsers, u.getNickname())) .ge(ZtMeeting::getMeetingDate, start).le(ZtMeeting::getMeetingDate, end)); if (CollectionUtils.isEmpty(list)) { return dto; diff --git a/src/main/java/com/sa/zentao/service/impl/ZtKanbanlaneServiceImpl.java b/src/main/java/com/sa/zentao/service/impl/ZtKanbanlaneServiceImpl.java index b08e45a..6fe6514 100644 --- a/src/main/java/com/sa/zentao/service/impl/ZtKanbanlaneServiceImpl.java +++ b/src/main/java/com/sa/zentao/service/impl/ZtKanbanlaneServiceImpl.java @@ -4,11 +4,7 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.sa.zentao.conf.RiskUserThreadLocal; import com.sa.zentao.dao.*; import com.sa.zentao.entity.*; -import com.sa.zentao.enums.ActionStatus; -import com.sa.zentao.enums.ActionType; -import com.sa.zentao.enums.KanbanCellType; -import com.sa.zentao.enums.KanbanColumnType; -import com.sa.zentao.enums.ProjectTypeEnums; +import com.sa.zentao.enums.*; import com.sa.zentao.utils.KanbanStageMapping; import com.sa.zentao.mapper.ZtKanbancolumnMapper; import com.sa.zentao.mapper.ZtKanbanlaneMapper; @@ -383,6 +379,13 @@ public class ZtKanbanlaneServiceImpl extends ServiceImpl o.getType().equals("verified")).collect(Collectors.toList()).get(0); + }else if (StoryStageEnums.productVerified.getValue().equals(st.getStage())){ + ztKanbancolumnDTO = ztKanbancolumnDTOS.stream().filter(o -> o.getType().equals(StoryStageEnums.productVerified.getValue())).collect(Collectors.toList()).get(0); + + }else if ( StoryStageEnums.productWaitVerified.getValue().equals(st.getStage())){ + ztKanbancolumnDTO = ztKanbancolumnDTOS.stream().filter(o -> o.getType().equals(StoryStageEnums.productWaitVerified.getValue())).collect(Collectors.toList()).get(0); + }else{ + ztKanbancolumnDTO = ztKanbancolumnDTOS.stream().filter(o -> o.getType().equals(st.getStage())).collect(Collectors.toList()).get(0); } ZtKanbancell kanbancell = this.kanbancellService.getOne(new QueryWrapper().lambda() .eq(ZtKanbancell::getKanban, id).eq(ZtKanbancell::getColumn, ztKanbancolumnDTO.getId())); diff --git a/src/main/java/com/sa/zentao/service/impl/ZtStoryExpandServiceImpl.java b/src/main/java/com/sa/zentao/service/impl/ZtStoryExpandServiceImpl.java new file mode 100644 index 0000000..40a39cd --- /dev/null +++ b/src/main/java/com/sa/zentao/service/impl/ZtStoryExpandServiceImpl.java @@ -0,0 +1,148 @@ +package com.sa.zentao.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sa.zentao.entity.ZtStoryExpand; +import com.sa.zentao.entity.ZtStoryMonthWorkload; +import com.sa.zentao.mapper.ZtStoryExpandMapper; +import com.sa.zentao.service.IZtStoryExpandService; +import com.sa.zentao.service.IZtStoryMonthWorkloadService; +import com.sa.zentao.utils.DateUtils; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +/** + *

+ * 服务实现类 + *

+ * + * @author gqb + * @since 2026-03-30 + */ +@Service +public class ZtStoryExpandServiceImpl extends ServiceImpl implements IZtStoryExpandService { + + @Autowired + private IZtStoryMonthWorkloadService ztStoryMonthWorkloadService; + @Autowired + private IZtStoryExpandService ztStoryExpandService; + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveOrUpdateExpand(ZtStoryExpand entity) { + if (entity == null || entity.getStoryId() == null) { + return; + } + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("story_id", entity.getStoryId()); + ZtStoryExpand exist = this.getOne(wrapper); + + // 已完成的记录不允许再更新 + if (exist != null && "finished".equals(exist.getRequirementStatus())) { + throw new RuntimeException("该需求已完成,不可再修改"); + } + + if (exist != null) { + entity.setId(exist.getId()); + entity.setUpdateTime(new Date()); + this.updateById(entity); + } else { + // 新增默认 inProgress + if (entity.getRequirementStatus() == null) { + entity.setRequirementStatus("inProgress"); + } + entity.setCreateTime(new Date()); + entity.setUpdateTime(new Date()); + this.save(entity); + } + + // finished 时:强制完成度100,计算剩余工作量写入 workload + if ("finished".equals(entity.getRequirementStatus())) { + entity.setMonth(DateUtils.formatDate(new Date(), "yyyy-MM")); + entity.setRequirementCompletionDegree("100"); + writeWorkload(entity, exist); + } + // inProgress 时:不计算 workload + } + + @Override + public List queryByMonth(String month, Integer projectId) { + return baseMapper.queryByMonthWithTitle(month, projectId); + } + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveScore(ZtStoryExpand entity) { + if (entity.getStoryId() == null) { + return; + } + // 根据完成度设置需求状态 + if (entity.getRequirementCompletionDegree() != null) { + String degree = entity.getRequirementCompletionDegree().trim(); + entity.setRequirementStatus("100".equals(degree) ? "finished" : "inProgress"); + } + // 保存/更新 zt_story_expand + QueryWrapper expandWrapper = new QueryWrapper<>(); + expandWrapper.eq("story_id", entity.getStoryId()); + ZtStoryExpand exist = this.getOne(expandWrapper); + if (exist != null) { + entity.setId(exist.getId()); + entity.setUpdateTime(new Date()); + this.updateById(entity); + } else { + entity.setCreateTime(new Date()); + entity.setUpdateTime(new Date()); + this.save(entity); + } + // 同步写 zt_story_month_workload + if (entity.getRequirementCompletionDegree() != null && entity.getMonth() != null) { + writeWorkload(entity, exist); + } + } + + /** + * 计算增量工作量并写入 zt_story_month_workload + * 增量 = workloadIndex × (本月完成度 - 历史最高) / 100 + */ + private void writeWorkload(ZtStoryExpand entity, ZtStoryExpand exist) { + BigDecimal degree = new BigDecimal(entity.getRequirementCompletionDegree().trim()); + // 取工作量指数(优先用 DB 已有值) + String workloadIndexStr = (exist != null && exist.getWorkloadIndex() != null) + ? exist.getWorkloadIndex() + : entity.getWorkloadIndex(); + BigDecimal baseWorkloadIndex = null; + if (workloadIndexStr != null && !workloadIndexStr.trim().isEmpty()) { + try { + baseWorkloadIndex = new BigDecimal(workloadIndexStr.trim()); + } catch (NumberFormatException ignored) {} + } + // 历史最高完成度 + BigDecimal maxBefore = ztStoryMonthWorkloadService.getMaxWorkloadBefore(entity.getStoryId(), entity.getMonth()); + BigDecimal prevDegree = maxBefore != null ? maxBefore : BigDecimal.ZERO; + ZtStoryMonthWorkload workload = new ZtStoryMonthWorkload(); + workload.setStoryId(entity.getStoryId()); + workload.setWorkload(degree); + workload.setTime(entity.getMonth()); + if (baseWorkloadIndex != null) { + BigDecimal increment = degree.subtract(prevDegree); + //暂时保留,冗余字段 + workload.setEvaluationTime( + increment.compareTo(BigDecimal.ZERO) > 0 + ? baseWorkloadIndex.multiply(increment).divide(new BigDecimal("100"), 2, java.math.RoundingMode.HALF_UP) + : BigDecimal.ZERO + ); + workload.setWorkloadIndex(increment.compareTo(BigDecimal.ZERO) > 0 + ? baseWorkloadIndex.multiply(increment).divide(new BigDecimal("100"), 2, java.math.RoundingMode.HALF_UP) + : BigDecimal.ZERO); + } + ztStoryMonthWorkloadService.saveCompletionDegree(workload); + entity.setRequirementCompletionDegree(degree.toString()); + this.ztStoryExpandService.updateById(entity); + } + +} diff --git a/src/main/java/com/sa/zentao/service/impl/ZtStoryMonthWorkloadServiceImpl.java b/src/main/java/com/sa/zentao/service/impl/ZtStoryMonthWorkloadServiceImpl.java new file mode 100644 index 0000000..c99bb17 --- /dev/null +++ b/src/main/java/com/sa/zentao/service/impl/ZtStoryMonthWorkloadServiceImpl.java @@ -0,0 +1,97 @@ +package com.sa.zentao.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; +import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; +import com.sa.zentao.entity.ZtStoryMonthWorkload; +import com.sa.zentao.mapper.ZtStoryMonthWorkloadMapper; +import com.sa.zentao.service.IZtStoryMonthWorkloadService; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.math.BigDecimal; +import java.util.Date; +import java.util.List; + +/** + *

+ * 服务实现类 + *

+ * + * @author gqb + * @since 2026-03-30 + */ +@Service +public class ZtStoryMonthWorkloadServiceImpl extends ServiceImpl implements IZtStoryMonthWorkloadService { + + @Override + @Transactional(rollbackFor = Exception.class) + public void saveCompletionDegree(ZtStoryMonthWorkload entity) { + if (entity == null || entity.getStoryId() == null || entity.getTime() == null) { + return; + } + // 规则1:完成度不超过100 + if (entity.getWorkload() != null && entity.getWorkload().compareTo(new BigDecimal("100")) > 0) { + throw new RuntimeException("完成度不能超过100%"); + } + // 规则2:本月不能低于历史最高完成度 + BigDecimal maxBefore = getMaxWorkloadBefore(entity.getStoryId(), entity.getTime()); + if (maxBefore != null && entity.getWorkload() != null + && entity.getWorkload().compareTo(maxBefore) < 0) { + throw new RuntimeException("本月完成度(" + entity.getWorkload() + "%)不能低于历史最高(" + maxBefore + "%)"); + } + // 规则3:按 storyId + time 唯一,同月覆盖 + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("story_id", entity.getStoryId()).eq("time", entity.getTime()); + ZtStoryMonthWorkload exist = this.getOne(wrapper); + if (exist != null) { + entity.setId(exist.getId()); + entity.setUpdateTime(new Date()); + this.updateById(entity); + } else { + entity.setCreateTime(new Date()); + entity.setUpdateTime(new Date()); + this.save(entity); + } + } + + @Override + public List listMonths() { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.select("DISTINCT time").isNotNull("time").ne("time", "").orderByDesc("time"); + return this.list(wrapper).stream() + .map(ZtStoryMonthWorkload::getTime) + .collect(java.util.stream.Collectors.toList()); + } + + @Override + public ZtStoryMonthWorkload getByStoryIdAndMonth(Integer storyId, String month) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("story_id", storyId).eq("time", month); + return this.getOne(wrapper); + } + + @Override + public BigDecimal getMaxWorkloadBefore(Integer storyId, String month) { + QueryWrapper wrapper = new QueryWrapper<>(); + wrapper.eq("story_id", storyId) + .lt("time", month) + .orderByDesc("workload") + .last("LIMIT 1"); + ZtStoryMonthWorkload record = this.getOne(wrapper); + return record != null ? record.getWorkload() : null; + } + + private String getLastMonth(String yearMonth) { + String[] parts = yearMonth.split("-"); + int year = Integer.parseInt(parts[0]); + int month = Integer.parseInt(parts[1]); + if (month == 1) { + year--; + month = 12; + } else { + month--; + } + return String.format("%d-%02d", year, month); + } + +} 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 2b4f5a7..47a3427 100644 --- a/src/main/java/com/sa/zentao/service/impl/ZtTaskServiceImpl.java +++ b/src/main/java/com/sa/zentao/service/impl/ZtTaskServiceImpl.java @@ -650,6 +650,9 @@ public class ZtTaskServiceImpl extends ServiceImpl impleme if (ztTask.getDeadline() == null) { throw new BusinessException("当前环境异常请联系管理员"); } + if("test".equals(ztTask.getType())&&"reviewing".equals(ztTask.getStatus())){ + ztTask.setStatus("wait"); + } this.baseMapper.insert(ztTask); if (ztTask.getDeadline() != null && ztTask.getStory() != null && ztTask.getStory() != 0) { ZtStory ztStory = this.storyService.getById(ztTask.getStory()); diff --git a/src/main/resources/mapper/ZtStoryExpandMapper.xml b/src/main/resources/mapper/ZtStoryExpandMapper.xml new file mode 100644 index 0000000..bf8a544 --- /dev/null +++ b/src/main/resources/mapper/ZtStoryExpandMapper.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/ZtStoryMapper.xml b/src/main/resources/mapper/ZtStoryMapper.xml index 00b7abc..6eca629 100644 --- a/src/main/resources/mapper/ZtStoryMapper.xml +++ b/src/main/resources/mapper/ZtStoryMapper.xml @@ -407,7 +407,11 @@ left join zt_project pj on pstory.project = pj.id left join zt_product pt on s.product = pt.id left join zt_projectstory ps on s.id = ps.story + left join zt_user zu on s.ys_user = zu.account WHERE 1=1 + + and zu.nickname like concat('%', #{qo.ysUser}, '%') + and s.pri = #{qo.pri} @@ -417,7 +421,9 @@ and s.title like concat('%', #{qo.title}, '%') - + + and s.nickname like concat('%', #{qo.ysUser}, '%') + and s.ys_user = #{qo.userName} @@ -932,7 +938,7 @@ from zt_story s left join zt_product pt on s.product = pt.id left join zt_storyreview v on s.id = v.story and s.version = v.version - + left join zt_user zu on s.ys_user = zu.account left join zt_projectstory ps on s.id = ps.story @@ -940,7 +946,9 @@ WHERE 1=1 - + + and zu.nickname like concat('%', #{qo.ysUser}, '%') + and s.test_user like concat('%', #{qo.testUser}, '%') diff --git a/src/main/resources/mapper/ZtStoryMonthWorkloadMapper.xml b/src/main/resources/mapper/ZtStoryMonthWorkloadMapper.xml new file mode 100644 index 0000000..eefe69b --- /dev/null +++ b/src/main/resources/mapper/ZtStoryMonthWorkloadMapper.xml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/mapper/ZtStoryUserMapper.xml b/src/main/resources/mapper/ZtStoryUserMapper.xml index aa3acfa..995b650 100644 --- a/src/main/resources/mapper/ZtStoryUserMapper.xml +++ b/src/main/resources/mapper/ZtStoryUserMapper.xml @@ -141,7 +141,9 @@ 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 + pt.name productName + from zt_story_user s + LEFT JOIN zt_product pt on s.product = pt.id WHERE 1=1 and s.product in @@ -228,6 +230,9 @@ and s.module = #{qo.module} + + and FIND_IN_SET(s.module, #{qo.modules}) + and s.product = #{qo.productId}