Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3e864a5770 |
@@ -18,17 +18,6 @@
|
|||||||
<boot.version>3.3.0</boot.version>
|
<boot.version>3.3.0</boot.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
|
||||||
<groupId>org.springframework.boot</groupId>
|
|
||||||
<artifactId>spring-boot-starter-validation</artifactId>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
|
|
||||||
<dependency>
|
|
||||||
<groupId>cn.hutool</groupId>
|
|
||||||
<artifactId>hutool-all</artifactId>
|
|
||||||
<version>4.6.3</version>
|
|
||||||
</dependency>
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.itextpdf</groupId>
|
<groupId>com.itextpdf</groupId>
|
||||||
@@ -99,6 +88,13 @@
|
|||||||
<version>1.5.2</version>
|
<version>1.5.2</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.alibaba</groupId>
|
||||||
|
<artifactId>easyexcel</artifactId>
|
||||||
|
<version>3.0.5</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.tencentcloudapi</groupId>
|
<groupId>com.tencentcloudapi</groupId>
|
||||||
@@ -282,15 +278,15 @@
|
|||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.projectlombok</groupId>
|
<groupId>org.projectlombok</groupId>
|
||||||
<artifactId>lombok</artifactId>
|
<artifactId>lombok</artifactId>
|
||||||
<version>1.18.34</version>
|
<version>1.18.8</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
|
||||||
<!-- <dependency>-->
|
<dependency>
|
||||||
<!-- <groupId>org.apache.commons</groupId>-->
|
<groupId>org.apache.commons</groupId>
|
||||||
<!-- <artifactId>commons-pool2</artifactId>-->
|
<artifactId>commons-pool2</artifactId>
|
||||||
<!-- <version>2.8.1</version>-->
|
<version>2.8.1</version>
|
||||||
<!-- </dependency>-->
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
|
|
||||||
<build>
|
<build>
|
||||||
@@ -300,11 +296,7 @@
|
|||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
<artifactId>spring-boot-maven-plugin</artifactId>
|
<artifactId>spring-boot-maven-plugin</artifactId>
|
||||||
</plugin>
|
</plugin>
|
||||||
<plugin>
|
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
|
||||||
<!-- 不硬编码 skipTests:需要跳过测试时用命令行 mvn -DskipTests ... -->
|
|
||||||
</plugin>
|
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-compiler-plugin</artifactId>
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class CodeGenerator {
|
|||||||
//自己的名字
|
//自己的名字
|
||||||
static String Author = "gqb";
|
static String Author = "gqb";
|
||||||
|
|
||||||
public static String tableName = "zt_story_month_workload";
|
public static String tableName = "car_driving_license";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
|
|||||||
@@ -15,24 +15,26 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
|
|||||||
@RestControllerAdvice
|
@RestControllerAdvice
|
||||||
public class GlobalExceptionHandler {
|
public class GlobalExceptionHandler {
|
||||||
|
|
||||||
@Value("${spring.application.name}")
|
@Value("spring.application.name")
|
||||||
private String application;
|
private String application;
|
||||||
|
|
||||||
@ExceptionHandler(Exception.class)
|
@ExceptionHandler(Exception.class)
|
||||||
public Result bizExceptionHandler(Exception e, HttpServletRequest request) {
|
public Result bizExceptionHandler(Exception e, HttpServletRequest request) {
|
||||||
log.error("系统发生异常!应用: {}, 请求路径: {}, 异常类型: {}, 异常原因: {}",
|
log.error("发生业务异常!原因是: {}", e.getMessage());
|
||||||
application, request.getRequestURI(), e.getClass().getSimpleName(), e.getMessage(), e);
|
log.error(application +"发生了异常 ,异常信息 ,{}",e);
|
||||||
return Result.fail(Code.FAIL);
|
return Result.fail(Code.FAIL);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ExceptionHandler(BusinessException.class)
|
@ExceptionHandler(BusinessException.class)
|
||||||
public Result bizExceptionHandler(BusinessException e, HttpServletRequest request) {
|
public Result bizExceptionHandler(BusinessException e, HttpServletRequest request) {
|
||||||
log.error("业务异常!应用: {}, 请求路径: {}, 异常原因: {}",
|
log.error("发生业务异常!原因是: {}", e.getMessage());
|
||||||
application, request.getRequestURI(), e.getMessage());
|
log.error(application +"发生了异常 ,异常信息 ,{}",e);
|
||||||
if(e.code==null){
|
if(e.code==null){
|
||||||
return Result.fail(e.value);
|
return Result.fail(e.value);
|
||||||
}else{
|
}else{
|
||||||
return Result.fail(e.code);
|
return Result.fail(e.code);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,6 +17,9 @@ public class WebMvcConfig implements WebMvcConfigurer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
public void addResourceHandlers(ResourceHandlerRegistry registry) {
|
||||||
|
for (int i =0;i<200;i++){
|
||||||
|
log.info("win");
|
||||||
|
}
|
||||||
String os = System.getProperty("os.name");
|
String os = System.getProperty("os.name");
|
||||||
if (os.toLowerCase().startsWith("win")) { //如果是Windows系统
|
if (os.toLowerCase().startsWith("win")) { //如果是Windows系统
|
||||||
log.info("win");
|
log.info("win");
|
||||||
|
|||||||
@@ -1,37 +0,0 @@
|
|||||||
package com.sa.zentao.constants;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 导出相关常量
|
|
||||||
*/
|
|
||||||
public class ExportConstants {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 导出最大记录数
|
|
||||||
*/
|
|
||||||
public static final int MAX_EXPORT_SIZE = 1000000;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Excel sheet 名称
|
|
||||||
*/
|
|
||||||
public static final String SHEET_NAME = "sheet1";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* URL 编码空格
|
|
||||||
*/
|
|
||||||
public static final String URL_SPACE_ENCODE = "%20";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 字符串分隔符
|
|
||||||
*/
|
|
||||||
public static final String STRING_SEPARATOR = ":";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ID 分隔符
|
|
||||||
*/
|
|
||||||
public static final String ID_SEPARATOR = ",";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 逗号替换为空字符串
|
|
||||||
*/
|
|
||||||
public static final String COMMA_REPLACE = "";
|
|
||||||
}
|
|
||||||
@@ -19,7 +19,6 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import org.apache.poi.ss.usermodel.*;
|
import org.apache.poi.ss.usermodel.*;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
|
|
||||||
@@ -107,7 +106,6 @@ public class CommonsController {
|
|||||||
f.setTitle(title);
|
f.setTitle(title);
|
||||||
f.setSize(bytes.length);
|
f.setSize(bytes.length);
|
||||||
f.setPathname(baseUrl+"/zentao/img/"+fName);
|
f.setPathname(baseUrl+"/zentao/img/"+fName);
|
||||||
f.setUrl("/zentao/img/"+fName); // url 存相对路径:经前端源/代理可达,避免绝对 baseUrl 在本地环境不可达(与 uploadBind 一致)
|
|
||||||
f.setObjectid(file.getObjectId());
|
f.setObjectid(file.getObjectId());
|
||||||
fileService.save(f);
|
fileService.save(f);
|
||||||
|
|
||||||
@@ -117,77 +115,6 @@ public class CommonsController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 上传+绑定一步完成(AI 无表单场景):写磁盘+插 zt_file(含 objectType/objectID/url),
|
|
||||||
* 并按 objectType 刷新主表访问链接字段(zt_story 7 url 列+code_review_status / zt_meeting.url)
|
|
||||||
* @param file 入参:file 文件、objectType 类型、objectId 主表id、title 可选、reviewResult 可选(aiCodeReview 用)
|
|
||||||
* @return zt_file 记录(含 id 与 url)
|
|
||||||
*/
|
|
||||||
@PostMapping("/uploadBind")
|
|
||||||
@Transactional
|
|
||||||
public Result<ZtFile> uploadBind(UploadDTO file){
|
|
||||||
//上传必须登录(AI 通道带 ai token;UI 带用户 token),文件/动态创建人取 token 身份(CHG-061)
|
|
||||||
if (RiskUserThreadLocal.get() == null || StringUtils.isEmpty(RiskUserThreadLocal.get().getName())) {
|
|
||||||
return Result.fail("请登录(上传需携带有效token)");
|
|
||||||
}
|
|
||||||
if (file.getFile().isEmpty()) {
|
|
||||||
return Result.fail(Code.FAIL);
|
|
||||||
}
|
|
||||||
if (StringUtils.isEmpty(file.getObjectType()) || file.getObjectId() == null) {
|
|
||||||
return Result.fail("objectType/objectId不能为空");
|
|
||||||
}
|
|
||||||
// CHG-077:story 一律归一为 storyPrd(uploadBind 仅 AI 通道使用;UI 手动附件走两段式上传不进此接口)
|
|
||||||
file.normalizeObjectTypeForBind();
|
|
||||||
|
|
||||||
try {
|
|
||||||
// 获取文件名
|
|
||||||
String fName = file.getFile().getOriginalFilename();
|
|
||||||
String title=StringUtils.isEmpty(file.getTitle())?fName:file.getTitle();
|
|
||||||
fName=fName.substring(fName.lastIndexOf("."),fName.length());
|
|
||||||
String extension=fName.toString();
|
|
||||||
fName= DateUtils.formatDate(new Date(),"yyyyMMddHHmmss")+UUID.randomUUID().toString().replaceAll("-","")+fName;
|
|
||||||
// 获取文件的字节
|
|
||||||
byte[] bytes = file.getFile().getBytes();
|
|
||||||
String p ="";
|
|
||||||
String os = System.getProperty("os.name");
|
|
||||||
if (os.toLowerCase().startsWith("win")) { //如果是Windows系统
|
|
||||||
log.info("win");
|
|
||||||
os=windowsFilePath +"\\";
|
|
||||||
}else{ //linux和mac系统
|
|
||||||
log.info("linux");
|
|
||||||
os=linuxFilePath+"/";
|
|
||||||
}
|
|
||||||
|
|
||||||
String url = "/zentao/img/" + fName; // 相对路径:经前端源/代理可达,规避跨域(baseUrl 绝对地址浏览器未必可达)
|
|
||||||
// 先校验 objectType/objectId 并刷新主表字段,非法则抛异常整体回滚不落盘
|
|
||||||
fileService.refreshOwnerUrl(file.getObjectType(), file.getObjectId(), url, file.getReviewResult());
|
|
||||||
|
|
||||||
// 使用文件名在服务器端创建文件
|
|
||||||
java.nio.file.Path path = java.nio.file.Paths.get(os + fName);
|
|
||||||
|
|
||||||
// 写入文件到服务器的指定目录
|
|
||||||
java.nio.file.Files.write(path, bytes);
|
|
||||||
ZtFile f=new ZtFile();
|
|
||||||
f.setAddedby(RiskUserThreadLocal.get()==null?"ai":RiskUserThreadLocal.get().getName());
|
|
||||||
f.setAddeddate(new Date());
|
|
||||||
f.setDeleted("0");
|
|
||||||
f.setExtension(extension);
|
|
||||||
f.setTitle(title);
|
|
||||||
f.setSize(bytes.length);
|
|
||||||
f.setPathname(url);
|
|
||||||
f.setUrl(url);
|
|
||||||
f.setObjecttype(file.getObjectType());
|
|
||||||
f.setObjectid(file.getObjectId());
|
|
||||||
fileService.save(f);
|
|
||||||
|
|
||||||
return Result.success(f);
|
|
||||||
} catch (BusinessException e) {
|
|
||||||
throw e;
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new BusinessException("文件上传失败");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@PostMapping("/copyFile")
|
@PostMapping("/copyFile")
|
||||||
public Result copyFile( UploadDTO file){
|
public Result copyFile( UploadDTO file){
|
||||||
String files = file.getFiles();
|
String files = file.getFiles();
|
||||||
@@ -263,11 +190,9 @@ public class CommonsController {
|
|||||||
|
|
||||||
String substring = pathname.substring(pathname.lastIndexOf("/")+1, pathname.length());
|
String substring = pathname.substring(pathname.lastIndexOf("/")+1, pathname.length());
|
||||||
try {
|
try {
|
||||||
// 与上传保持一致的 OS 路径选择(原硬编码 linuxFilePath 导致 Windows 本地下载全部失败)
|
File f = new File(linuxFilePath+"/"+substring);
|
||||||
String osName = System.getProperty("os.name");
|
// File f = new File("D:\\1.bmp");
|
||||||
String basePath = osName.toLowerCase().startsWith("win") ? windowsFilePath : linuxFilePath;
|
InputStream fis = new BufferedInputStream(new FileInputStream(linuxFilePath+"/"+substring));
|
||||||
File f = new File(basePath + File.separator + substring);
|
|
||||||
InputStream fis = new BufferedInputStream(new FileInputStream(f));
|
|
||||||
// InputStream fis = new BufferedInputStream(new FileInputStream("D:\\1.bmp"));
|
// InputStream fis = new BufferedInputStream(new FileInputStream("D:\\1.bmp"));
|
||||||
byte[] buffer = new byte[fis.available()];
|
byte[] buffer = new byte[fis.available()];
|
||||||
|
|
||||||
|
|||||||
@@ -4,22 +4,18 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|||||||
import com.sa.zentao.dao.*;
|
import com.sa.zentao.dao.*;
|
||||||
import com.sa.zentao.entity.ZtProject;
|
import com.sa.zentao.entity.ZtProject;
|
||||||
import com.sa.zentao.qo.ZtCountQo;
|
import com.sa.zentao.qo.ZtCountQo;
|
||||||
import com.sa.zentao.qo.ZtDocCheckQo;
|
|
||||||
import com.sa.zentao.qo.ZtProjectQo;
|
import com.sa.zentao.qo.ZtProjectQo;
|
||||||
import com.sa.zentao.service.IZtDocCheckService;
|
|
||||||
import com.sa.zentao.service.IZtProjectService;
|
import com.sa.zentao.service.IZtProjectService;
|
||||||
import com.sa.zentao.service.impl.IZtCountService;
|
import com.sa.zentao.service.impl.IZtCountService;
|
||||||
import com.sa.zentao.utils.DateUtils;
|
import com.sa.zentao.utils.DateUtils;
|
||||||
import jakarta.servlet.ServletOutputStream;
|
import jakarta.servlet.ServletOutputStream;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.SneakyThrows;
|
import lombok.SneakyThrows;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
import org.springframework.web.bind.annotation.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
@@ -39,9 +35,6 @@ public class ZtCountController {
|
|||||||
@Autowired
|
@Autowired
|
||||||
private IZtProjectService projectService;
|
private IZtProjectService projectService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtDocCheckService docCheckService;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 开发看到的
|
* 开发看到的
|
||||||
@@ -212,48 +205,4 @@ public class ZtCountController {
|
|||||||
return Result.success(countService.monthScopeByProgram(qo));
|
return Result.success(countService.monthScopeByProgram(qo));
|
||||||
}
|
}
|
||||||
|
|
||||||
//工作量指标完成率(FR-012 三期):Σ(当月 workload 增量) ÷ (团队可用工作天数×5),测试人员不计入产出方
|
|
||||||
@RequestMapping(value = "/workloadRate", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result workloadRate(@RequestParam(required = false) String month, @RequestParam Integer projectId){
|
|
||||||
return Result.success(countService.workloadRate(month, projectId));
|
|
||||||
}
|
|
||||||
|
|
||||||
//手动触发文档齐备快照(FR-014 三期),月末 28 日 02:00 另有定时预生成
|
|
||||||
@RequestMapping(value = "/generateDocCheck", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result generateDocCheck(@RequestBody ZtDocCheckQo qo){
|
|
||||||
String month = StringUtils.isEmpty(qo.getMonth()) ? DateUtils.formatDate(new Date(), "yyyy-MM") : qo.getMonth();
|
|
||||||
return Result.success(docCheckService.generateMonthSnapshot(month));
|
|
||||||
}
|
|
||||||
|
|
||||||
//文档齐备申诉(FR-014):状态流 pending → appealing
|
|
||||||
@RequestMapping(value = "/docCheckAppeal", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result docCheckAppeal(@RequestBody ZtDocCheckQo qo){
|
|
||||||
docCheckService.appeal(qo.getStoryId(), qo.getMonth(), qo.getDocType(), qo.getReason());
|
|
||||||
return Result.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
//文档齐备申诉复核(FR-014):appealing → resolved,复核通过回滚扣分
|
|
||||||
@RequestMapping(value = "/docCheckAppealResolve", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result docCheckAppealResolve(@RequestBody ZtDocCheckQo qo){
|
|
||||||
docCheckService.resolveAppeal(qo.getStoryId(), qo.getMonth(), qo.getDocType(),
|
|
||||||
qo.getPass() != null && qo.getPass(), qo.getRemark());
|
|
||||||
return Result.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
//文档齐备矩阵页数据(FR-014 三期):按需求聚合五类判定/扣分/状态
|
|
||||||
@RequestMapping(value = "/docCheckMatrix", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result docCheckMatrix(String month){
|
|
||||||
if (StringUtils.isEmpty(month)) {
|
|
||||||
month = DateUtils.formatDate(new Date(), "yyyy-MM");
|
|
||||||
}
|
|
||||||
return Result.success(docCheckService.listMatrix(month));
|
|
||||||
}
|
|
||||||
|
|
||||||
//月度确认(FR-014 三期):项目经理确认后该月快照锁定
|
|
||||||
@RequestMapping(value = "/docCheckConfirm", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result docCheckConfirm(@RequestBody ZtDocCheckQo qo){
|
|
||||||
docCheckService.confirmMonth(qo.getMonth());
|
|
||||||
return Result.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,176 +0,0 @@
|
|||||||
package com.sa.zentao.controller;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.sa.zentao.conf.RiskUserThreadLocal;
|
|
||||||
import com.sa.zentao.dao.BusinessException;
|
|
||||||
import com.sa.zentao.dao.Result;
|
|
||||||
import com.sa.zentao.entity.ZtPerfConfig;
|
|
||||||
import com.sa.zentao.qo.ZtPerfQo;
|
|
||||||
import com.sa.zentao.service.IZtPerfConfigService;
|
|
||||||
import com.sa.zentao.service.IZtPerfReportService;
|
|
||||||
import com.sa.zentao.service.perf.PerfMonthScope;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
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.RequestParam;
|
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* 绩效报表(三期批2:报表引擎+接口) 前端控制器
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/zt-perf")
|
|
||||||
public class ZtPerfController {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtPerfReportService perfReportService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtPerfConfigService perfConfigService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 按岗位 tab 报表数据(指标/权重/得分/数据来源/明细)
|
|
||||||
*/
|
|
||||||
@RequestMapping(value = "/report", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result<PerfMonthScope> report(@RequestParam String month, @RequestParam String role, @RequestParam(required = false) String account) {
|
|
||||||
return Result.success(perfReportService.report(month, role, account));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量生成/重算月度得分写 zt_month_score.scopeJson
|
|
||||||
*/
|
|
||||||
@RequestMapping(value = "/generateMonthScore", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result generateMonthScore(@RequestBody ZtPerfQo qo) {
|
|
||||||
perfReportService.generateMonthScore(qo.getMonth(), qo.getRole(), qo.getAccount());
|
|
||||||
return Result.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 人工评分合并写 scopeJson
|
|
||||||
*/
|
|
||||||
@RequestMapping(value = "/manualScore", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result manualScore(@RequestBody ZtPerfQo qo) {
|
|
||||||
perfReportService.manualScore(qo);
|
|
||||||
return Result.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 申诉登记(写 scopeJson.appeals)
|
|
||||||
*/
|
|
||||||
@RequestMapping(value = "/appeal", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result appeal(@RequestBody ZtPerfQo qo) {
|
|
||||||
perfReportService.appeal(qo);
|
|
||||||
return Result.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 申诉复核:upheld 维持 / overturned 撤销回滚
|
|
||||||
*/
|
|
||||||
@RequestMapping(value = "/appealReview", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result appealReview(@RequestBody ZtPerfQo qo) {
|
|
||||||
perfReportService.appealReview(qo);
|
|
||||||
return Result.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绩效规则配置列表(可按岗位过滤)
|
|
||||||
*/
|
|
||||||
@RequestMapping(value = "/config/list", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result<List<ZtPerfConfig>> configList(@RequestParam(required = false) String role) {
|
|
||||||
QueryWrapper<ZtPerfConfig> wrapper = new QueryWrapper<>();
|
|
||||||
if (StringUtils.isNotBlank(role)) {
|
|
||||||
wrapper.eq("role", role);
|
|
||||||
}
|
|
||||||
wrapper.orderByAsc("role", "id");
|
|
||||||
return Result.success(perfConfigService.list(wrapper));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 新增配置(仅 admin)
|
|
||||||
*/
|
|
||||||
@RequestMapping(value = "/config/add", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result configAdd(@RequestBody ZtPerfQo qo) {
|
|
||||||
checkAdmin();
|
|
||||||
ZtPerfConfig config = buildConfig(qo, new ZtPerfConfig());
|
|
||||||
Date now = new Date();
|
|
||||||
String operator = RiskUserThreadLocal.get().getName();
|
|
||||||
config.setCreateTime(now);
|
|
||||||
config.setCreateUser(operator);
|
|
||||||
config.setUpdateTime(now);
|
|
||||||
config.setUpdateUser(operator);
|
|
||||||
perfConfigService.save(config);
|
|
||||||
return Result.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 修改配置(仅 admin)
|
|
||||||
*/
|
|
||||||
@RequestMapping(value = "/config/updateById", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result configUpdate(@RequestBody ZtPerfQo qo) {
|
|
||||||
checkAdmin();
|
|
||||||
if (StringUtils.isBlank(qo.getId())) {
|
|
||||||
throw new BusinessException("id 不能为空");
|
|
||||||
}
|
|
||||||
ZtPerfConfig config = perfConfigService.getById(Integer.valueOf(qo.getId()));
|
|
||||||
if (config == null) {
|
|
||||||
throw new BusinessException("配置不存在:" + qo.getId());
|
|
||||||
}
|
|
||||||
buildConfig(qo, config);
|
|
||||||
config.setUpdateTime(new Date());
|
|
||||||
config.setUpdateUser(RiskUserThreadLocal.get().getName());
|
|
||||||
perfConfigService.updateById(config);
|
|
||||||
return Result.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 删除配置(仅 admin)
|
|
||||||
*/
|
|
||||||
@RequestMapping(value = "/config/delete", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result configDelete(@RequestBody ZtPerfQo qo) {
|
|
||||||
checkAdmin();
|
|
||||||
if (StringUtils.isBlank(qo.getId())) {
|
|
||||||
throw new BusinessException("id 不能为空");
|
|
||||||
}
|
|
||||||
perfConfigService.removeById(Integer.valueOf(qo.getId()));
|
|
||||||
return Result.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
private ZtPerfConfig buildConfig(ZtPerfQo qo, ZtPerfConfig config) {
|
|
||||||
if (StringUtils.isNotBlank(qo.getRole())) {
|
|
||||||
config.setRole(qo.getRole());
|
|
||||||
}
|
|
||||||
if (StringUtils.isNotBlank(qo.getItemKey())) {
|
|
||||||
config.setItemKey(qo.getItemKey());
|
|
||||||
}
|
|
||||||
if (StringUtils.isNotBlank(qo.getItemName())) {
|
|
||||||
config.setItemName(qo.getItemName());
|
|
||||||
}
|
|
||||||
if (qo.getWeight() != null) {
|
|
||||||
config.setWeight(BigDecimal.valueOf(qo.getWeight()));
|
|
||||||
}
|
|
||||||
if (StringUtils.isNotBlank(qo.getRuleJson())) {
|
|
||||||
config.setRuleJson(qo.getRuleJson());
|
|
||||||
}
|
|
||||||
if (qo.getEnabled() != null) {
|
|
||||||
config.setEnabled(qo.getEnabled());
|
|
||||||
}
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkAdmin() {
|
|
||||||
if (!"admin".equals(RiskUserThreadLocal.get().getName())) {
|
|
||||||
throw new BusinessException("仅管理员可维护绩效配置");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -160,9 +160,4 @@ public class ZtProductController {
|
|||||||
public Result projectTeamById(@RequestBody ZtProjectQo qo){
|
public Result projectTeamById(@RequestBody ZtProjectQo qo){
|
||||||
return Result.success(this.ztProductService.productTeamByPid(qo));
|
return Result.success(this.ztProductService.productTeamByPid(qo));
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(value = "/getExecutionByProductId", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result getExecutionByProductId(@RequestBody ZtProjectQo qo){
|
|
||||||
return Result.success(ztProductService.getExecutionByProductId(qo));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,18 +8,14 @@ import com.alibaba.excel.metadata.data.WriteCellData;
|
|||||||
import com.alibaba.excel.write.metadata.WriteSheet;
|
import com.alibaba.excel.write.metadata.WriteSheet;
|
||||||
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
|
||||||
import com.github.pagehelper.PageInfo;
|
import com.github.pagehelper.PageInfo;
|
||||||
import com.sa.zentao.constants.ExportConstants;
|
|
||||||
import com.sa.zentao.dao.*;
|
import com.sa.zentao.dao.*;
|
||||||
import com.sa.zentao.entity.ZtProject;
|
import com.sa.zentao.entity.ZtProject;
|
||||||
import com.sa.zentao.entity.ZtProjectproduct;
|
import com.sa.zentao.entity.ZtProjectproduct;
|
||||||
import com.sa.zentao.entity.ZtStoryFeedback;
|
import com.sa.zentao.entity.ZtStoryFeedback;
|
||||||
import com.sa.zentao.entity.ZtStoryUser;
|
|
||||||
import com.sa.zentao.enums.StoryStageEnums;
|
import com.sa.zentao.enums.StoryStageEnums;
|
||||||
import com.sa.zentao.enums.StoryStatusEnums;
|
import com.sa.zentao.enums.StoryStatusEnums;
|
||||||
import com.sa.zentao.enums.UserStoryEnums;
|
import com.sa.zentao.enums.UserStoryEnums;
|
||||||
import com.sa.zentao.mapper.ZtProjectMapper;
|
|
||||||
import com.sa.zentao.qo.SearchQo;
|
import com.sa.zentao.qo.SearchQo;
|
||||||
import com.sa.zentao.qo.StoryQo;
|
import com.sa.zentao.qo.StoryQo;
|
||||||
import com.sa.zentao.qo.ZtProjectQo;
|
import com.sa.zentao.qo.ZtProjectQo;
|
||||||
@@ -30,7 +26,6 @@ import org.apache.commons.lang3.StringUtils;
|
|||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.validation.annotation.Validated;
|
|
||||||
import org.springframework.web.bind.annotation.RequestBody;
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
import org.springframework.web.bind.annotation.RequestMapping;
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
|
||||||
@@ -65,8 +60,6 @@ public class ZtStoryController {
|
|||||||
|
|
||||||
@Value("${file.backUrl}")
|
@Value("${file.backUrl}")
|
||||||
private String url;
|
private String url;
|
||||||
@Autowired
|
|
||||||
private ZtProjectMapper ztProjectMapper;
|
|
||||||
|
|
||||||
@RequestMapping(value = "/pageList", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
@RequestMapping(value = "/pageList", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
||||||
public Result<ZtStoryDTO> storyPageList(@RequestBody ZtProjectQo qo){
|
public Result<ZtStoryDTO> storyPageList(@RequestBody ZtProjectQo qo){
|
||||||
@@ -75,7 +68,7 @@ public class ZtStoryController {
|
|||||||
|
|
||||||
@RequestMapping(value = "/storyExport", method = RequestMethod.POST)
|
@RequestMapping(value = "/storyExport", method = RequestMethod.POST)
|
||||||
public void storyExport(@RequestBody ZtProjectQo qo, jakarta.servlet.ServletResponse response){
|
public void storyExport(@RequestBody ZtProjectQo qo, jakarta.servlet.ServletResponse response){
|
||||||
qo.setPageSize(ExportConstants.MAX_EXPORT_SIZE);
|
qo.setPageSize(1000000);
|
||||||
PageInfo<ZtStoryDTO> p = ztStoryService.pageList(qo);
|
PageInfo<ZtStoryDTO> p = ztStoryService.pageList(qo);
|
||||||
List<ZtStoryDTO> list = p.getList();
|
List<ZtStoryDTO> list = p.getList();
|
||||||
List<Integer> feedbackIds = list.stream().filter(o -> o.getFeedback() != null && o.getFeedback() != 0).map(o -> o.getFeedback()).collect(Collectors.toList());
|
List<Integer> feedbackIds = list.stream().filter(o -> o.getFeedback() != null && o.getFeedback() != 0).map(o -> o.getFeedback()).collect(Collectors.toList());
|
||||||
@@ -86,12 +79,12 @@ public class ZtStoryController {
|
|||||||
|
|
||||||
ZtStoryFeedback ztStoryFeedback = feedbackMap.get(ztStoryUserDTO.getFeedback());
|
ZtStoryFeedback ztStoryFeedback = feedbackMap.get(ztStoryUserDTO.getFeedback());
|
||||||
if (ztStoryFeedback != null) {
|
if (ztStoryFeedback != null) {
|
||||||
String reName=ztStoryFeedback.getId()+ExportConstants.STRING_SEPARATOR+ztStoryFeedback.getSpec();
|
String reName=ztStoryFeedback.getId()+":"+ztStoryFeedback.getSpec();
|
||||||
String reUrl = url+"/#/product-feedback-info/"+ztStoryFeedback.getId();
|
String reUrl = url+"/#/product-feedback-info/"+ztStoryFeedback.getId();
|
||||||
WriteCellData<String> hyperlink = new WriteCellData<>(reName);
|
WriteCellData<String> hyperlink = new WriteCellData<>(reName);
|
||||||
HyperlinkData hyperlinkData = new HyperlinkData();
|
HyperlinkData hyperlinkData = new HyperlinkData();
|
||||||
try {
|
try {
|
||||||
hyperlinkData.setAddress(reUrl.replace(" ", ExportConstants.URL_SPACE_ENCODE));
|
hyperlinkData.setAddress(reUrl.replace(" ", "%20"));
|
||||||
hyperlinkData.setHyperlinkType(HyperlinkData.HyperlinkType.URL);
|
hyperlinkData.setHyperlinkType(HyperlinkData.HyperlinkType.URL);
|
||||||
hyperlink.setHyperlinkData(hyperlinkData);
|
hyperlink.setHyperlinkData(hyperlinkData);
|
||||||
ztStoryUserDTO.setFeedbackSpecUrl(hyperlink );
|
ztStoryUserDTO.setFeedbackSpecUrl(hyperlink );
|
||||||
@@ -100,12 +93,12 @@ public class ZtStoryController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(ztStoryUserDTO.getParent()!=null&&ztStoryUserDTO.getParent()!=0){
|
if(ztStoryUserDTO.getParent()!=null&&ztStoryUserDTO.getParent()!=0){
|
||||||
String reName = ztStoryUserDTO.getParent()+ExportConstants.STRING_SEPARATOR+ztStoryUserDTO.getParentName();
|
String reName = ztStoryUserDTO.getParent()+":"+ztStoryUserDTO.getParentName();
|
||||||
String reUrl = url+"/#/product-story-info/"+ztStoryUserDTO.getParent();
|
String reUrl = url+"/#/product-story-info/"+ztStoryUserDTO.getParent();
|
||||||
WriteCellData<String> hyperlink = new WriteCellData<>(reName);
|
WriteCellData<String> hyperlink = new WriteCellData<>(reName);
|
||||||
HyperlinkData hyperlinkData = new HyperlinkData();
|
HyperlinkData hyperlinkData = new HyperlinkData();
|
||||||
try {
|
try {
|
||||||
hyperlinkData.setAddress(reUrl.replace(" ", ExportConstants.URL_SPACE_ENCODE));
|
hyperlinkData.setAddress(reUrl.replace(" ", "%20"));
|
||||||
hyperlinkData.setHyperlinkType(HyperlinkData.HyperlinkType.URL);
|
hyperlinkData.setHyperlinkType(HyperlinkData.HyperlinkType.URL);
|
||||||
hyperlink.setHyperlinkData(hyperlinkData);
|
hyperlink.setHyperlinkData(hyperlinkData);
|
||||||
ztStoryUserDTO.setParentNameUrl(hyperlink);
|
ztStoryUserDTO.setParentNameUrl(hyperlink);
|
||||||
@@ -114,12 +107,12 @@ public class ZtStoryController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if(ztStoryUserDTO.getUserStory()!=null&&ztStoryUserDTO.getUserStory()!=0){
|
if(ztStoryUserDTO.getUserStory()!=null&&ztStoryUserDTO.getUserStory()!=0){
|
||||||
String reName=ztStoryUserDTO.getUserStory()+ExportConstants.STRING_SEPARATOR+ztStoryUserDTO.getUserStoryName();
|
String reName=ztStoryUserDTO.getUserStory()+":"+ztStoryUserDTO.getUserStoryName();
|
||||||
String reUrl = url+"/#/product-user-story-info/"+ztStoryUserDTO.getUserStory();
|
String reUrl = url+"/#/product-user-story-info/"+ztStoryUserDTO.getUserStory();
|
||||||
WriteCellData<String> hyperlink = new WriteCellData<>(reName);
|
WriteCellData<String> hyperlink = new WriteCellData<>(reName);
|
||||||
HyperlinkData hyperlinkData = new HyperlinkData();
|
HyperlinkData hyperlinkData = new HyperlinkData();
|
||||||
try {
|
try {
|
||||||
hyperlinkData.setAddress(reUrl.replace(" ", ExportConstants.URL_SPACE_ENCODE));
|
hyperlinkData.setAddress(reUrl.replace(" ", "%20"));
|
||||||
hyperlinkData.setHyperlinkType(HyperlinkData.HyperlinkType.URL);
|
hyperlinkData.setHyperlinkType(HyperlinkData.HyperlinkType.URL);
|
||||||
hyperlink.setHyperlinkData(hyperlinkData);
|
hyperlink.setHyperlinkData(hyperlinkData);
|
||||||
ztStoryUserDTO.setUserStoryNameUrl(hyperlink);
|
ztStoryUserDTO.setUserStoryNameUrl(hyperlink);
|
||||||
@@ -127,12 +120,12 @@ public class ZtStoryController {
|
|||||||
ztStoryUserDTO.setUserStoryNameUrl(null);
|
ztStoryUserDTO.setUserStoryNameUrl(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
String reName=ztStoryUserDTO.getId()+ExportConstants.STRING_SEPARATOR+ztStoryUserDTO.getTitle();
|
String reName=ztStoryUserDTO.getId()+":"+ztStoryUserDTO.getTitle();
|
||||||
String reUrl = url+"/#/product-story-info/"+ztStoryUserDTO.getId();
|
String reUrl = url+"/#/product-story-info/"+ztStoryUserDTO.getId();
|
||||||
WriteCellData<String> hyperlink = new WriteCellData<>(reName);
|
WriteCellData<String> hyperlink = new WriteCellData<>(reName);
|
||||||
HyperlinkData hyperlinkData = new HyperlinkData();
|
HyperlinkData hyperlinkData = new HyperlinkData();
|
||||||
try {
|
try {
|
||||||
hyperlinkData.setAddress(reUrl.replace(" ", ExportConstants.URL_SPACE_ENCODE));
|
hyperlinkData.setAddress(reUrl.replace(" ", "%20"));
|
||||||
hyperlinkData.setHyperlinkType(HyperlinkData.HyperlinkType.URL);
|
hyperlinkData.setHyperlinkType(HyperlinkData.HyperlinkType.URL);
|
||||||
hyperlink.setHyperlinkData(hyperlinkData);
|
hyperlink.setHyperlinkData(hyperlinkData);
|
||||||
ztStoryUserDTO.setStoryUrl(hyperlink);
|
ztStoryUserDTO.setStoryUrl(hyperlink);
|
||||||
@@ -141,7 +134,7 @@ public class ZtStoryController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if(!StringUtils.isEmpty(ztStoryUserDTO.getReviewedbyName())){
|
if(!StringUtils.isEmpty(ztStoryUserDTO.getReviewedbyName())){
|
||||||
ztStoryUserDTO.setReviewedbyName(ztStoryUserDTO.getReviewedbyName().replaceAll(ExportConstants.ID_SEPARATOR, ExportConstants.COMMA_REPLACE));
|
ztStoryUserDTO.setReviewedbyName(ztStoryUserDTO.getReviewedbyName().replaceAll(",",""));
|
||||||
}
|
}
|
||||||
ztStoryUserDTO.setStatus(StoryStatusEnums.transfer(ztStoryUserDTO.getStatus())==null?null:StoryStatusEnums.transfer(ztStoryUserDTO.getStatus()).getDesc());
|
ztStoryUserDTO.setStatus(StoryStatusEnums.transfer(ztStoryUserDTO.getStatus())==null?null:StoryStatusEnums.transfer(ztStoryUserDTO.getStatus()).getDesc());
|
||||||
ztStoryUserDTO.setStage(StoryStageEnums.transfer(ztStoryUserDTO.getStage())==null?null:StoryStageEnums.transfer(ztStoryUserDTO.getStage()).getDesc());
|
ztStoryUserDTO.setStage(StoryStageEnums.transfer(ztStoryUserDTO.getStage())==null?null:StoryStageEnums.transfer(ztStoryUserDTO.getStage()).getDesc());
|
||||||
@@ -149,10 +142,10 @@ public class ZtStoryController {
|
|||||||
ztStoryUserDTO.setYsFlagName(ztStoryUserDTO.getYsFlag()==1?"通过":"不通过");
|
ztStoryUserDTO.setYsFlagName(ztStoryUserDTO.getYsFlag()==1?"通过":"不通过");
|
||||||
}
|
}
|
||||||
if(!StringUtils.isEmpty(ztStoryUserDTO.getUserStoryName())){
|
if(!StringUtils.isEmpty(ztStoryUserDTO.getUserStoryName())){
|
||||||
ztStoryUserDTO.setUserStoryName(ztStoryUserDTO.getUserStoryId()+ExportConstants.STRING_SEPARATOR+ztStoryUserDTO.getUserStoryName());
|
ztStoryUserDTO.setUserStoryName(ztStoryUserDTO.getUserStoryId()+":"+ztStoryUserDTO.getUserStoryName());
|
||||||
}
|
}
|
||||||
if(ztStoryUserDTO.getParent()!=null&&ztStoryUserDTO.getParent()!=0){
|
if(ztStoryUserDTO.getParent()!=null&&ztStoryUserDTO.getParent()!=0){
|
||||||
ztStoryUserDTO.setParentName(ztStoryUserDTO.getParent()+ExportConstants.STRING_SEPARATOR+ztStoryUserDTO.getParentName());
|
ztStoryUserDTO.setParentName(ztStoryUserDTO.getParent()+":"+ztStoryUserDTO.getParentName());
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -163,7 +156,7 @@ public class ZtStoryController {
|
|||||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||||
.needHead(Boolean.TRUE).build();
|
.needHead(Boolean.TRUE).build();
|
||||||
|
|
||||||
WriteSheet writeSheet = EasyExcel.writerSheet(0, ExportConstants.SHEET_NAME)
|
WriteSheet writeSheet = EasyExcel.writerSheet(0, "sheet1")
|
||||||
.head(ZtStoryDTO.class)
|
.head(ZtStoryDTO.class)
|
||||||
.needHead(Boolean.TRUE).build();
|
.needHead(Boolean.TRUE).build();
|
||||||
|
|
||||||
@@ -182,47 +175,6 @@ public class ZtStoryController {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(value = "/projectStoryExport", method = RequestMethod.POST)
|
|
||||||
public void projectStoryExport(@RequestBody ZtProjectQo qo, jakarta.servlet.ServletResponse response){
|
|
||||||
qo.setPageSize(ExportConstants.MAX_EXPORT_SIZE);
|
|
||||||
PageInfo<ZtStoryDTO> p = ztStoryService.projectStoryPageList(qo);
|
|
||||||
List<ZtStoryDTO> list = p.getList();
|
|
||||||
|
|
||||||
List<ProjectStoryExportDTO> exportList = new java.util.ArrayList<>();
|
|
||||||
for (ZtStoryDTO dto : list) {
|
|
||||||
ProjectStoryExportDTO exportDTO = new ProjectStoryExportDTO();
|
|
||||||
exportDTO.setId(dto.getId());
|
|
||||||
exportDTO.setTitle(dto.getTitle());
|
|
||||||
exportDTO.setPri(dto.getPri());
|
|
||||||
exportDTO.setStatus(StoryStatusEnums.transfer(dto.getStatus()) == null ? null : StoryStatusEnums.transfer(dto.getStatus()).getDesc());
|
|
||||||
exportDTO.setPlanEndDate(dto.getPlanEndDate());
|
|
||||||
exportDTO.setStage(StoryStageEnums.transfer(dto.getStage()) == null ? null : StoryStageEnums.transfer(dto.getStage()).getDesc());
|
|
||||||
exportDTO.setAssignedToName(dto.getAssignedToName());
|
|
||||||
exportDTO.setYsUserName(dto.getYsUserName());
|
|
||||||
exportDTO.setTaskCount(dto.getTaskCount());
|
|
||||||
exportDTO.setOpenedbyName(dto.getOpenedbyName());
|
|
||||||
exportDTO.setOpeneddate(dto.getOpeneddate());
|
|
||||||
exportList.add(exportDTO);
|
|
||||||
}
|
|
||||||
|
|
||||||
ExcelWriter excelWriter = null;
|
|
||||||
response.setContentType("application/vnd.ms-excel;charset=UTF-8");
|
|
||||||
try {
|
|
||||||
excelWriter = EasyExcel.write(response.getOutputStream())
|
|
||||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
|
||||||
.needHead(Boolean.TRUE).build();
|
|
||||||
|
|
||||||
WriteSheet writeSheet = EasyExcel.writerSheet(0, ExportConstants.SHEET_NAME)
|
|
||||||
.head(ProjectStoryExportDTO.class)
|
|
||||||
.needHead(Boolean.TRUE).build();
|
|
||||||
|
|
||||||
excelWriter.write(exportList, writeSheet);
|
|
||||||
excelWriter.finish();
|
|
||||||
} catch (Exception e) {
|
|
||||||
throw new RuntimeException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping(value = "/myStoryPageList", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
@RequestMapping(value = "/myStoryPageList", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
||||||
public Result<ZtStoryDTO> myStoryPageList(@RequestBody ZtProjectQo qo){
|
public Result<ZtStoryDTO> myStoryPageList(@RequestBody ZtProjectQo qo){
|
||||||
|
|
||||||
@@ -230,27 +182,6 @@ public class ZtStoryController {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 测试提交验收
|
|
||||||
* @param qo
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
@RequestMapping(value = "/testSubmitVerified", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result<ZtStoryDTO> 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<ZtStoryDTO> storyProductUserYs(@RequestBody ZtStoryDTO qo){
|
|
||||||
ztStoryService.storyProductUserYs(qo);
|
|
||||||
return Result.success();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@RequestMapping(value = "/storyListByProject", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
@RequestMapping(value = "/storyListByProject", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
||||||
public Result<ZtStoryDTO> storyListByProject(@RequestBody ZtProjectQo qo){
|
public Result<ZtStoryDTO> storyListByProject(@RequestBody ZtProjectQo qo){
|
||||||
@@ -383,7 +314,9 @@ public class ZtStoryController {
|
|||||||
return Result.success(searchDTOPageInfo);
|
return Result.success(searchDTOPageInfo);
|
||||||
}
|
}
|
||||||
|
|
||||||
//迭代列表根据项目id 或者产品id
|
|
||||||
|
|
||||||
|
//迭代列表根据项目id
|
||||||
@RequestMapping(value = "/execListByProject", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
@RequestMapping(value = "/execListByProject", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
||||||
public Result execListByProject(@RequestBody ZtStoryDTO dto){
|
public Result execListByProject(@RequestBody ZtStoryDTO dto){
|
||||||
if(dto.getProduct()!=null){
|
if(dto.getProduct()!=null){
|
||||||
@@ -403,26 +336,6 @@ public class ZtStoryController {
|
|||||||
List<ZtProject> list = ztStoryService.execListByProject(dto);
|
List<ZtProject> list = ztStoryService.execListByProject(dto);
|
||||||
return Result.success(list);
|
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1,78 +0,0 @@
|
|||||||
package com.sa.zentao.controller;
|
|
||||||
|
|
||||||
import com.sa.zentao.dao.Result;
|
|
||||||
import com.sa.zentao.entity.ZtStoryExpand;
|
|
||||||
import com.sa.zentao.service.IZtStoryExpandService;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* 前端控制器
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @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<List<ZtStoryExpand>> queryByMonth(String month, Integer projectId) {
|
|
||||||
List<ZtStoryExpand> list = ztStoryExpandService.queryByMonth(month, projectId);
|
|
||||||
return Result.success(list);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 按需求 ID 单查(AI 指标区块用)
|
|
||||||
* @param storyId 需求 ID
|
|
||||||
* @return 需求扩展信息(无记录返回 data=null)
|
|
||||||
*/
|
|
||||||
@RequestMapping(value = "/queryByStoryId", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result<ZtStoryExpand> queryByStoryId(Integer storyId) {
|
|
||||||
if (storyId == null) {
|
|
||||||
return Result.fail("storyId不能为空");
|
|
||||||
}
|
|
||||||
return Result.success(ztStoryExpandService.getOne(
|
|
||||||
new QueryWrapper<ZtStoryExpand>().lambda().eq(ZtStoryExpand::getStoryId, storyId)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存或更新(有数据了更新不新增)
|
|
||||||
* @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();
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* 前端控制器
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @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<List<String>> listMonths() {
|
|
||||||
return Result.success(ztStoryMonthWorkloadService.listMonths());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 按 storyId + 月份查询单条记录
|
|
||||||
*/
|
|
||||||
@RequestMapping(value = "/getByMonth", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result<ZtStoryMonthWorkload> 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<BigDecimal> maxWorkloadBefore(Integer storyId, String month) {
|
|
||||||
return Result.success(ztStoryMonthWorkloadService.getMaxWorkloadBefore(storyId, month));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -7,14 +7,10 @@ import com.alibaba.excel.write.metadata.WriteSheet;
|
|||||||
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
|
||||||
import com.github.pagehelper.PageInfo;
|
import com.github.pagehelper.PageInfo;
|
||||||
import com.sa.zentao.conf.RiskUserThreadLocal;
|
|
||||||
import com.sa.zentao.dao.*;
|
import com.sa.zentao.dao.*;
|
||||||
import com.sa.zentao.entity.ZtStory;
|
import com.sa.zentao.entity.ZtStory;
|
||||||
import com.sa.zentao.entity.ZtStoryUser;
|
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.enums.UserStoryEnums;
|
||||||
import com.sa.zentao.qo.StoryQo;
|
import com.sa.zentao.qo.StoryQo;
|
||||||
import com.sa.zentao.qo.ZtProjectQo;
|
import com.sa.zentao.qo.ZtProjectQo;
|
||||||
@@ -23,7 +19,6 @@ import lombok.extern.slf4j.Slf4j;
|
|||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import org.springframework.validation.annotation.Validated;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@@ -61,17 +56,6 @@ public class ZtStoryUserController {
|
|||||||
return Result.success();
|
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")
|
@RequestMapping(value = "/addStory", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
||||||
public Result addStory(@RequestBody ZtStoryUserDTO dto){
|
public Result addStory(@RequestBody ZtStoryUserDTO dto){
|
||||||
@@ -140,7 +124,7 @@ public class ZtStoryUserController {
|
|||||||
eq.in(ZtStoryUser::getStatus, UserStoryEnums.JH.getCode(),UserStoryEnums.CFM.getCode(),UserStoryEnums.KFZ.getCode());
|
eq.in(ZtStoryUser::getStatus, UserStoryEnums.JH.getCode(),UserStoryEnums.CFM.getCode(),UserStoryEnums.KFZ.getCode());
|
||||||
}
|
}
|
||||||
eq.select(ZtStoryUser::getId,ZtStoryUser::getTitle,ZtStoryUser::getProduct,
|
eq.select(ZtStoryUser::getId,ZtStoryUser::getTitle,ZtStoryUser::getProduct,
|
||||||
ZtStoryUser::getOpenedby,ZtStoryUser::getAssignedto,ZtStoryUser::getYsUser,ZtStoryUser::getProductUser
|
ZtStoryUser::getOpenedby,ZtStoryUser::getAssignedto,ZtStoryUser::getYsUser
|
||||||
);
|
);
|
||||||
List<ZtStoryUser> list = storyUserService.list(eq
|
List<ZtStoryUser> list = storyUserService.list(eq
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -100,16 +100,6 @@ public class ZtTaskController {
|
|||||||
return Result.success();
|
return Result.success();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* AI 批量创建任务:storyId 不存在或 type 非 devel/test 整批拒绝;重复跳过记 skipped
|
|
||||||
* @param dto 报文 {"storyId":N,"tasks":[{"name","type","assignedTo","aiEvaluationTime","planStartDate","deadline"}]}
|
|
||||||
* @return {"code":0,"data":{"created":N,"taskIds":[...],"skipped":[...]}}
|
|
||||||
*/
|
|
||||||
@RequestMapping(value = "/aiBatchAdd", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
|
||||||
public Result aiBatchAdd(@RequestBody ZtTaskAiBatchDTO dto){
|
|
||||||
return Result.success(ztTaskService.aiBatchAdd(dto));
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequestMapping(value = "/approval", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
@RequestMapping(value = "/approval", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
||||||
public Result approval(@RequestBody ZtTaskDTO dto){
|
public Result approval(@RequestBody ZtTaskDTO dto){
|
||||||
ztTaskService.approval(dto);
|
ztTaskService.approval(dto);
|
||||||
@@ -641,16 +631,4 @@ public class ZtTaskController {
|
|||||||
|
|
||||||
return Result.success();
|
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();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -135,13 +135,11 @@ public class ZtUserController {
|
|||||||
}
|
}
|
||||||
eq.in(ZtUser::getAccount,execution.stream().map(o->o.getAccount()).collect(Collectors.toList()));
|
eq.in(ZtUser::getAccount,execution.stream().map(o->o.getAccount()).collect(Collectors.toList()));
|
||||||
}else{
|
}else{
|
||||||
this.userMapper.selectOne(new QueryWrapper<ZtUser>().lambda()
|
this.userMapper.selectOne(new QueryWrapper<ZtUser>().lambda().eq(ZtUser::getAccount,RiskUserThreadLocal.get().getName()));
|
||||||
.eq(ZtUser::getAccount,RiskUserThreadLocal.get().getName()));
|
|
||||||
}
|
}
|
||||||
eq.eq(dto.getUserType()!=null,ZtUser::getUserType,dto.getUserType());
|
|
||||||
//CHG-073:userList 剔除 password 列——MD5 哈希不得下发前端(按属性名匹配,实体列名带反引号)
|
return Result.success(userService.list(eq
|
||||||
eq.select(ZtUser.class, fieldInfo -> !"password".equals(fieldInfo.getProperty()));
|
));
|
||||||
return Result.success(userService.list(eq));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequestMapping(value = "/storyReviewList", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
@RequestMapping(value = "/storyReviewList", method = RequestMethod.POST, produces = "application/json; charset=UTF-8")
|
||||||
|
|||||||
@@ -3,33 +3,15 @@ package com.sa.zentao.dao;
|
|||||||
|
|
||||||
public class BusinessException extends RuntimeException {
|
public class BusinessException extends RuntimeException {
|
||||||
|
|
||||||
|
|
||||||
public BusinessException(Code code){
|
public BusinessException(Code code){
|
||||||
super(code.getValue());
|
super(code.getValue());
|
||||||
this.code=code;
|
this.code=code;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BusinessException(String value){
|
public BusinessException(String value){
|
||||||
super(value);
|
super(value);
|
||||||
this.value=value;
|
this.value=value;
|
||||||
}
|
}
|
||||||
|
|
||||||
public BusinessException(Code code, String message){
|
|
||||||
super(message);
|
|
||||||
this.code=code;
|
|
||||||
this.value=message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BusinessException(String value, Throwable cause){
|
|
||||||
super(value, cause);
|
|
||||||
this.value=value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public BusinessException(Code code, String message, Throwable cause){
|
|
||||||
super(message, cause);
|
|
||||||
this.code=code;
|
|
||||||
this.value=message;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String value;
|
public String value;
|
||||||
public Code code;
|
public Code code;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,11 +4,7 @@ public enum Code {
|
|||||||
SUCCESS(0, "成功"),
|
SUCCESS(0, "成功"),
|
||||||
FAIL(-1, "失败"),
|
FAIL(-1, "失败"),
|
||||||
CFTJ(-2, "重复添加"),
|
CFTJ(-2, "重复添加"),
|
||||||
PLEASELOGIN(401, "请登录"),
|
PLEASELOGIN(401, "请登录");
|
||||||
PARAM_ERROR(400, "参数错误"),
|
|
||||||
NOT_FOUND(404, "资源不存在"),
|
|
||||||
FORBIDDEN(403, "无权限访问"),
|
|
||||||
INTERNAL_ERROR(500, "系统内部错误");
|
|
||||||
|
|
||||||
private Integer code;
|
private Integer code;
|
||||||
private String value;
|
private String value;
|
||||||
|
|||||||
@@ -160,53 +160,4 @@ public class PerformanceDTO implements Serializable {
|
|||||||
private BigDecimal professionalSkillEnhancementScore=BigDecimal.ZERO;
|
private BigDecimal professionalSkillEnhancementScore=BigDecimal.ZERO;
|
||||||
//问题管理得分
|
//问题管理得分
|
||||||
private BigDecimal developFeedbackStory=BigDecimal.valueOf(10);
|
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;
|
|
||||||
|
|
||||||
/** ===== CHG-037 新 Excel 角色改造新增字段(score×weight 加权得分) ===== */
|
|
||||||
//需求PRD工作量指标完成率得分(项目经理20/产品经理40/产品助理50)
|
|
||||||
private BigDecimal workloadPrdScore;
|
|
||||||
//团队工作量指标完成率得分(项目经理30/产品经理20)
|
|
||||||
private BigDecimal workloadTeamScore;
|
|
||||||
//运维大项任务及时完成得分(20)
|
|
||||||
private BigDecimal opsMajorTaskScore;
|
|
||||||
//系统运维监控得分(15)
|
|
||||||
private BigDecimal opsMonitorScore;
|
|
||||||
//职场巡检得分(10)
|
|
||||||
private BigDecimal opsInspectScore;
|
|
||||||
//数据库备份得分(10)
|
|
||||||
private BigDecimal opsBackupScore;
|
|
||||||
//其他运维工作得分(15,人工)
|
|
||||||
private BigDecimal otherOpsScore;
|
|
||||||
//开发方向(CHG-038,KFZ 弹窗分流用):frontend=前端 / backend=后端
|
|
||||||
private String devDirection;
|
|
||||||
//CHG-041 指标过程值明细(itemKey → "分子/分母/率"),弹窗「绩效数据」列展示
|
|
||||||
private java.util.Map<String, String> perfRawDetail;
|
|
||||||
/** ===== CHG-044 达标工时全链路(弹窗「绩效数据」列展示分子分母) ===== */
|
|
||||||
//团队总工作天数(人·天,=工作天数×KFZ 人数 − 团队请假折算天数)
|
|
||||||
private BigDecimal teamWorkDays;
|
|
||||||
//团队请假折算天数(团队请假小时 ÷8)
|
|
||||||
private BigDecimal teamLeaveDays;
|
|
||||||
//团队达标总工时(=团队总工作天数 ×5)
|
|
||||||
private BigDecimal teamTargetTime;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
package com.sa.zentao.dao;
|
|
||||||
|
|
||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 项目需求导出DTO
|
|
||||||
* 表头: ID 研发需求名称 P 状态 期望完成时间 阶段 指派给 验收人 任务量 创建者 创建日期
|
|
||||||
*
|
|
||||||
* @author gqb
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class ProjectStoryExportDTO implements Serializable {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
@ExcelProperty(value = "ID", index = 0)
|
|
||||||
private Integer id;
|
|
||||||
|
|
||||||
@ExcelProperty(value = "研发需求名称", index = 1)
|
|
||||||
private String title;
|
|
||||||
|
|
||||||
@ExcelProperty(value = "P", index = 2)
|
|
||||||
private Integer pri;
|
|
||||||
|
|
||||||
@ExcelProperty(value = "状态", index = 3)
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
@ExcelProperty(value = "期望完成时间", index = 4)
|
|
||||||
private Date planEndDate;
|
|
||||||
|
|
||||||
@ExcelProperty(value = "阶段", index = 5)
|
|
||||||
private String stage;
|
|
||||||
|
|
||||||
@ExcelProperty(value = "指派给", index = 6)
|
|
||||||
private String assignedToName;
|
|
||||||
|
|
||||||
@ExcelProperty(value = "验收人", index = 7)
|
|
||||||
private String ysUserName;
|
|
||||||
|
|
||||||
@ExcelProperty(value = "任务量", index = 8)
|
|
||||||
private Integer taskCount;
|
|
||||||
|
|
||||||
@ExcelProperty(value = "创建者", index = 9)
|
|
||||||
private String openedbyName;
|
|
||||||
|
|
||||||
@ExcelProperty(value = "创建日期", index = 10)
|
|
||||||
private Date openeddate;
|
|
||||||
}
|
|
||||||
@@ -26,20 +26,6 @@ public class UploadDTO implements Serializable {
|
|||||||
|
|
||||||
private Integer objectId;
|
private Integer objectId;
|
||||||
|
|
||||||
//代码审查结果 pass/reject(objectType=aiCodeReview 时可选传入)
|
|
||||||
private String reviewResult;
|
|
||||||
|
|
||||||
private List<Integer> testIds;
|
private List<Integer> testIds;
|
||||||
|
|
||||||
/**
|
|
||||||
* uploadBind 入口归一化(CHG-077):story 一律按 storyPrd 处理。
|
|
||||||
* uploadBind 仅 AI 通道使用(UI 手动附件走两段式上传不进此接口),
|
|
||||||
* 归一后文件进详情页「需求文档」区块并刷新 prd_url,不再落通用附件区。
|
|
||||||
*/
|
|
||||||
public void normalizeObjectTypeForBind() {
|
|
||||||
if ("story".equals(this.objectType)) {
|
|
||||||
this.objectType = "storyPrd";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,14 +103,4 @@ public class ZtComputerSourceDTO implements Serializable {
|
|||||||
private String code;
|
private String code;
|
||||||
|
|
||||||
private String ip;
|
private String ip;
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用环境:生产环境/备用环境/开发环境/测试环境/UAT环境/办公环境
|
|
||||||
*/
|
|
||||||
private String useEnv;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 到期时间
|
|
||||||
*/
|
|
||||||
private Date expireDate;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,6 @@ package com.sa.zentao.dao;
|
|||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.sa.zentao.enums.UserType;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
@@ -206,8 +205,6 @@ public class ZtProjectDTO<T> implements Serializable {
|
|||||||
|
|
||||||
private String deleted;
|
private String deleted;
|
||||||
|
|
||||||
private UserType userType;
|
|
||||||
|
|
||||||
private List<T> children;
|
private List<T> children;
|
||||||
|
|
||||||
private List<Integer> storyIds;
|
private List<Integer> storyIds;
|
||||||
|
|||||||
@@ -8,7 +8,6 @@ import com.baomidou.mybatisplus.annotation.TableField;
|
|||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.sa.zentao.entity.ZtStoryCase;
|
import com.sa.zentao.entity.ZtStoryCase;
|
||||||
import com.sa.zentao.entity.ZtStoryreview;
|
import com.sa.zentao.entity.ZtStoryreview;
|
||||||
import jakarta.validation.constraints.NotNull;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
@@ -32,7 +31,6 @@ public class ZtStoryDTO implements Serializable {
|
|||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
@NotNull(message = "请录入ID")
|
|
||||||
@ExcelProperty(value = "ID",index =0)
|
@ExcelProperty(value = "ID",index =0)
|
||||||
private Integer id;
|
private Integer id;
|
||||||
@ExcelProperty(value = "研发需求名称",index =1)
|
@ExcelProperty(value = "研发需求名称",index =1)
|
||||||
@@ -50,8 +48,6 @@ public class ZtStoryDTO implements Serializable {
|
|||||||
private Date planEndDate;
|
private Date planEndDate;
|
||||||
@ExcelProperty(value = "阶段",index =6)
|
@ExcelProperty(value = "阶段",index =6)
|
||||||
private String stage;
|
private String stage;
|
||||||
@ExcelIgnore
|
|
||||||
private String stageName;
|
|
||||||
@ExcelProperty(value = "指派给",index =7)
|
@ExcelProperty(value = "指派给",index =7)
|
||||||
private String assignedToName;
|
private String assignedToName;
|
||||||
|
|
||||||
@@ -156,36 +152,8 @@ public class ZtStoryDTO implements Serializable {
|
|||||||
private Integer frombug;
|
private Integer frombug;
|
||||||
@ExcelIgnore
|
@ExcelIgnore
|
||||||
private Integer feedback;
|
private Integer feedback;
|
||||||
/**
|
|
||||||
* 产品人
|
|
||||||
*/
|
|
||||||
@ExcelIgnore
|
|
||||||
private String productUser;
|
|
||||||
/**
|
|
||||||
* 产品人
|
|
||||||
*/
|
|
||||||
@ExcelIgnore
|
|
||||||
private String productUserName;
|
|
||||||
@ExcelIgnore
|
|
||||||
private String productUserColor;
|
|
||||||
/**
|
|
||||||
* 产品人
|
|
||||||
*/
|
|
||||||
@ExcelIgnore
|
|
||||||
private String testUser;
|
|
||||||
@ExcelIgnore
|
|
||||||
private String testUserColor;
|
|
||||||
/**
|
|
||||||
* 产品人
|
|
||||||
*/
|
|
||||||
@ExcelIgnore
|
|
||||||
private String testUserName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 1是否内部验收 0不需要
|
|
||||||
*/
|
|
||||||
@ExcelIgnore
|
|
||||||
private Integer innerYsFlag;
|
|
||||||
|
|
||||||
@ExcelIgnore
|
@ExcelIgnore
|
||||||
private String keywords;
|
private String keywords;
|
||||||
@@ -249,6 +217,7 @@ public class ZtStoryDTO implements Serializable {
|
|||||||
private Date planYsDate;
|
private Date planYsDate;
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@ExcelIgnore
|
@ExcelIgnore
|
||||||
private Date activateddate;
|
private Date activateddate;
|
||||||
@ExcelIgnore
|
@ExcelIgnore
|
||||||
@@ -378,22 +347,4 @@ public class ZtStoryDTO implements Serializable {
|
|||||||
*/
|
*/
|
||||||
@ExcelIgnore
|
@ExcelIgnore
|
||||||
private String deliverContent;
|
private String deliverContent;
|
||||||
|
|
||||||
// ===== AI 文档链接与审查状态(对应 zt_story 新列,详见 dev_plan v4.0 §3.1)=====
|
|
||||||
/** PRD 文档访问链接 */
|
|
||||||
private String prdUrl;
|
|
||||||
/** 代码审查报告访问链接 */
|
|
||||||
private String codeReviewUrl;
|
|
||||||
/** 工作日志访问链接 */
|
|
||||||
private String workLogUrl;
|
|
||||||
/** 测试用例访问链接 */
|
|
||||||
private String testCaseUrl;
|
|
||||||
/** 测试报告模版下载访问链接 */
|
|
||||||
private String testReportDownloadUrl;
|
|
||||||
/** 测试报告提交访问链接 */
|
|
||||||
private String testReportSubmitUrl;
|
|
||||||
/** 其他测试文档访问链接 */
|
|
||||||
private String testOtherUrl;
|
|
||||||
/** 审查状态 pass/reject */
|
|
||||||
private String codeReviewStatus;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,25 +2,18 @@ package com.sa.zentao.dao;
|
|||||||
|
|
||||||
import com.alibaba.excel.annotation.ExcelIgnore;
|
import com.alibaba.excel.annotation.ExcelIgnore;
|
||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import com.alibaba.excel.metadata.data.HyperlinkData;
|
|
||||||
import com.alibaba.excel.metadata.data.WriteCellData;
|
import com.alibaba.excel.metadata.data.WriteCellData;
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
import com.baomidou.mybatisplus.annotation.IdType;
|
||||||
import com.baomidou.mybatisplus.annotation.TableField;
|
import com.baomidou.mybatisplus.annotation.TableField;
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
import com.baomidou.mybatisplus.annotation.TableId;
|
||||||
import com.sa.zentao.entity.ZtProject;
|
|
||||||
import jakarta.validation.constraints.NotBlank;
|
|
||||||
import jakarta.validation.constraints.NotNull;
|
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
import lombok.NonNull;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
import java.time.LocalDateTime;
|
import java.time.LocalDateTime;
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -32,14 +25,12 @@ import java.util.Set;
|
|||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = false)
|
@EqualsAndHashCode(callSuper = false)
|
||||||
@Slf4j
|
|
||||||
public class ZtStoryUserDTO implements Serializable {
|
public class ZtStoryUserDTO implements Serializable {
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
// @TableId(value = "id", type = IdType.AUTO)
|
// @TableId(value = "id", type = IdType.AUTO)
|
||||||
// @ExcelProperty
|
// @ExcelProperty
|
||||||
@NotNull(message = "ID不能为空")
|
|
||||||
@ExcelProperty(value = "ID",index =0)
|
@ExcelProperty(value = "ID",index =0)
|
||||||
private Integer id;
|
private Integer id;
|
||||||
|
|
||||||
@@ -55,48 +46,41 @@ public class ZtStoryUserDTO implements Serializable {
|
|||||||
*/
|
*/
|
||||||
@ExcelProperty(value = "关联研发需求",index =4)
|
@ExcelProperty(value = "关联研发需求",index =4)
|
||||||
private String storyList;
|
private String storyList;
|
||||||
@ExcelIgnore
|
@ExcelProperty(value = "状态",index =5)
|
||||||
private Set<ZtProject> execList;
|
|
||||||
/**
|
|
||||||
* 迭代版本(CHG-078):execList 中迭代名称去重排序逗号拼接,仅供导出/列表展示
|
|
||||||
*/
|
|
||||||
@ExcelProperty(value = "迭代版本", index = 5)
|
|
||||||
private String execNames;
|
|
||||||
@ExcelProperty(value = "状态",index =6)
|
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
@ExcelProperty(value = "来源",index =7)
|
@ExcelProperty(value = "来源",index =6)
|
||||||
private String source;
|
private String source;
|
||||||
|
|
||||||
@ExcelProperty(value = "期望完成时间",index =8)
|
@ExcelProperty(value = "期望完成时间",index =7)
|
||||||
private Date planEndDate;
|
private Date planEndDate;
|
||||||
/**
|
/**
|
||||||
* 验收人
|
* 验收人
|
||||||
*/
|
*/
|
||||||
@ExcelIgnore
|
@ExcelIgnore
|
||||||
private String ysUser;
|
private String ysUser;
|
||||||
@ExcelProperty(value = "验收人",index =9)
|
@ExcelProperty(value = "验收人",index =8)
|
||||||
private String ysUserName;
|
private String ysUserName;
|
||||||
@ExcelProperty(value = "评审人",index =10)
|
@ExcelProperty(value = "评审人",index =9)
|
||||||
private String reviewedbyName;
|
private String reviewedbyName;
|
||||||
|
|
||||||
@ExcelProperty(value = "创建日期",index =11)
|
@ExcelProperty(value = "创建日期",index =10)
|
||||||
private Date openeddate;
|
private Date openeddate;
|
||||||
|
|
||||||
@ExcelProperty(value = "更新日期",index =12)
|
@ExcelProperty(value = "更新日期",index =11)
|
||||||
private Date lastediteddate;
|
private Date lastediteddate;
|
||||||
|
|
||||||
@ExcelIgnore
|
@ExcelIgnore
|
||||||
private String closedby;
|
private String closedby;
|
||||||
@ExcelProperty(value = "由谁关闭",index =13)
|
@ExcelProperty(value = "由谁关闭",index =12)
|
||||||
private String closedbyName;
|
private String closedbyName;
|
||||||
@ExcelIgnore
|
@ExcelIgnore
|
||||||
private Date closeddate;
|
private Date closeddate;
|
||||||
|
|
||||||
@ExcelProperty(value = "关闭原因",index =14)
|
@ExcelProperty(value = "关闭原因",index =13)
|
||||||
private String closedreason;
|
private String closedreason;
|
||||||
|
|
||||||
@ExcelProperty(value = "需求链接",index =15)
|
@ExcelProperty(value = "需求链接",index =14)
|
||||||
private WriteCellData<String> url;
|
private WriteCellData<String> url;
|
||||||
@ExcelIgnore
|
@ExcelIgnore
|
||||||
private String vision;
|
private String vision;
|
||||||
@@ -146,10 +130,6 @@ public class ZtStoryUserDTO implements Serializable {
|
|||||||
@ExcelIgnore
|
@ExcelIgnore
|
||||||
private String color;
|
private String color;
|
||||||
@ExcelIgnore
|
@ExcelIgnore
|
||||||
private String productUserName;
|
|
||||||
@ExcelIgnore
|
|
||||||
private String productUserColor;
|
|
||||||
@ExcelIgnore
|
|
||||||
private String stage;
|
private String stage;
|
||||||
|
|
||||||
@ExcelIgnore
|
@ExcelIgnore
|
||||||
@@ -167,7 +147,7 @@ public class ZtStoryUserDTO implements Serializable {
|
|||||||
private String openedby;
|
private String openedby;
|
||||||
|
|
||||||
|
|
||||||
@NotBlank(message = "指派??")
|
|
||||||
@ExcelIgnore
|
@ExcelIgnore
|
||||||
private String assignedto;
|
private String assignedto;
|
||||||
@ExcelIgnore
|
@ExcelIgnore
|
||||||
@@ -293,30 +273,4 @@ public class ZtStoryUserDTO implements Serializable {
|
|||||||
//1.需要 2.不需要
|
//1.需要 2.不需要
|
||||||
@ExcelIgnore
|
@ExcelIgnore
|
||||||
private Integer needImprove;
|
private Integer needImprove;
|
||||||
/**
|
|
||||||
* 需求背景
|
|
||||||
*/
|
|
||||||
@ExcelIgnore
|
|
||||||
private String storyBackground;
|
|
||||||
/**
|
|
||||||
* 产品用户
|
|
||||||
*/
|
|
||||||
@ExcelIgnore
|
|
||||||
private String productUser;
|
|
||||||
|
|
||||||
public void setLinkUrl(String baseUrl) {
|
|
||||||
String reName = this.getTitle();
|
|
||||||
String reUrl = baseUrl + "/#/product-user-story-info/" + this.getId();
|
|
||||||
WriteCellData<String> hyperlink = new WriteCellData<>(reName);
|
|
||||||
HyperlinkData hyperlinkData = new HyperlinkData();
|
|
||||||
try {
|
|
||||||
hyperlinkData.setAddress(reUrl.replace(" ", "%20"));
|
|
||||||
hyperlinkData.setHyperlinkType(HyperlinkData.HyperlinkType.URL);
|
|
||||||
hyperlink.setHyperlinkData(hyperlinkData);
|
|
||||||
this.setUrl(hyperlink);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.error(e.getMessage(), e);
|
|
||||||
this.setUrl(null);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,39 +0,0 @@
|
|||||||
package com.sa.zentao.dao;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AI 批量创建任务报文(POST /zt-task/aiBatchAdd)
|
|
||||||
* {"storyId":N,"tasks":[{"name","type","assignedTo","aiEvaluationTime","planStartDate","deadline"}]}
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode(callSuper = false)
|
|
||||||
public class ZtTaskAiBatchDTO implements Serializable {
|
|
||||||
|
|
||||||
private Integer storyId;
|
|
||||||
|
|
||||||
private List<AiTaskItem> tasks;
|
|
||||||
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode(callSuper = false)
|
|
||||||
public static class AiTaskItem implements Serializable {
|
|
||||||
|
|
||||||
private String name;
|
|
||||||
//devel 开发 / test 测试 / design 设计(CHG-081 放行)/ affair 事务(CHG-082 放行)
|
|
||||||
private String type;
|
|
||||||
|
|
||||||
private String assignedTo;
|
|
||||||
//任务描述,可选,写入 zt_task.desc
|
|
||||||
private String desc;
|
|
||||||
//AI评估工时,写入 zt_task.estimate
|
|
||||||
private Float aiEvaluationTime;
|
|
||||||
//预计开始,格式 yyyy-MM-dd,写入 zt_task.estStarted
|
|
||||||
private String planStartDate;
|
|
||||||
//预计完成,格式 yyyy-MM-dd,写入 zt_task.deadline
|
|
||||||
private String deadline;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -212,8 +212,4 @@ public class ZtTaskDTO implements Serializable {
|
|||||||
* 交付物
|
* 交付物
|
||||||
*/
|
*/
|
||||||
private String deliverContent;
|
private String deliverContent;
|
||||||
/**
|
|
||||||
* 1提交 0不提交
|
|
||||||
*/
|
|
||||||
private Integer innerYsFlag;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -128,9 +128,6 @@ public class ZtUserDTO implements Serializable {
|
|||||||
|
|
||||||
private UserType userType;
|
private UserType userType;
|
||||||
|
|
||||||
//开发方向(CHG-038,仅 KFZ):frontend=前端 / backend=后端
|
|
||||||
private String devDirection;
|
|
||||||
|
|
||||||
private String userTypeValue;
|
private String userTypeValue;
|
||||||
|
|
||||||
private String roleTypeValue;
|
private String roleTypeValue;
|
||||||
|
|||||||
@@ -104,14 +104,4 @@ public class ZtComputerSource implements Serializable {
|
|||||||
private String code;
|
private String code;
|
||||||
|
|
||||||
private String ip;
|
private String ip;
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用环境:生产环境/备用环境/开发环境/测试环境/UAT环境/办公环境
|
|
||||||
*/
|
|
||||||
private String useEnv;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 到期时间
|
|
||||||
*/
|
|
||||||
private Date expireDate;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,114 +0,0 @@
|
|||||||
package com.sa.zentao.entity;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* 大型需求文档齐备核查快照表(FR-014 三期)
|
|
||||||
* 一行 = 某大型需求某月某类文档的核查结果;
|
|
||||||
* 状态流 pending(待确认)→ appealing(申诉中)→ resolved(复核完结,appealResult=pass 时回滚扣分)
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode(callSuper = false)
|
|
||||||
public class ZtDocCheck implements Serializable {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 状态-待确认
|
|
||||||
*/
|
|
||||||
public static final String STATUS_PENDING = "pending";
|
|
||||||
/**
|
|
||||||
* 状态-申诉中
|
|
||||||
*/
|
|
||||||
public static final String STATUS_APPEALING = "appealing";
|
|
||||||
/**
|
|
||||||
* 状态-复核完结
|
|
||||||
*/
|
|
||||||
public static final String STATUS_RESOLVED = "resolved";
|
|
||||||
/**
|
|
||||||
* 复核结果-通过(回滚扣分)
|
|
||||||
*/
|
|
||||||
public static final String APPEAL_PASS = "pass";
|
|
||||||
/**
|
|
||||||
* 复核结果-驳回
|
|
||||||
*/
|
|
||||||
public static final String APPEAL_REJECT = "reject";
|
|
||||||
|
|
||||||
@TableId(value = "id", type = IdType.AUTO)
|
|
||||||
private Integer id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 研发需求ID(zt_story.id,大型需求=workload_index>20)
|
|
||||||
*/
|
|
||||||
private Integer storyId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 考核月份 yyyy-MM
|
|
||||||
*/
|
|
||||||
private String month;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文档类型 testCase/testReport/aiCodeReview/aiWorkLog/docUpdate
|
|
||||||
*/
|
|
||||||
private String docType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否齐备 1有 0缺失
|
|
||||||
*/
|
|
||||||
private Integer present;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 判定明细(命中记录说明)
|
|
||||||
*/
|
|
||||||
private String detail;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 状态 pending/appealing/resolved
|
|
||||||
*/
|
|
||||||
private String status;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 申诉理由
|
|
||||||
*/
|
|
||||||
private String appealReason;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 复核结果 pass/reject(pass 回滚扣分)
|
|
||||||
*/
|
|
||||||
private String appealResult;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 复核备注
|
|
||||||
*/
|
|
||||||
private String resolveRemark;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 核查时间
|
|
||||||
*/
|
|
||||||
private Date checkTime;
|
|
||||||
|
|
||||||
private Date createTime;
|
|
||||||
|
|
||||||
private Date updateTime;
|
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否计入缺失扣分(缺失 且 非复核通过)
|
|
||||||
*/
|
|
||||||
public boolean countsAsMissing() {
|
|
||||||
return (this.present == null || this.present == 0)
|
|
||||||
&& !(STATUS_RESOLVED.equals(this.status) && APPEAL_PASS.equals(this.appealResult));
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -29,9 +29,6 @@ public class ZtFile implements Serializable {
|
|||||||
|
|
||||||
private String pathname;
|
private String pathname;
|
||||||
|
|
||||||
//附件访问链接
|
|
||||||
private String url;
|
|
||||||
|
|
||||||
//文件名
|
//文件名
|
||||||
private String title;
|
private String title;
|
||||||
//后缀
|
//后缀
|
||||||
|
|||||||
@@ -73,9 +73,4 @@ public class ZtMeeting implements Serializable {
|
|||||||
private String result;
|
private String result;
|
||||||
private String meetingAfter;
|
private String meetingAfter;
|
||||||
|
|
||||||
/**
|
|
||||||
* 会议纪要MD访问链接(最新一份)
|
|
||||||
*/
|
|
||||||
private String url;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,69 +0,0 @@
|
|||||||
package com.sa.zentao.entity;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.annotation.IdType;
|
|
||||||
import com.baomidou.mybatisplus.annotation.TableId;
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.Date;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* 绩效指标规则配置表(三期,prd §7.5 九岗位算分模型)
|
|
||||||
* 一行 = 某岗位某指标项;rule_json 存规则参数(type: linear/threshold/manual)
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode(callSuper = false)
|
|
||||||
public class ZtPerfConfig implements Serializable {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
@TableId(value = "id", type = IdType.AUTO)
|
|
||||||
private Integer id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 岗位编码(projectManager/productManager/backendDev/tester/uiEngineer/opsEngineer 等)
|
|
||||||
*/
|
|
||||||
private String role;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 指标编码(与 PerfIndicatorCalculator.key() 对应)
|
|
||||||
*/
|
|
||||||
private String itemKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 指标名称
|
|
||||||
*/
|
|
||||||
private String itemName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 权重(0~1,加分项为0)
|
|
||||||
*/
|
|
||||||
private BigDecimal weight;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 规则参数JSON(type=linear/threshold/manual)
|
|
||||||
*/
|
|
||||||
private String ruleJson;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否启用(加分项可配开关)
|
|
||||||
*/
|
|
||||||
private Integer enabled;
|
|
||||||
|
|
||||||
private String createUser;
|
|
||||||
|
|
||||||
private Date createTime;
|
|
||||||
|
|
||||||
private String updateUser;
|
|
||||||
|
|
||||||
private Date updateTime;
|
|
||||||
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -190,12 +190,7 @@ public class ZtStory implements Serializable {
|
|||||||
private Date developedDate;
|
private Date developedDate;
|
||||||
//结束日期 测试结束
|
//结束日期 测试结束
|
||||||
private Date endDate;
|
private Date endDate;
|
||||||
//内部验收时间
|
|
||||||
@TableField(exist = false)
|
|
||||||
private Date innerYsTime;
|
|
||||||
//测试提交验收时间
|
|
||||||
@TableField(exist = false)
|
|
||||||
private Date testSubmitYsTime;
|
|
||||||
private Integer project;
|
private Integer project;
|
||||||
//1通过 2不通过
|
//1通过 2不通过
|
||||||
private Integer ysFlag;
|
private Integer ysFlag;
|
||||||
@@ -228,61 +223,5 @@ public class ZtStory implements Serializable {
|
|||||||
// private Date develDate;
|
// private Date develDate;
|
||||||
// //测试完成时间
|
// //测试完成时间
|
||||||
// private Date testedDate;
|
// private Date testedDate;
|
||||||
/**
|
|
||||||
* 内部验收标识 1需要内部验收 0不需要
|
|
||||||
*/
|
|
||||||
private Integer innerYsFlag;
|
|
||||||
/**
|
|
||||||
* 产品人员
|
|
||||||
*/
|
|
||||||
private String productUser;
|
|
||||||
/**
|
|
||||||
* 测试人员
|
|
||||||
*/
|
|
||||||
private String testUser;
|
|
||||||
/**
|
|
||||||
* PRD文档访问链接
|
|
||||||
*/
|
|
||||||
@TableField("prd_url")
|
|
||||||
private String prdUrl;
|
|
||||||
/**
|
|
||||||
* 代码审查报告访问链接
|
|
||||||
*/
|
|
||||||
@TableField("code_review_url")
|
|
||||||
private String codeReviewUrl;
|
|
||||||
/**
|
|
||||||
* 工作日志访问链接
|
|
||||||
*/
|
|
||||||
@TableField("work_log_url")
|
|
||||||
private String workLogUrl;
|
|
||||||
/**
|
|
||||||
* AI项目文档更新记录访问链接(CHG-046)
|
|
||||||
*/
|
|
||||||
@TableField("ai_doc_update_url")
|
|
||||||
private String aiDocUpdateUrl;
|
|
||||||
/**
|
|
||||||
* 测试用例访问链接
|
|
||||||
*/
|
|
||||||
@TableField("test_case_url")
|
|
||||||
private String testCaseUrl;
|
|
||||||
/**
|
|
||||||
* 测试报告模版下载访问链接
|
|
||||||
*/
|
|
||||||
@TableField("test_report_download_url")
|
|
||||||
private String testReportDownloadUrl;
|
|
||||||
/**
|
|
||||||
* 测试报告提交访问链接
|
|
||||||
*/
|
|
||||||
@TableField("test_report_submit_url")
|
|
||||||
private String testReportSubmitUrl;
|
|
||||||
/**
|
|
||||||
* 其他测试文档访问链接
|
|
||||||
*/
|
|
||||||
@TableField("test_other_url")
|
|
||||||
private String testOtherUrl;
|
|
||||||
/**
|
|
||||||
* 审查状态 pass/reject
|
|
||||||
*/
|
|
||||||
@TableField("code_review_status")
|
|
||||||
private String codeReviewStatus;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,129 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
*
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AI参与率(只存不算,口径待定)
|
|
||||||
*/
|
|
||||||
private String aiParticipationRate;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 评估工时
|
|
||||||
*/
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 产品人员
|
|
||||||
*/
|
|
||||||
private String productPerson;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开发人员
|
|
||||||
*/
|
|
||||||
private String developPerson;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 测试人员
|
|
||||||
*/
|
|
||||||
private String testPerson;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* AI框架验收指标(Given/When/Then,MD 文本;框架经 saveOrUpdate 上传,
|
|
||||||
* 与老验收标准 zt_storyspec.verify 互不干扰——CHG-022)
|
|
||||||
*/
|
|
||||||
private String acceptanceCriteria;
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,56 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
*
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @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;
|
|
||||||
}
|
|
||||||
@@ -225,9 +225,4 @@ public class ZtStoryUser implements Serializable {
|
|||||||
private String deliverRemark;
|
private String deliverRemark;
|
||||||
|
|
||||||
private String oldStatus;
|
private String oldStatus;
|
||||||
/**
|
|
||||||
* 产品用户
|
|
||||||
*/
|
|
||||||
private String productUser;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,6 +31,5 @@ public class ZtStoryUserspec implements Serializable {
|
|||||||
|
|
||||||
private String files;
|
private String files;
|
||||||
|
|
||||||
private String storyBackground;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -126,9 +126,6 @@ public class ZtUser implements Serializable {
|
|||||||
|
|
||||||
private UserType userType;
|
private UserType userType;
|
||||||
|
|
||||||
//开发方向(CHG-038,仅 user_type=KFZ 有效):frontend=前端 / backend=后端,NULL 按后端口径
|
|
||||||
private String devDirection;
|
|
||||||
|
|
||||||
private String productIds;
|
private String productIds;
|
||||||
|
|
||||||
@TableField(exist = false)
|
@TableField(exist = false)
|
||||||
|
|||||||
@@ -62,12 +62,6 @@ public enum ActionStatus {
|
|||||||
XGMM(101, "xgmm","修改密码"),
|
XGMM(101, "xgmm","修改密码"),
|
||||||
CZMM(102, "czmm","重置密码"),
|
CZMM(102, "czmm","重置密码"),
|
||||||
CZINFO(103, "czmm","重置"),
|
CZINFO(103, "czmm","重置"),
|
||||||
|
|
||||||
//测试提交验收
|
|
||||||
TESTSUBMITYS(200, "testSubmitYs","测试提交验收"),
|
|
||||||
PRODUCTYS(201, "productYs","产品验收"),
|
|
||||||
ASSIGNTOPRODUCTUSER(202, "assignProductUser","指派产品人"),
|
|
||||||
ASSIGNTOTESTUSER(203, "assignTestUser","指派测试人"),
|
|
||||||
;
|
;
|
||||||
|
|
||||||
@EnumValue
|
@EnumValue
|
||||||
|
|||||||
@@ -13,14 +13,6 @@ public enum FileTypes {
|
|||||||
feedbackStory("feedbackStory", "问题反馈"),
|
feedbackStory("feedbackStory", "问题反馈"),
|
||||||
ywTask("ywTask", "运维任务"),
|
ywTask("ywTask", "运维任务"),
|
||||||
meeting("meeting", "会议"),
|
meeting("meeting", "会议"),
|
||||||
aiCodeReview("aiCodeReview", "代码审查报告"),
|
|
||||||
aiWorkLog("aiWorkLog", "工作日志"),
|
|
||||||
aiDocUpdate("aiDocUpdate", "AI项目文档更新记录"),
|
|
||||||
storyPrd("storyPrd", "需求文档"),
|
|
||||||
testCase("testCase", "测试用例"),
|
|
||||||
testReport("testReport", "测试报告模版"),
|
|
||||||
testReportSubmit("testReportSubmit", "测试报告提交"),
|
|
||||||
testOther("testOther", "其他测试文档"),
|
|
||||||
;
|
;
|
||||||
|
|
||||||
@EnumValue
|
@EnumValue
|
||||||
|
|||||||
@@ -1,49 +0,0 @@
|
|||||||
package com.sa.zentao.enums;
|
|
||||||
|
|
||||||
public enum KanbanColumnType {
|
|
||||||
|
|
||||||
// 需求看板列类型
|
|
||||||
STORY_BACKLOG("backlog", "待办"),
|
|
||||||
STORY_DEVELOPING("developing", "研发中"),
|
|
||||||
STORY_DEVELOPED("developed", "研发完毕"),
|
|
||||||
STORY_TESTING("testing", "测试中"),
|
|
||||||
STORY_TESTED("tested", "测试完毕"),
|
|
||||||
STORY_RELEASED("released", "已发布"),
|
|
||||||
STORY_VERIFIED("verified", "已验收"),
|
|
||||||
STORY_CLOSED("closed", "已关闭"),
|
|
||||||
|
|
||||||
// Bug看板列类型
|
|
||||||
BUG_UNCONFIRMED("unconfirmed", "未确认"),
|
|
||||||
BUG_CONFIRMED("confirmed", "已确认"),
|
|
||||||
BUG_RESOLVED("resolved", "已解决"),
|
|
||||||
BUG_CLOSED("closed", "已关闭"),
|
|
||||||
|
|
||||||
// 任务看板列类型
|
|
||||||
TASK_WAIT("wait", "等待"),
|
|
||||||
TASK_DEVELOPING("developing", "进行中"),
|
|
||||||
TASK_DEVELOPED("developed", "已完成"),
|
|
||||||
TASK_CANCEL("cancel", "已取消"),
|
|
||||||
TASK_CLOSED("closed", "已关闭"),
|
|
||||||
|
|
||||||
// 通用列类型
|
|
||||||
WAIT("wait", "等待"),
|
|
||||||
DOING("doing", "进行中"),
|
|
||||||
DONE("done", "已完成"),
|
|
||||||
CLOSED("closed", "已关闭");
|
|
||||||
|
|
||||||
private String value;
|
|
||||||
private String desc;
|
|
||||||
|
|
||||||
private KanbanColumnType(String value, String desc) {
|
|
||||||
this.value = value;
|
|
||||||
this.desc = desc;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getValue() {
|
|
||||||
return this.value;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getDesc() {
|
|
||||||
return this.desc;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -16,8 +16,6 @@ public enum StoryStageEnums {
|
|||||||
developed(10, "developed","研发完毕"),
|
developed(10, "developed","研发完毕"),
|
||||||
testing(5, "testing","测试中"),
|
testing(5, "testing","测试中"),
|
||||||
tested(6, "tested","测试完毕"),
|
tested(6, "tested","测试完毕"),
|
||||||
productWaitVerified(9, "productWaitVerified","产品验收中"),
|
|
||||||
productVerified(10, "productVerified","产品已验收"),
|
|
||||||
released(7, "released","已发布"),
|
released(7, "released","已发布"),
|
||||||
verified(8, "verified","已验收"),
|
verified(8, "verified","已验收"),
|
||||||
;
|
;
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.sa.zentao.mapper;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|
||||||
import com.sa.zentao.entity.ZtDocCheck;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Mapper 接口
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
public interface ZtDocCheckMapper extends BaseMapper<ZtDocCheck> {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.sa.zentao.mapper;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|
||||||
import com.sa.zentao.entity.ZtPerfConfig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Mapper 接口
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
public interface ZtPerfConfigMapper extends BaseMapper<ZtPerfConfig> {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Mapper 接口
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author gqb
|
|
||||||
* @since 2026-03-30
|
|
||||||
*/
|
|
||||||
public interface ZtStoryExpandMapper extends BaseMapper<ZtStoryExpand> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 按月份和项目查询,关联 zt_story 获取需求名称
|
|
||||||
* @param month 月份,格式:yyyy-MM
|
|
||||||
* @param projectId 产品项目 id(zt_project.id),为 null 时不过滤
|
|
||||||
* @return 需求扩展信息列表(含 storyTitle)
|
|
||||||
*/
|
|
||||||
List<ZtStoryExpand> queryByMonthWithTitle(@Param("month") String month, @Param("projectId") Integer projectId);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -10,7 +10,6 @@ import org.apache.ibatis.annotations.Param;
|
|||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -60,7 +59,4 @@ public interface ZtStoryMapper extends BaseMapper<ZtStory> {
|
|||||||
* @return
|
* @return
|
||||||
*/
|
*/
|
||||||
List<SearchDTO> searchAll(@Param("keyword") String keyword,@Param("pIds")List<Integer> pIds);
|
List<SearchDTO> searchAll(@Param("keyword") String keyword,@Param("pIds")List<Integer> pIds);
|
||||||
|
|
||||||
Map<String, Object> storyReleaseOnTimeByProducts(@Param("pIds") List<Integer> pids,
|
|
||||||
@Param("startTime") Date firstDayOfMonth,@Param("endTime") Date lastDayOfMonth);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.sa.zentao.mapper;
|
|
||||||
|
|
||||||
import com.sa.zentao.entity.ZtStoryMonthWorkload;
|
|
||||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* Mapper 接口
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author gqb
|
|
||||||
* @since 2026-03-30
|
|
||||||
*/
|
|
||||||
public interface ZtStoryMonthWorkloadMapper extends BaseMapper<ZtStoryMonthWorkload> {
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -28,12 +28,6 @@ public interface ZtTaskMapper extends BaseMapper<ZtTask> {
|
|||||||
|
|
||||||
List<ItApproval> itApprovalByUserName(@Param("name") String name, @Param("startDate") Date firstDayOfMonth, @Param("endDate") Date lastDayOfMonth);
|
List<ItApproval> itApprovalByUserName(@Param("name") String name, @Param("startDate") Date firstDayOfMonth, @Param("endDate") Date lastDayOfMonth);
|
||||||
|
|
||||||
/**
|
|
||||||
* 按姓名批量查询请假审批(条件与 itApprovalByUserName 完全一致,仅 name 改为 IN 批量;
|
|
||||||
* 供 storyBarChart 月度循环批量化使用,老方法保留不动)
|
|
||||||
*/
|
|
||||||
List<ItApproval> itApprovalsByNames(@Param("names") List<String> names, @Param("startDate") Date firstDayOfMonth, @Param("endDate") Date lastDayOfMonth);
|
|
||||||
|
|
||||||
List<ZtTask> taskListByIds(@Param("ids") List<String> ids);
|
List<ZtTask> taskListByIds(@Param("ids") List<String> ids);
|
||||||
|
|
||||||
List<ZtTask> taskListByStoryIds(@Param("ids") List<Integer> sIds);
|
List<ZtTask> taskListByStoryIds(@Param("ids") List<Integer> sIds);
|
||||||
|
|||||||
@@ -18,27 +18,15 @@ public class StoryQo extends BaseQo {
|
|||||||
private String assignedTo;
|
private String assignedTo;
|
||||||
private String userName;
|
private String userName;
|
||||||
private String module;
|
private String module;
|
||||||
/**
|
|
||||||
* 多个模块,逗号分割
|
|
||||||
*/
|
|
||||||
private String modules;
|
|
||||||
private String searchVal;
|
private String searchVal;
|
||||||
private Integer productId;
|
private Integer productId;
|
||||||
private String openedby;
|
private String openedby;
|
||||||
private String status;
|
private String status;
|
||||||
/**
|
|
||||||
* 用户产品
|
|
||||||
*/
|
|
||||||
private String productUser;
|
|
||||||
private List<Integer> productIds;
|
private List<Integer> productIds;
|
||||||
|
|
||||||
private List<String> statusList;
|
private List<String> statusList;
|
||||||
|
|
||||||
private String ids;
|
private String ids;
|
||||||
/**
|
|
||||||
* 迭代
|
|
||||||
*/
|
|
||||||
private Integer execution;
|
|
||||||
|
|
||||||
private List<String> storyIds;
|
private List<String> storyIds;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -104,14 +104,4 @@ public class ZtComputerSourceQo extends BaseQo {
|
|||||||
|
|
||||||
private String ip;
|
private String ip;
|
||||||
|
|
||||||
/**
|
|
||||||
* 使用环境:生产环境/备用环境/开发环境/测试环境/UAT环境/办公环境
|
|
||||||
*/
|
|
||||||
private String useEnv;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 到期时间
|
|
||||||
*/
|
|
||||||
private Date expireDate;
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,52 +0,0 @@
|
|||||||
package com.sa.zentao.qo;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
import lombok.EqualsAndHashCode;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* 文档齐备核查入参(FR-014 三期):generateDocCheck 用 month;
|
|
||||||
* docCheckAppeal 用 storyId/month/docType/reason;docCheckAppealResolve 另加 pass/remark
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
@EqualsAndHashCode(callSuper = false)
|
|
||||||
public class ZtDocCheckQo implements Serializable {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 月份 yyyy-MM
|
|
||||||
*/
|
|
||||||
private String month;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 需求ID
|
|
||||||
*/
|
|
||||||
private Integer storyId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文档类型 testCase/testReport/aiCodeReview/aiWorkLog/docUpdate
|
|
||||||
*/
|
|
||||||
private String docType;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 申诉理由
|
|
||||||
*/
|
|
||||||
private String reason;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 复核是否通过
|
|
||||||
*/
|
|
||||||
private Boolean pass;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 复核备注
|
|
||||||
*/
|
|
||||||
private String remark;
|
|
||||||
}
|
|
||||||
@@ -1,66 +0,0 @@
|
|||||||
package com.sa.zentao.qo;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绩效报表/评分/申诉/配置 请求参数(三期批2 /zt-perf)
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class ZtPerfQo implements Serializable {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配置维护时为 zt_perf_config.id;申诉复核时为申诉 id(account@month#序号)
|
|
||||||
*/
|
|
||||||
private String id;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 月份 yyyy-MM
|
|
||||||
*/
|
|
||||||
private String month;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 岗位
|
|
||||||
*/
|
|
||||||
private String role;
|
|
||||||
|
|
||||||
private String account;
|
|
||||||
|
|
||||||
private String itemKey;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 人工评分分值 0~100
|
|
||||||
*/
|
|
||||||
private Double score;
|
|
||||||
|
|
||||||
private String remark;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 申诉原因
|
|
||||||
*/
|
|
||||||
private String reason;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 复核结果:upheld 维持 / overturned 撤销
|
|
||||||
*/
|
|
||||||
private String result;
|
|
||||||
|
|
||||||
private String reviewer;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 配置项字段(/zt-perf/config 维护用)
|
|
||||||
*/
|
|
||||||
private String itemName;
|
|
||||||
|
|
||||||
private Double weight;
|
|
||||||
|
|
||||||
private String ruleJson;
|
|
||||||
|
|
||||||
private Integer enabled;
|
|
||||||
}
|
|
||||||
@@ -37,6 +37,7 @@ public class ZtProjectQo extends BaseQo {
|
|||||||
private String severity;
|
private String severity;
|
||||||
|
|
||||||
private String productName;
|
private String productName;
|
||||||
|
|
||||||
private Integer productId;
|
private Integer productId;
|
||||||
|
|
||||||
private List<Integer> productIds;
|
private List<Integer> productIds;
|
||||||
@@ -63,8 +64,6 @@ public class ZtProjectQo extends BaseQo {
|
|||||||
|
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
private List<String> statusList;
|
|
||||||
|
|
||||||
private List<String> stageList;
|
private List<String> stageList;
|
||||||
|
|
||||||
private String title;
|
private String title;
|
||||||
@@ -120,8 +119,4 @@ public class ZtProjectQo extends BaseQo {
|
|||||||
private String account;
|
private String account;
|
||||||
//1 延期 2不延期
|
//1 延期 2不延期
|
||||||
private Integer delayFlag=0 ;
|
private Integer delayFlag=0 ;
|
||||||
/**
|
|
||||||
* 验收人
|
|
||||||
*/
|
|
||||||
private String ysUser;
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,60 +0,0 @@
|
|||||||
package com.sa.zentao.service;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
|
||||||
import com.sa.zentao.entity.ZtDocCheck;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* 大型需求文档齐备核查 服务类(FR-014 三期)
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
public interface IZtDocCheckService extends IService<ZtDocCheck> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 生成某月文档齐备快照:大型需求(workload_index>20 且当月有完成度变化)× 五类文档,
|
|
||||||
* 结果写 zt_doc_check,并按 缺失数×2 合并写 zt_month_score.scopeJson 的 docCheck 节点
|
|
||||||
* @param month 考核月份 yyyy-MM
|
|
||||||
* @return 摘要 {month, storyCount, checkRows, deductByAccount}
|
|
||||||
*/
|
|
||||||
Map<String, Object> generateMonthSnapshot(String month);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 申诉:缺失项状态 pending → appealing
|
|
||||||
* @param storyId 需求ID
|
|
||||||
* @param month 月份 yyyy-MM
|
|
||||||
* @param docType 文档类型
|
|
||||||
* @param reason 申诉理由
|
|
||||||
*/
|
|
||||||
void appeal(Integer storyId, String month, String docType, String reason);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 复核:appealing → resolved;pass 时回滚该账号当月 docCheck 扣分
|
|
||||||
* @param storyId 需求ID
|
|
||||||
* @param month 月份 yyyy-MM
|
|
||||||
* @param docType 文档类型
|
|
||||||
* @param pass 复核是否通过
|
|
||||||
* @param remark 复核备注
|
|
||||||
*/
|
|
||||||
void resolveAppeal(Integer storyId, String month, String docType, boolean pass, String remark);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 某月的全部核查记录(供绩效 calculator 取数)
|
|
||||||
*/
|
|
||||||
List<ZtDocCheck> listMonthRows(String month);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文档齐备矩阵页数据(按需求聚合:docs 五类判定/扣分/状态/申诉状态)
|
|
||||||
*/
|
|
||||||
List<Map<String, Object>> listMatrix(String month);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 月度确认:该月快照全部标记 CONFIRMED(项目经理确认后锁定)
|
|
||||||
*/
|
|
||||||
void confirmMonth(String month);
|
|
||||||
}
|
|
||||||
@@ -16,16 +16,4 @@ public interface IZtFileService extends IService<ZtFile> {
|
|||||||
|
|
||||||
|
|
||||||
void updateFile(String fileIds, Integer objectId, FileTypes type);
|
void updateFile(String fileIds, Integer objectId, FileTypes type);
|
||||||
|
|
||||||
/**
|
|
||||||
* uploadBind 绑定后按 objectType 刷新主表访问链接字段
|
|
||||||
* story→zt_story.prd_url;aiCodeReview→code_review_url+code_review_status;
|
|
||||||
* aiWorkLog→work_log_url;testCase→test_case_url;testReport→test_report_download_url;
|
|
||||||
* testReportSubmit→test_report_submit_url;testOther→test_other_url;meeting→zt_meeting.url
|
|
||||||
* @param objectType 附件类型(FileTypes.code)
|
|
||||||
* @param objectId 主表记录 id(zt_story.id / zt_meeting.id)
|
|
||||||
* @param url 附件访问链接
|
|
||||||
* @param reviewResult 审查结果 pass/reject,仅 objectType=aiCodeReview 时可选
|
|
||||||
*/
|
|
||||||
void refreshOwnerUrl(String objectType, Integer objectId, String url, String reviewResult);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
package com.sa.zentao.service;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
|
||||||
import com.sa.zentao.entity.ZtPerfConfig;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* 绩效指标规则配置 服务类(三期)
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
public interface IZtPerfConfigService extends IService<ZtPerfConfig> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 取岗位某指标的启用配置;role 为空或无匹配时回退到该指标任意一条启用配置
|
|
||||||
* @param role 岗位编码(可为空)
|
|
||||||
* @param itemKey 指标编码
|
|
||||||
* @return 配置,无则 null
|
|
||||||
*/
|
|
||||||
ZtPerfConfig getEnabledConfig(String role, String itemKey);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 按账号解析岗位编码(依据 zt_user.userType;前后端无法区分时默认 backendDev)
|
|
||||||
* @param account 账号
|
|
||||||
* @return 岗位编码,无法识别返回 null
|
|
||||||
*/
|
|
||||||
String resolveRole(String account);
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
package com.sa.zentao.service;
|
|
||||||
|
|
||||||
import com.sa.zentao.qo.ZtPerfQo;
|
|
||||||
import com.sa.zentao.service.perf.PerfMonthScope;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* 绩效报表(三期批2:报表引擎+接口) 服务类
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
public interface IZtPerfReportService {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 按岗位 tab 报表:已落库读 zt_month_score.scopeJson,未落库实时计算(不写库)
|
|
||||||
*/
|
|
||||||
PerfMonthScope report(String month, String role, String account);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批量生成/重算月度得分写 zt_month_score.scopeJson;重算保留已有人工评分与申诉记录
|
|
||||||
*/
|
|
||||||
void generateMonthScore(String month, String role, String account);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 人工评分合并写 scopeJson(PRD §7.5 ❌ 项录入入口)
|
|
||||||
*/
|
|
||||||
void manualScore(ZtPerfQo qo);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 申诉登记:写 scopeJson.appeals(id 规则 account@month#序号)
|
|
||||||
*/
|
|
||||||
void appeal(ZtPerfQo qo);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 申诉复核:upheld 维持原判;overturned 撤销扣分回滚该项为满分并重算总分
|
|
||||||
*/
|
|
||||||
void appealReview(ZtPerfQo qo);
|
|
||||||
}
|
|
||||||
@@ -57,6 +57,4 @@ public interface IZtProductService extends IService<ZtProduct> {
|
|||||||
|
|
||||||
List<ZtProduct> productListByProgramName(String programName);
|
List<ZtProduct> productListByProgramName(String programName);
|
||||||
|
|
||||||
PageInfo<ZtProjectDTO> getExecutionByProductId(ZtProjectQo qo);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
package com.sa.zentao.service;
|
|
||||||
|
|
||||||
import com.sa.zentao.entity.ZtStoryExpand;
|
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* 服务类
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author gqb
|
|
||||||
* @since 2026-03-30
|
|
||||||
*/
|
|
||||||
public interface IZtStoryExpandService extends IService<ZtStoryExpand> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存或更新(有数据了更新不新增)
|
|
||||||
* @param entity 需求扩展信息
|
|
||||||
*/
|
|
||||||
void saveOrUpdateExpand(ZtStoryExpand entity);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 按月份和项目查询(不分页,含需求名称)
|
|
||||||
* @param month 月份,格式:yyyy-MM
|
|
||||||
* @param projectId 产品项目 id,为 null 时不过滤
|
|
||||||
* @return 需求扩展信息列表(含 storyTitle)
|
|
||||||
*/
|
|
||||||
List<ZtStoryExpand> queryByMonth(String month, Integer projectId);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 打分接口
|
|
||||||
* @param entity 需求扩展信息
|
|
||||||
*/
|
|
||||||
void saveScore(ZtStoryExpand entity);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,40 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* 服务类
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author gqb
|
|
||||||
* @since 2026-03-30
|
|
||||||
*/
|
|
||||||
public interface IZtStoryMonthWorkloadService extends IService<ZtStoryMonthWorkload> {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 保存完成度
|
|
||||||
* @param entity 月度工作量信息
|
|
||||||
*/
|
|
||||||
void saveCompletionDegree(ZtStoryMonthWorkload entity);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取所有不重复的月份列表
|
|
||||||
* @return 月份列表,格式 yyyy-MM
|
|
||||||
*/
|
|
||||||
List<String> listMonths();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 按 storyId + 月份查询单条记录
|
|
||||||
*/
|
|
||||||
ZtStoryMonthWorkload getByStoryIdAndMonth(Integer storyId, String month);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 查询某需求在指定月份之前(不含)的历史最高完成度
|
|
||||||
*/
|
|
||||||
BigDecimal getMaxWorkloadBefore(Integer storyId, String month);
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -57,7 +57,7 @@ public interface IZtStoryService extends IService<ZtStory> {
|
|||||||
void waitStory(Integer story);
|
void waitStory(Integer story);
|
||||||
void startStory(Integer story);
|
void startStory(Integer story);
|
||||||
//开发完成
|
//开发完成
|
||||||
void develFinishStory(Integer story,String finishBy);
|
void finishStory(Integer story,String finishBy);
|
||||||
//测试完毕
|
//测试完毕
|
||||||
void testedStory(Integer story,String finishBy);
|
void testedStory(Integer story,String finishBy);
|
||||||
//测试中
|
//测试中
|
||||||
@@ -77,11 +77,7 @@ public interface IZtStoryService extends IService<ZtStory> {
|
|||||||
void storyYs(ZtStoryDTO dto);
|
void storyYs(ZtStoryDTO dto);
|
||||||
|
|
||||||
ZtStoryDTO getStoryById(Integer id,Integer execution);
|
ZtStoryDTO getStoryById(Integer id,Integer execution);
|
||||||
/**
|
|
||||||
* 迭代 跟进项目
|
|
||||||
* @param dto
|
|
||||||
* @return
|
|
||||||
*/
|
|
||||||
List<ZtProject> execListByProject(ZtStoryDTO dto);
|
List<ZtProject> execListByProject(ZtStoryDTO dto);
|
||||||
|
|
||||||
void changeAssignedTo(ZtStoryDTO dto);
|
void changeAssignedTo(ZtStoryDTO dto);
|
||||||
@@ -109,15 +105,4 @@ public interface IZtStoryService extends IService<ZtStory> {
|
|||||||
List<ZtStory> getStoryListByDatePidsProject(List<Integer> pids, Integer id, Date firstDayOfMonth, Date lastDayOfMonth);
|
List<ZtStory> getStoryListByDatePidsProject(List<Integer> pids, Integer id, Date firstDayOfMonth, Date lastDayOfMonth);
|
||||||
|
|
||||||
PageInfo<SearchDTO> searchByName(SearchQo qo);
|
PageInfo<SearchDTO> searchByName(SearchQo qo);
|
||||||
|
|
||||||
void testSubmitVerified(ZtStoryDTO qo);
|
|
||||||
|
|
||||||
void storyProductUserYs(ZtStoryDTO qo);
|
|
||||||
|
|
||||||
Map<String, Object> storyReleaseOnTimeByProducts(@Param("pids") List<Integer> pids, @Param("startTime")Date firstDayOfMonth,
|
|
||||||
@Param("endTime")Date lastDayOfMonth);
|
|
||||||
|
|
||||||
void assignedToProductUser(ZtStoryDTO dto);
|
|
||||||
|
|
||||||
void assignedToTestUser(ZtStoryDTO dto);
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -54,7 +54,4 @@ public interface IZtStoryUserService extends IService<ZtStoryUser> {
|
|||||||
void confirmStory(ZtStoryUserDTO dto);
|
void confirmStory(ZtStoryUserDTO dto);
|
||||||
|
|
||||||
void needMeetOrDesign(ZtStoryUserDTO dto);
|
void needMeetOrDesign(ZtStoryUserDTO dto);
|
||||||
|
|
||||||
void assignedToProductUser(ZtStoryUserDTO dto);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@ package com.sa.zentao.service;
|
|||||||
|
|
||||||
import com.github.pagehelper.PageInfo;
|
import com.github.pagehelper.PageInfo;
|
||||||
import com.sa.zentao.dao.ItApproval;
|
import com.sa.zentao.dao.ItApproval;
|
||||||
import com.sa.zentao.dao.ZtTaskAiBatchDTO;
|
|
||||||
import com.sa.zentao.dao.ZtTaskDTO;
|
import com.sa.zentao.dao.ZtTaskDTO;
|
||||||
import com.sa.zentao.entity.ZtTask;
|
import com.sa.zentao.entity.ZtTask;
|
||||||
import com.baomidou.mybatisplus.extension.service.IService;
|
import com.baomidou.mybatisplus.extension.service.IService;
|
||||||
@@ -10,7 +9,6 @@ import com.sa.zentao.qo.ZtProjectQo;
|
|||||||
|
|
||||||
import java.util.Date;
|
import java.util.Date;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
@@ -40,14 +38,6 @@ public interface IZtTaskService extends IService<ZtTask> {
|
|||||||
|
|
||||||
void batchAddTask(ZtTaskDTO dto);
|
void batchAddTask(ZtTaskDTO dto);
|
||||||
|
|
||||||
/**
|
|
||||||
* AI 批量创建任务(事务):storyId 不存在或 type 非 devel/test 整批拒绝;
|
|
||||||
* 同 storyId+name+type 已存在跳过记 skipped;创建 zt_task(status=wait、创建人=ai、estimate=aiEvaluationTime)
|
|
||||||
* @param dto 报文 {"storyId":N,"tasks":[...]}
|
|
||||||
* @return {"created":N,"taskIds":[...],"skipped":[...]}
|
|
||||||
*/
|
|
||||||
Map<String, Object> aiBatchAdd(ZtTaskAiBatchDTO dto);
|
|
||||||
|
|
||||||
void approval(ZtTaskDTO dto);
|
void approval(ZtTaskDTO dto);
|
||||||
|
|
||||||
PageInfo<ZtTaskDTO> myTaskPageList(ZtProjectQo qo);
|
PageInfo<ZtTaskDTO> myTaskPageList(ZtProjectQo qo);
|
||||||
@@ -66,12 +56,6 @@ public interface IZtTaskService extends IService<ZtTask> {
|
|||||||
|
|
||||||
List<ItApproval> itApprovalByUserName(String s, Date firstDayOfMonth, Date lastDayOfMonth);
|
List<ItApproval> itApprovalByUserName(String s, Date firstDayOfMonth, Date lastDayOfMonth);
|
||||||
|
|
||||||
/**
|
|
||||||
* 按姓名批量查询请假审批(条件与 itApprovalByUserName 一致,name 改 IN;
|
|
||||||
* 供 storyBarChart 月度循环批量化,老方法保留不动)
|
|
||||||
*/
|
|
||||||
List<ItApproval> itApprovalsByNames(List<String> names, Date firstDayOfMonth, Date lastDayOfMonth);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
* @param firstDayOfMonth 开始
|
* @param firstDayOfMonth 开始
|
||||||
|
|||||||
@@ -46,9 +46,7 @@ public class FestivalConfigServiceImpl extends ServiceImpl<FestivalConfigMapper,
|
|||||||
|
|
||||||
|
|
||||||
|
|
||||||
List<FestivalConfig> festivalConfigs = this.baseMapper.selectList(new QueryWrapper<FestivalConfig>().lambda()
|
List<FestivalConfig> festivalConfigs = this.baseMapper.selectList(new QueryWrapper<FestivalConfig>().lambda().eq(FestivalConfig::getName, qo.getName()).
|
||||||
.eq(FestivalConfig::getYear,qo.getYear())
|
|
||||||
.eq(FestivalConfig::getName, qo.getName()).
|
|
||||||
eq(FestivalConfig::getDeleteFlag, DeleteFlagEnum.USED));
|
eq(FestivalConfig::getDeleteFlag, DeleteFlagEnum.USED));
|
||||||
if(!CollectionUtils.isEmpty(festivalConfigs)){
|
if(!CollectionUtils.isEmpty(festivalConfigs)){
|
||||||
throw new BusinessException("已存在");
|
throw new BusinessException("已存在");
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -526,8 +526,7 @@ public class ZtBugServiceImpl extends ServiceImpl<ZtBugMapper, ZtBug> implements
|
|||||||
ztBugs = this.baseMapper.selectList(new QueryWrapper<ZtBug>().lambda().eq(ZtBug::getTask, task));
|
ztBugs = this.baseMapper.selectList(new QueryWrapper<ZtBug>().lambda().eq(ZtBug::getTask, task));
|
||||||
}
|
}
|
||||||
if(story!=null&&story!=0){
|
if(story!=null&&story!=0){
|
||||||
//CHG-067:入参是 toStory 的需求ID,关联查询应打 toStory 列(story 列全库未用)
|
ztBugs = this.baseMapper.selectList(new QueryWrapper<ZtBug>().lambda().eq(ZtBug::getStory, story));
|
||||||
ztBugs = this.baseMapper.selectList(new QueryWrapper<ZtBug>().lambda().eq(ZtBug::getTostory, story));
|
|
||||||
}
|
}
|
||||||
List<ZtBugDTO> ztBugDTOS =new ArrayList<>();
|
List<ZtBugDTO> ztBugDTOS =new ArrayList<>();
|
||||||
if(!CollectionUtils.isEmpty(ztBugs)){
|
if(!CollectionUtils.isEmpty(ztBugs)){
|
||||||
|
|||||||
@@ -1,464 +0,0 @@
|
|||||||
package com.sa.zentao.service.impl;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import com.alibaba.fastjson.JSONArray;
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|
||||||
import com.sa.zentao.dao.BusinessException;
|
|
||||||
import com.sa.zentao.entity.ZtCase;
|
|
||||||
import com.sa.zentao.entity.ZtDocCheck;
|
|
||||||
import com.sa.zentao.entity.ZtFile;
|
|
||||||
import com.sa.zentao.entity.ZtMonthScore;
|
|
||||||
import com.sa.zentao.entity.ZtStory;
|
|
||||||
import com.sa.zentao.entity.ZtStoryExpand;
|
|
||||||
import com.sa.zentao.entity.ZtStoryMonthWorkload;
|
|
||||||
import com.sa.zentao.mapper.ZtDocCheckMapper;
|
|
||||||
import com.sa.zentao.service.IZtCaseService;
|
|
||||||
import com.sa.zentao.service.IZtDocCheckService;
|
|
||||||
import com.sa.zentao.service.IZtFileService;
|
|
||||||
import com.sa.zentao.service.IZtMonthScoreService;
|
|
||||||
import com.sa.zentao.service.IZtStoryExpandService;
|
|
||||||
import com.sa.zentao.service.IZtStoryMonthWorkloadService;
|
|
||||||
import com.sa.zentao.service.IZtStoryService;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* 大型需求文档齐备核查 服务实现类(FR-014 三期)
|
|
||||||
* 大型需求 = zt_story_expand.workload_index > 20 且当月有完成度变化(zt_story_month_workload.time=month)
|
|
||||||
* 五类文档判定源:
|
|
||||||
* 《需求测试用例》=zt_case(story 关联存在);《需求测试报告》=zt_file(testReportSubmit);
|
|
||||||
* 《AI代码审查报告》=zt_file(aiCodeReview);《AI工作日志》=zt_file(aiWorkLog);
|
|
||||||
* 《AI项目文档更新记录》=zt_file(aiWorkLog) 且 extra/title 含 doc_update(宽松匹配)
|
|
||||||
* 扣分 = 缺失数×2,合并写 zt_month_score.scopeJson 的 docCheck 节点(account+dateStr 维度)
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
@Slf4j
|
|
||||||
public class ZtDocCheckServiceImpl extends ServiceImpl<ZtDocCheckMapper, ZtDocCheck> implements IZtDocCheckService {
|
|
||||||
|
|
||||||
/** 《需求测试用例》 */
|
|
||||||
public static final String DOC_TEST_CASE = "testCase";
|
|
||||||
/** 《需求测试报告》(判定源 zt_file objectType=testReportSubmit) */
|
|
||||||
public static final String DOC_TEST_REPORT = "testReport";
|
|
||||||
/** 《AI代码审查报告》 */
|
|
||||||
public static final String DOC_AI_CODE_REVIEW = "aiCodeReview";
|
|
||||||
/** 《AI工作日志》 */
|
|
||||||
public static final String DOC_AI_WORK_LOG = "aiWorkLog";
|
|
||||||
/** 《AI项目文档更新记录》(zt_file(aiWorkLog) 中 doc_update 类内容) */
|
|
||||||
public static final String DOC_DOC_UPDATE = "docUpdate";
|
|
||||||
|
|
||||||
public static final List<String> DOC_TYPES = Arrays.asList(
|
|
||||||
DOC_TEST_CASE, DOC_TEST_REPORT, DOC_AI_CODE_REVIEW, DOC_AI_WORK_LOG, DOC_DOC_UPDATE);
|
|
||||||
|
|
||||||
/** 大型需求阈值:AI 评估工作量指数 > 20 */
|
|
||||||
public static final BigDecimal LARGE_WORKLOAD_INDEX = new BigDecimal("20");
|
|
||||||
|
|
||||||
/** 每缺失一份扣分 */
|
|
||||||
public static final int DEDUCT_PER_MISSING = 2;
|
|
||||||
|
|
||||||
/** 系统写入人 */
|
|
||||||
private static final String SYSTEM_USER = "system";
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtStoryExpandService storyExpandService;
|
|
||||||
@Autowired
|
|
||||||
private IZtStoryMonthWorkloadService storyMonthWorkloadService;
|
|
||||||
@Autowired
|
|
||||||
private IZtStoryService storyService;
|
|
||||||
@Autowired
|
|
||||||
private IZtCaseService caseService;
|
|
||||||
@Autowired
|
|
||||||
private IZtFileService fileService;
|
|
||||||
@Autowired
|
|
||||||
private IZtMonthScoreService monthScoreService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public Map<String, Object> generateMonthSnapshot(String month) {
|
|
||||||
if (StringUtils.isEmpty(month)) {
|
|
||||||
throw new BusinessException("月份不能为空");
|
|
||||||
}
|
|
||||||
List<Integer> storyIds = listLargeStoryIds(month);
|
|
||||||
Date now = new Date();
|
|
||||||
int checkRows = 0;
|
|
||||||
for (Integer storyId : storyIds) {
|
|
||||||
for (String docType : DOC_TYPES) {
|
|
||||||
ZtDocCheck row = checkOne(storyId, month, docType, now);
|
|
||||||
upsertRow(row);
|
|
||||||
checkRows++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 按责任人(zt_story.assignedTo)归集缺失扣分
|
|
||||||
Map<String, BigDecimal> deductByAccount = new HashMap<>();
|
|
||||||
if (!CollectionUtils.isEmpty(storyIds)) {
|
|
||||||
List<ZtDocCheck> rows = this.list(new QueryWrapper<ZtDocCheck>().lambda()
|
|
||||||
.eq(ZtDocCheck::getMonth, month).in(ZtDocCheck::getStoryId, storyIds));
|
|
||||||
List<ZtStory> stories = storyService.listByIds(storyIds);
|
|
||||||
Map<Integer, ZtStory> storyMap = stories.stream().collect(Collectors.toMap(ZtStory::getId, o -> o, (a, b) -> a));
|
|
||||||
Set<String> accounts = new HashSet<>();
|
|
||||||
for (ZtStory story : stories) {
|
|
||||||
if (StringUtils.isNotEmpty(story.getAssignedTo())) {
|
|
||||||
accounts.add(story.getAssignedTo());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (String account : accounts) {
|
|
||||||
BigDecimal deduct = writeAccountDocCheckNode(account, month, rows, storyMap);
|
|
||||||
deductByAccount.put(account, deduct);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
result.put("month", month);
|
|
||||||
result.put("storyCount", storyIds.size());
|
|
||||||
result.put("checkRows", checkRows);
|
|
||||||
result.put("deductByAccount", deductByAccount);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public void appeal(Integer storyId, String month, String docType, String reason) {
|
|
||||||
if (storyId == null || StringUtils.isEmpty(month) || StringUtils.isEmpty(docType)) {
|
|
||||||
throw new BusinessException("storyId/month/docType 不能为空");
|
|
||||||
}
|
|
||||||
if (StringUtils.isEmpty(reason)) {
|
|
||||||
throw new BusinessException("申诉理由不能为空");
|
|
||||||
}
|
|
||||||
ZtDocCheck row = getRow(storyId, month, docType);
|
|
||||||
if (row == null) {
|
|
||||||
throw new BusinessException("核查记录不存在,请先生成当月快照");
|
|
||||||
}
|
|
||||||
if (row.getPresent() != null && row.getPresent() == 1) {
|
|
||||||
throw new BusinessException("该文档已齐备,无需申诉");
|
|
||||||
}
|
|
||||||
if (!ZtDocCheck.STATUS_PENDING.equals(row.getStatus())) {
|
|
||||||
throw new BusinessException("当前状态不可申诉(仅待确认记录可申诉)");
|
|
||||||
}
|
|
||||||
row.setStatus(ZtDocCheck.STATUS_APPEALING);
|
|
||||||
row.setAppealReason(reason);
|
|
||||||
row.setUpdateTime(new Date());
|
|
||||||
this.updateById(row);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public void resolveAppeal(Integer storyId, String month, String docType, boolean pass, String remark) {
|
|
||||||
if (storyId == null || StringUtils.isEmpty(month) || StringUtils.isEmpty(docType)) {
|
|
||||||
throw new BusinessException("storyId/month/docType 不能为空");
|
|
||||||
}
|
|
||||||
ZtDocCheck row = getRow(storyId, month, docType);
|
|
||||||
if (row == null) {
|
|
||||||
throw new BusinessException("核查记录不存在");
|
|
||||||
}
|
|
||||||
if (!ZtDocCheck.STATUS_APPEALING.equals(row.getStatus())) {
|
|
||||||
throw new BusinessException("仅申诉中记录可复核");
|
|
||||||
}
|
|
||||||
row.setStatus(ZtDocCheck.STATUS_RESOLVED);
|
|
||||||
row.setAppealResult(pass ? ZtDocCheck.APPEAL_PASS : ZtDocCheck.APPEAL_REJECT);
|
|
||||||
row.setResolveRemark(remark);
|
|
||||||
row.setUpdateTime(new Date());
|
|
||||||
this.updateById(row);
|
|
||||||
// 复核通过 → 回滚该账号当月 docCheck 扣分(重算责任人维度缺失)
|
|
||||||
ZtStory story = storyService.getById(storyId);
|
|
||||||
if (story != null && StringUtils.isNotEmpty(story.getAssignedTo())) {
|
|
||||||
recomputeAccountDeduction(story.getAssignedTo(), month);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<ZtDocCheck> listMonthRows(String month) {
|
|
||||||
if (StringUtils.isEmpty(month)) {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
return this.list(new QueryWrapper<ZtDocCheck>().lambda().eq(ZtDocCheck::getMonth, month));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<Map<String, Object>> listMatrix(String month) {
|
|
||||||
List<ZtDocCheck> rows = listMonthRows(month);
|
|
||||||
Map<Integer, List<ZtDocCheck>> byStory = rows.stream().collect(Collectors.groupingBy(ZtDocCheck::getStoryId));
|
|
||||||
List<Map<String, Object>> result = new ArrayList<>();
|
|
||||||
if (byStory.isEmpty()) {
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
List<ZtStory> stories = storyService.listByIds(byStory.keySet());
|
|
||||||
Map<Integer, String> titleMap = stories.stream()
|
|
||||||
.collect(Collectors.toMap(ZtStory::getId, ZtStory::getTitle, (a, b) -> a));
|
|
||||||
Map<Integer, String> wiMap = new HashMap<>();
|
|
||||||
for (ZtStoryExpand e : storyExpandService.list(
|
|
||||||
new QueryWrapper<ZtStoryExpand>().lambda().in(ZtStoryExpand::getStoryId, byStory.keySet()))) {
|
|
||||||
wiMap.put(e.getStoryId(), e.getWorkloadIndex());
|
|
||||||
}
|
|
||||||
for (Map.Entry<Integer, List<ZtDocCheck>> en : byStory.entrySet()) {
|
|
||||||
Map<String, Object> row = new HashMap<>();
|
|
||||||
Map<String, Integer> docs = new HashMap<>();
|
|
||||||
int missing = 0;
|
|
||||||
String status = null;
|
|
||||||
String appealStatus = null;
|
|
||||||
for (ZtDocCheck c : en.getValue()) {
|
|
||||||
int present = (c.getPresent() != null && c.getPresent() == 1) ? 1 : 0;
|
|
||||||
docs.put(c.getDocType(), present);
|
|
||||||
if (present == 0 && c.countsAsMissing()) {
|
|
||||||
missing++;
|
|
||||||
}
|
|
||||||
if (StringUtils.isNotEmpty(c.getStatus())) {
|
|
||||||
status = c.getStatus();
|
|
||||||
}
|
|
||||||
if (StringUtils.isNotEmpty(c.getAppealResult())) {
|
|
||||||
appealStatus = c.getAppealResult();
|
|
||||||
} else if (StringUtils.isNotEmpty(c.getAppealReason())) {
|
|
||||||
appealStatus = "appealing";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
row.put("storyId", en.getKey());
|
|
||||||
row.put("title", titleMap.get(en.getKey()));
|
|
||||||
row.put("workloadIndex", wiMap.get(en.getKey()));
|
|
||||||
row.put("docs", docs);
|
|
||||||
row.put("deduct", missing * DEDUCT_PER_MISSING);
|
|
||||||
row.put("status", status);
|
|
||||||
row.put("appealStatus", appealStatus);
|
|
||||||
result.add(row);
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public void confirmMonth(String month) {
|
|
||||||
if (StringUtils.isEmpty(month)) {
|
|
||||||
throw new BusinessException("月份不能为空");
|
|
||||||
}
|
|
||||||
List<ZtDocCheck> rows = listMonthRows(month);
|
|
||||||
for (ZtDocCheck c : rows) {
|
|
||||||
c.setStatus("CONFIRMED");
|
|
||||||
}
|
|
||||||
if (!rows.isEmpty()) {
|
|
||||||
this.updateBatchById(rows);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 大型需求 = workload_index>20 且当月有完成度变化
|
|
||||||
*/
|
|
||||||
public List<Integer> listLargeStoryIds(String month) {
|
|
||||||
List<ZtStoryMonthWorkload> monthRecords = storyMonthWorkloadService.list(
|
|
||||||
new QueryWrapper<ZtStoryMonthWorkload>().lambda().eq(ZtStoryMonthWorkload::getTime, month));
|
|
||||||
if (CollectionUtils.isEmpty(monthRecords)) {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
Set<Integer> monthStoryIds = monthRecords.stream().map(ZtStoryMonthWorkload::getStoryId).collect(Collectors.toSet());
|
|
||||||
List<ZtStoryExpand> expands = storyExpandService.list(
|
|
||||||
new QueryWrapper<ZtStoryExpand>().lambda().in(ZtStoryExpand::getStoryId, monthStoryIds));
|
|
||||||
List<Integer> storyIds = new ArrayList<>();
|
|
||||||
for (ZtStoryExpand expand : expands) {
|
|
||||||
BigDecimal workloadIndex = parseWorkloadIndex(expand.getWorkloadIndex());
|
|
||||||
if (workloadIndex != null && workloadIndex.compareTo(LARGE_WORKLOAD_INDEX) > 0) {
|
|
||||||
storyIds.add(expand.getStoryId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return storyIds;
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigDecimal parseWorkloadIndex(String workloadIndex) {
|
|
||||||
if (StringUtils.isEmpty(workloadIndex) || StringUtils.isEmpty(workloadIndex.trim())) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return new BigDecimal(workloadIndex.trim());
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 单需求单文档类型五路判定
|
|
||||||
*/
|
|
||||||
private ZtDocCheck checkOne(Integer storyId, String month, String docType, Date now) {
|
|
||||||
boolean present;
|
|
||||||
String detail;
|
|
||||||
switch (docType) {
|
|
||||||
case DOC_TEST_CASE: {
|
|
||||||
long count = caseService.count(new QueryWrapper<ZtCase>().lambda().eq(ZtCase::getStory, storyId));
|
|
||||||
present = count > 0;
|
|
||||||
detail = present ? "zt_case 关联用例 " + count + " 条" : "zt_case 无关联用例";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DOC_TEST_REPORT: {
|
|
||||||
long count = countFile(storyId, "testReportSubmit", false);
|
|
||||||
present = count > 0;
|
|
||||||
detail = present ? "zt_file(testReportSubmit) " + count + " 份" : "zt_file(testReportSubmit) 无附件";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DOC_AI_CODE_REVIEW: {
|
|
||||||
long count = countFile(storyId, "aiCodeReview", false);
|
|
||||||
present = count > 0;
|
|
||||||
detail = present ? "zt_file(aiCodeReview) " + count + " 份" : "zt_file(aiCodeReview) 无附件";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DOC_AI_WORK_LOG: {
|
|
||||||
long count = countFile(storyId, "aiWorkLog", false);
|
|
||||||
present = count > 0;
|
|
||||||
detail = present ? "zt_file(aiWorkLog) " + count + " 份" : "zt_file(aiWorkLog) 无附件";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
case DOC_DOC_UPDATE: {
|
|
||||||
//CHG-046 《AI项目文档更新记录》改独立通道 zt_file(aiDocUpdate)(原 aiWorkLog 的 doc_update 类从未产出,已废弃)
|
|
||||||
long count = countFile(storyId, "aiDocUpdate", false);
|
|
||||||
present = count > 0;
|
|
||||||
detail = present ? "zt_file(aiDocUpdate) " + count + " 份" : "zt_file(aiDocUpdate) 无附件";
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
throw new BusinessException("未知文档类型:" + docType);
|
|
||||||
}
|
|
||||||
ZtDocCheck row = new ZtDocCheck();
|
|
||||||
row.setStoryId(storyId);
|
|
||||||
row.setMonth(month);
|
|
||||||
row.setDocType(docType);
|
|
||||||
row.setPresent(present ? 1 : 0);
|
|
||||||
row.setDetail(detail);
|
|
||||||
row.setStatus(ZtDocCheck.STATUS_PENDING);
|
|
||||||
row.setCheckTime(now);
|
|
||||||
return row;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* zt_file 附件计数(未删除;docUpdate 类要求 extra/title 含 doc_update,宽松匹配)
|
|
||||||
*/
|
|
||||||
private long countFile(Integer storyId, String objectType, boolean docUpdateOnly) {
|
|
||||||
LambdaQueryWrapper<ZtFile> wrapper = new QueryWrapper<ZtFile>().lambda()
|
|
||||||
.eq(ZtFile::getObjecttype, objectType)
|
|
||||||
.eq(ZtFile::getObjectid, storyId)
|
|
||||||
.and(w -> w.isNull(ZtFile::getDeleted).or().eq(ZtFile::getDeleted, "0"));
|
|
||||||
if (docUpdateOnly) {
|
|
||||||
wrapper.and(w -> w.like(ZtFile::getExtra, "doc_update").or().like(ZtFile::getTitle, "doc_update"));
|
|
||||||
}
|
|
||||||
return fileService.count(wrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 按 uk_story_month_doc 覆盖写入;申诉中/已复核记录保留状态,仅刷新判定结果
|
|
||||||
*/
|
|
||||||
private void upsertRow(ZtDocCheck row) {
|
|
||||||
ZtDocCheck exist = getRow(row.getStoryId(), row.getMonth(), row.getDocType());
|
|
||||||
if (exist == null) {
|
|
||||||
row.setCreateTime(new Date());
|
|
||||||
row.setUpdateTime(new Date());
|
|
||||||
this.save(row);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
exist.setPresent(row.getPresent());
|
|
||||||
exist.setDetail(row.getDetail());
|
|
||||||
exist.setCheckTime(row.getCheckTime());
|
|
||||||
exist.setUpdateTime(new Date());
|
|
||||||
this.updateById(exist);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ZtDocCheck getRow(Integer storyId, String month, String docType) {
|
|
||||||
return this.getOne(new QueryWrapper<ZtDocCheck>().lambda()
|
|
||||||
.eq(ZtDocCheck::getStoryId, storyId)
|
|
||||||
.eq(ZtDocCheck::getMonth, month)
|
|
||||||
.eq(ZtDocCheck::getDocType, docType)
|
|
||||||
.last("LIMIT 1"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重算某账号当月的 docCheck 扣分节点(申诉复核通过后回滚用)
|
|
||||||
*/
|
|
||||||
private void recomputeAccountDeduction(String account, String month) {
|
|
||||||
List<ZtDocCheck> rows = listMonthRows(month);
|
|
||||||
if (CollectionUtils.isEmpty(rows)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
Set<Integer> storyIds = rows.stream().map(ZtDocCheck::getStoryId).collect(Collectors.toSet());
|
|
||||||
List<ZtStory> stories = storyService.listByIds(storyIds);
|
|
||||||
Map<Integer, ZtStory> storyMap = stories.stream().collect(Collectors.toMap(ZtStory::getId, o -> o, (a, b) -> a));
|
|
||||||
writeAccountDocCheckNode(account, month, rows, storyMap);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 合并写 zt_month_score.scopeJson 的 docCheck 节点(account+dateStr 维度,保留已有明细)
|
|
||||||
* @return 本次缺失扣分
|
|
||||||
*/
|
|
||||||
private BigDecimal writeAccountDocCheckNode(String account, String month, List<ZtDocCheck> rows, Map<Integer, ZtStory> storyMap) {
|
|
||||||
List<ZtDocCheck> mine = rows.stream()
|
|
||||||
.filter(o -> {
|
|
||||||
ZtStory story = storyMap.get(o.getStoryId());
|
|
||||||
return story != null && account.equals(story.getAssignedTo());
|
|
||||||
})
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
List<ZtDocCheck> missing = mine.stream().filter(ZtDocCheck::countsAsMissing).collect(Collectors.toList());
|
|
||||||
BigDecimal deduct = BigDecimal.valueOf((long) missing.size() * DEDUCT_PER_MISSING);
|
|
||||||
|
|
||||||
JSONObject docCheck = new JSONObject();
|
|
||||||
docCheck.put("month", month);
|
|
||||||
docCheck.put("missingCount", missing.size());
|
|
||||||
docCheck.put("deductScore", deduct);
|
|
||||||
docCheck.put("deductPerMissing", DEDUCT_PER_MISSING);
|
|
||||||
JSONArray items = new JSONArray();
|
|
||||||
for (ZtDocCheck row : missing) {
|
|
||||||
JSONObject item = new JSONObject();
|
|
||||||
item.put("storyId", row.getStoryId());
|
|
||||||
item.put("docType", row.getDocType());
|
|
||||||
item.put("detail", row.getDetail());
|
|
||||||
items.add(item);
|
|
||||||
}
|
|
||||||
docCheck.put("items", items);
|
|
||||||
|
|
||||||
ZtMonthScore monthScore = monthScoreService.getOne(new QueryWrapper<ZtMonthScore>().lambda()
|
|
||||||
.eq(ZtMonthScore::getAccount, account)
|
|
||||||
.eq(ZtMonthScore::getDateStr, month)
|
|
||||||
.last("LIMIT 1"));
|
|
||||||
Date now = new Date();
|
|
||||||
if (monthScore == null) {
|
|
||||||
monthScore = new ZtMonthScore();
|
|
||||||
monthScore.setAccount(account);
|
|
||||||
monthScore.setDateStr(month);
|
|
||||||
monthScore.setCreateDate(now);
|
|
||||||
monthScore.setCreateUser(SYSTEM_USER);
|
|
||||||
JSONObject scopeJson = new JSONObject();
|
|
||||||
scopeJson.put("docCheck", docCheck);
|
|
||||||
monthScore.setScopeJson(scopeJson.toJSONString());
|
|
||||||
monthScore.setUpdateDate(now);
|
|
||||||
monthScore.setUpdateUser(SYSTEM_USER);
|
|
||||||
monthScoreService.save(monthScore);
|
|
||||||
} else {
|
|
||||||
JSONObject scopeJson;
|
|
||||||
try {
|
|
||||||
scopeJson = StringUtils.isEmpty(monthScore.getScopeJson())
|
|
||||||
? new JSONObject() : JSON.parseObject(monthScore.getScopeJson());
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("zt_month_score scopeJson 非 JSON,重写 docCheck 节点 account={} month={}", account, month);
|
|
||||||
scopeJson = new JSONObject();
|
|
||||||
}
|
|
||||||
scopeJson.put("docCheck", docCheck);
|
|
||||||
monthScore.setScopeJson(scopeJson.toJSONString());
|
|
||||||
monthScore.setUpdateDate(now);
|
|
||||||
monthScore.setUpdateUser(SYSTEM_USER);
|
|
||||||
monthScoreService.updateById(monthScore);
|
|
||||||
}
|
|
||||||
return deduct;
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,21 +1,13 @@
|
|||||||
package com.sa.zentao.service.impl;
|
package com.sa.zentao.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
import com.sa.zentao.conf.RiskUserThreadLocal;
|
|
||||||
import com.sa.zentao.dao.BusinessException;
|
|
||||||
import com.sa.zentao.entity.ZtFile;
|
import com.sa.zentao.entity.ZtFile;
|
||||||
import com.sa.zentao.entity.ZtMeeting;
|
|
||||||
import com.sa.zentao.entity.ZtStory;
|
|
||||||
import com.sa.zentao.enums.FileTypes;
|
import com.sa.zentao.enums.FileTypes;
|
||||||
import com.sa.zentao.mapper.ZtFileMapper;
|
import com.sa.zentao.mapper.ZtFileMapper;
|
||||||
import com.sa.zentao.mapper.ZtMeetingMapper;
|
|
||||||
import com.sa.zentao.mapper.ZtStoryMapper;
|
|
||||||
import com.sa.zentao.service.IZtFileService;
|
import com.sa.zentao.service.IZtFileService;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
@@ -34,18 +26,6 @@ import java.util.List;
|
|||||||
@Service
|
@Service
|
||||||
public class ZtFileServiceImpl extends ServiceImpl<ZtFileMapper, ZtFile> implements IZtFileService {
|
public class ZtFileServiceImpl extends ServiceImpl<ZtFileMapper, ZtFile> implements IZtFileService {
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ZtStoryMapper storyMapper;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private ZtMeetingMapper meetingMapper;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private com.sa.zentao.service.IZtActionService actionService;
|
|
||||||
|
|
||||||
@org.springframework.beans.factory.annotation.Value("${file.baseUrl}")
|
|
||||||
private String baseUrl;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void updateFile(String fileIds, Integer objectId, FileTypes type) {
|
public void updateFile(String fileIds, Integer objectId, FileTypes type) {
|
||||||
@@ -70,88 +50,4 @@ public class ZtFileServiceImpl extends ServiceImpl<ZtFileMapper, ZtFile> impleme
|
|||||||
this.saveOrUpdateBatch(ztFiles);
|
this.saveOrUpdateBatch(ztFiles);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void refreshOwnerUrl(String objectType, Integer objectId, String url, String reviewResult) {
|
|
||||||
if (StringUtils.isEmpty(objectType) || objectId == null) {
|
|
||||||
throw new BusinessException("objectType/objectId不能为空");
|
|
||||||
}
|
|
||||||
//动态留痕 actor 取 token 身份(无登录态兜底 ai,兼容既有单测直调)(CHG-061)
|
|
||||||
String actionActor = RiskUserThreadLocal.get() == null || StringUtils.isEmpty(RiskUserThreadLocal.get().getName())
|
|
||||||
? "ai" : RiskUserThreadLocal.get().getName();
|
|
||||||
FileTypes type = FileTypes.transferType(objectType);
|
|
||||||
if (type == null) {
|
|
||||||
throw new BusinessException("不支持的objectType:" + objectType);
|
|
||||||
}
|
|
||||||
switch (type) {
|
|
||||||
case story:
|
|
||||||
case storyPrd:
|
|
||||||
case aiCodeReview:
|
|
||||||
case aiWorkLog:
|
|
||||||
case aiDocUpdate:
|
|
||||||
case testCase:
|
|
||||||
case testReport:
|
|
||||||
case testReportSubmit:
|
|
||||||
case testOther:
|
|
||||||
ZtStory ztStory = this.storyMapper.selectById(objectId);
|
|
||||||
if (ztStory == null) {
|
|
||||||
throw new BusinessException("需求不存在:" + objectId);
|
|
||||||
}
|
|
||||||
LambdaUpdateWrapper<ZtStory> wrapper = new UpdateWrapper<ZtStory>().lambda()
|
|
||||||
.eq(ZtStory::getId, objectId);
|
|
||||||
switch (type) {
|
|
||||||
case story:
|
|
||||||
case storyPrd:
|
|
||||||
wrapper.set(ZtStory::getPrdUrl, url);
|
|
||||||
break;
|
|
||||||
case aiCodeReview:
|
|
||||||
wrapper.set(ZtStory::getCodeReviewUrl, url);
|
|
||||||
if (StringUtils.isNotEmpty(reviewResult)) {
|
|
||||||
wrapper.set(ZtStory::getCodeReviewStatus, reviewResult);
|
|
||||||
}
|
|
||||||
break;
|
|
||||||
case aiWorkLog:
|
|
||||||
wrapper.set(ZtStory::getWorkLogUrl, url);
|
|
||||||
break;
|
|
||||||
case aiDocUpdate:
|
|
||||||
wrapper.set(ZtStory::getAiDocUpdateUrl, url);
|
|
||||||
break;
|
|
||||||
case testCase:
|
|
||||||
wrapper.set(ZtStory::getTestCaseUrl, url);
|
|
||||||
break;
|
|
||||||
case testReport:
|
|
||||||
wrapper.set(ZtStory::getTestReportDownloadUrl, url);
|
|
||||||
break;
|
|
||||||
case testReportSubmit:
|
|
||||||
wrapper.set(ZtStory::getTestReportSubmitUrl, url);
|
|
||||||
break;
|
|
||||||
case testOther:
|
|
||||||
wrapper.set(ZtStory::getTestOtherUrl, url);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
this.storyMapper.update(null, wrapper);
|
|
||||||
//上传动态日志(zt_action):与禅道原生操作一致留痕
|
|
||||||
actionService.addAction(com.sa.zentao.enums.ActionType.XQ, com.sa.zentao.enums.ActionStatus.BJ,
|
|
||||||
objectId, ztStory.getProduct() == null ? null : ztStory.getProduct() + "", null, null,
|
|
||||||
actionActor, "上传" + type.getValue() + ":" + baseUrl + url, null);
|
|
||||||
break;
|
|
||||||
case meeting:
|
|
||||||
ZtMeeting ztMeeting = this.meetingMapper.selectById(objectId);
|
|
||||||
if (ztMeeting == null) {
|
|
||||||
throw new BusinessException("会议不存在:" + objectId);
|
|
||||||
}
|
|
||||||
this.meetingMapper.update(null, new UpdateWrapper<ZtMeeting>().lambda()
|
|
||||||
.set(ZtMeeting::getUrl, url)
|
|
||||||
.eq(ZtMeeting::getId, objectId));
|
|
||||||
actionService.addAction(com.sa.zentao.enums.ActionType.MEET, com.sa.zentao.enums.ActionStatus.BJ,
|
|
||||||
objectId, ztMeeting.getProductId() == null ? null : ztMeeting.getProductId() + "", null, null,
|
|
||||||
actionActor, "上传会议纪要:" + baseUrl + url, null);
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
throw new BusinessException("uploadBind不支持的objectType:" + objectType);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|||||||
import com.sa.zentao.conf.RiskUserThreadLocal;
|
import com.sa.zentao.conf.RiskUserThreadLocal;
|
||||||
import com.sa.zentao.dao.*;
|
import com.sa.zentao.dao.*;
|
||||||
import com.sa.zentao.entity.*;
|
import com.sa.zentao.entity.*;
|
||||||
import com.sa.zentao.enums.*;
|
import com.sa.zentao.enums.ActionStatus;
|
||||||
import com.sa.zentao.utils.KanbanStageMapping;
|
import com.sa.zentao.enums.ActionType;
|
||||||
|
import com.sa.zentao.enums.KanbanCellType;
|
||||||
|
import com.sa.zentao.enums.ProjectTypeEnums;
|
||||||
import com.sa.zentao.mapper.ZtKanbancolumnMapper;
|
import com.sa.zentao.mapper.ZtKanbancolumnMapper;
|
||||||
import com.sa.zentao.mapper.ZtKanbanlaneMapper;
|
import com.sa.zentao.mapper.ZtKanbanlaneMapper;
|
||||||
import com.sa.zentao.qo.KanbanQo;
|
import com.sa.zentao.qo.KanbanQo;
|
||||||
@@ -15,7 +17,6 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
|||||||
import com.sa.zentao.utils.BeanCopyUtil;
|
import com.sa.zentao.utils.BeanCopyUtil;
|
||||||
import com.sa.zentao.utils.DateUtils;
|
import com.sa.zentao.utils.DateUtils;
|
||||||
import com.sa.zentao.utils.KanBanConstant;
|
import com.sa.zentao.utils.KanBanConstant;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
@@ -36,7 +37,6 @@ import java.util.stream.Stream;
|
|||||||
* @since 2024-07-09
|
* @since 2024-07-09
|
||||||
*/
|
*/
|
||||||
@Service
|
@Service
|
||||||
@Slf4j
|
|
||||||
public class ZtKanbanlaneServiceImpl extends ServiceImpl<ZtKanbanlaneMapper, ZtKanbanlane> implements IZtKanbanlaneService {
|
public class ZtKanbanlaneServiceImpl extends ServiceImpl<ZtKanbanlaneMapper, ZtKanbanlane> implements IZtKanbanlaneService {
|
||||||
|
|
||||||
@Autowired
|
@Autowired
|
||||||
@@ -116,16 +116,6 @@ public class ZtKanbanlaneServiceImpl extends ServiceImpl<ZtKanbanlaneMapper, ZtK
|
|||||||
st.setAssignedToName(ztUser.getNickname());
|
st.setAssignedToName(ztUser.getNickname());
|
||||||
st.setColor(ztUser.getColor());
|
st.setColor(ztUser.getColor());
|
||||||
}
|
}
|
||||||
ztUser = userMap.get(st.getProductUser());
|
|
||||||
if(ztUser!=null){
|
|
||||||
st.setProductUserName(ztUser.getNickname());
|
|
||||||
st.setProductUserColor(ztUser.getColor());
|
|
||||||
}
|
|
||||||
ztUser = userMap.get(st.getTestUser());
|
|
||||||
if(ztUser!=null){
|
|
||||||
st.setTestUserName(ztUser.getNickname());
|
|
||||||
st.setTestUserColor(ztUser.getColor());
|
|
||||||
}
|
|
||||||
if(product!=null){
|
if(product!=null){
|
||||||
st.setProductName(product.getName());
|
st.setProductName(product.getName());
|
||||||
}
|
}
|
||||||
@@ -229,7 +219,7 @@ public class ZtKanbanlaneServiceImpl extends ServiceImpl<ZtKanbanlaneMapper, ZtK
|
|||||||
ZtKanbancell fromkanbancell = this.kanbancellService.getOne(new QueryWrapper<ZtKanbancell>().lambda()
|
ZtKanbancell fromkanbancell = this.kanbancellService.getOne(new QueryWrapper<ZtKanbancell>().lambda()
|
||||||
.eq(ZtKanbancell::getColumn, fromId));
|
.eq(ZtKanbancell::getColumn, fromId));
|
||||||
|
|
||||||
Set<String> carIds = new HashSet<>(Arrays.asList(fromkanbancell.getCards().split(",")));
|
List<String> carIds = new ArrayList<>(Arrays.asList(fromkanbancell.getCards().split(",")));
|
||||||
carIds.remove(qo.getId().toString());
|
carIds.remove(qo.getId().toString());
|
||||||
|
|
||||||
|
|
||||||
@@ -245,7 +235,7 @@ public class ZtKanbanlaneServiceImpl extends ServiceImpl<ZtKanbanlaneMapper, ZtK
|
|||||||
if(StringUtils.isEmpty(tokanbancell.getCards())){
|
if(StringUtils.isEmpty(tokanbancell.getCards())){
|
||||||
tokanbancell.setCards(qo.getId().toString());
|
tokanbancell.setCards(qo.getId().toString());
|
||||||
}else{
|
}else{
|
||||||
carIds = new HashSet<>(Arrays.asList(tokanbancell.getCards().split(",")));
|
carIds = new ArrayList<>(Arrays.asList(tokanbancell.getCards().split(",")));
|
||||||
carIds.add(qo.getId().toString());
|
carIds.add(qo.getId().toString());
|
||||||
tokanbancell.setCards(StringUtils.join(carIds,","));
|
tokanbancell.setCards(StringUtils.join(carIds,","));
|
||||||
}
|
}
|
||||||
@@ -253,14 +243,14 @@ public class ZtKanbanlaneServiceImpl extends ServiceImpl<ZtKanbanlaneMapper, ZtK
|
|||||||
this.kanbancellService.updateById(tokanbancell);
|
this.kanbancellService.updateById(tokanbancell);
|
||||||
|
|
||||||
ActionType type= null;
|
ActionType type= null;
|
||||||
if(KanbanCellType.STORY.getValue().equalsIgnoreCase(tabType)){
|
if("story".equalsIgnoreCase(tabType)){
|
||||||
type=ActionType.XQ;
|
type=ActionType.XQ;
|
||||||
//需求
|
//需求
|
||||||
// storyChange(qo);
|
// storyChange(qo);
|
||||||
|
|
||||||
//testing 测试中
|
//testing 测试中
|
||||||
}
|
}
|
||||||
if(KanbanCellType.BUG.getValue().equalsIgnoreCase(tabType)){
|
if("bug".equalsIgnoreCase(tabType)){
|
||||||
type=ActionType.BUG;
|
type=ActionType.BUG;
|
||||||
//bug
|
//bug
|
||||||
// bugChange(qo);
|
// bugChange(qo);
|
||||||
@@ -271,7 +261,7 @@ public class ZtKanbanlaneServiceImpl extends ServiceImpl<ZtKanbanlaneMapper, ZtK
|
|||||||
//closed 关闭
|
//closed 关闭
|
||||||
|
|
||||||
}
|
}
|
||||||
if(KanbanCellType.TASK.getValue().equalsIgnoreCase(tabType)){
|
if("task".equalsIgnoreCase(tabType)){
|
||||||
type=ActionType.RW;
|
type=ActionType.RW;
|
||||||
//task
|
//task
|
||||||
// taskChange(qo);
|
// taskChange(qo);
|
||||||
@@ -283,7 +273,7 @@ public class ZtKanbanlaneServiceImpl extends ServiceImpl<ZtKanbanlaneMapper, ZtK
|
|||||||
//完成有新任务回退到进行中
|
//完成有新任务回退到进行中
|
||||||
|
|
||||||
//
|
//
|
||||||
// if(KanbanColumnType.TASK_CANCEL.getValue().equalsIgnoreCase(statusType)){
|
// if("canceled".equalsIgnoreCase(statusType)){
|
||||||
// //取消
|
// //取消
|
||||||
// }
|
// }
|
||||||
|
|
||||||
@@ -313,15 +303,12 @@ public class ZtKanbanlaneServiceImpl extends ServiceImpl<ZtKanbanlaneMapper, ZtK
|
|||||||
List<String> carIds = new ArrayList<>(Arrays.asList(cell.getCards().split(",")));
|
List<String> carIds = new ArrayList<>(Arrays.asList(cell.getCards().split(",")));
|
||||||
if(carIds.contains(bussId.toString())){
|
if(carIds.contains(bussId.toString())){
|
||||||
thisZtKanbancell=cell;
|
thisZtKanbancell=cell;
|
||||||
break;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<String> carIds = new HashSet<>(Arrays.asList(thisZtKanbancell.getCards().split(",")));
|
List<String> carIds = new ArrayList<>(Arrays.asList(thisZtKanbancell.getCards().split(",")));
|
||||||
while (carIds.contains(bussId.toString())) {
|
|
||||||
carIds.remove(bussId.toString());
|
carIds.remove(bussId.toString());
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
if(CollectionUtils.isEmpty(carIds)){
|
if(CollectionUtils.isEmpty(carIds)){
|
||||||
thisZtKanbancell.setCards("");
|
thisZtKanbancell.setCards("");
|
||||||
@@ -330,13 +317,8 @@ public class ZtKanbanlaneServiceImpl extends ServiceImpl<ZtKanbanlaneMapper, ZtK
|
|||||||
thisZtKanbancell.setCards(StringUtils.join(carIds,","));
|
thisZtKanbancell.setCards(StringUtils.join(carIds,","));
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ZtKanbancell ztKanbanlane = this.baseMapper.getZtKanbanlane(type, toStatus, execId);
|
|
||||||
if(ztKanbanlane==null){
|
|
||||||
log.info("看板未获取到execId={} bussId={} type={} toStatus={}", execId, bussId, type, toStatus);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
this.kanbancellService.updateById(thisZtKanbancell);
|
this.kanbancellService.updateById(thisZtKanbancell);
|
||||||
|
ZtKanbancell ztKanbanlane = this.baseMapper.getZtKanbanlane(type, toStatus, execId);
|
||||||
String cards = ztKanbanlane.getCards();
|
String cards = ztKanbanlane.getCards();
|
||||||
List l=StringUtils.isEmpty(cards)?new ArrayList<>():new ArrayList<>(Arrays.asList(cards.split(",")));
|
List l=StringUtils.isEmpty(cards)?new ArrayList<>():new ArrayList<>(Arrays.asList(cards.split(",")));
|
||||||
l.add(bussId.toString());
|
l.add(bussId.toString());
|
||||||
@@ -347,7 +329,7 @@ public class ZtKanbanlaneServiceImpl extends ServiceImpl<ZtKanbanlaneMapper, ZtK
|
|||||||
@Override
|
@Override
|
||||||
public void addStory(Integer id, List<ZtStory> ztStories) {
|
public void addStory(Integer id, List<ZtStory> ztStories) {
|
||||||
ZtKanbanlane lane = this.baseMapper.selectOne(new QueryWrapper<ZtKanbanlane>().lambda()
|
ZtKanbanlane lane = this.baseMapper.selectOne(new QueryWrapper<ZtKanbanlane>().lambda()
|
||||||
.eq(ZtKanbanlane::getExecution, id).eq(ZtKanbanlane::getType, KanbanCellType.STORY.getValue()));
|
.eq(ZtKanbanlane::getExecution, id).eq(ZtKanbanlane::getType, "story"));
|
||||||
|
|
||||||
List<ZtKanbancolumnDTO> ztKanbancolumnDTOS = this.kanbancolumnMapper.listByLaneIds(Arrays.asList(lane.getId()));
|
List<ZtKanbancolumnDTO> ztKanbancolumnDTOS = this.kanbancolumnMapper.listByLaneIds(Arrays.asList(lane.getId()));
|
||||||
|
|
||||||
@@ -379,13 +361,6 @@ public class ZtKanbanlaneServiceImpl extends ServiceImpl<ZtKanbanlaneMapper, ZtK
|
|||||||
}else if ("verified".equals(st.getStage())){
|
}else if ("verified".equals(st.getStage())){
|
||||||
ztKanbancolumnDTO = ztKanbancolumnDTOS.stream().filter(o -> o.getType().equals("verified")).collect(Collectors.toList()).get(0);
|
ztKanbancolumnDTO = ztKanbancolumnDTOS.stream().filter(o -> 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<ZtKanbancell>().lambda()
|
ZtKanbancell kanbancell = this.kanbancellService.getOne(new QueryWrapper<ZtKanbancell>().lambda()
|
||||||
.eq(ZtKanbancell::getKanban, id).eq(ZtKanbancell::getColumn, ztKanbancolumnDTO.getId()));
|
.eq(ZtKanbancell::getKanban, id).eq(ZtKanbancell::getColumn, ztKanbancolumnDTO.getId()));
|
||||||
@@ -407,7 +382,7 @@ public class ZtKanbanlaneServiceImpl extends ServiceImpl<ZtKanbanlaneMapper, ZtK
|
|||||||
public void addTask(Integer id, List<ZtTask> asList) {
|
public void addTask(Integer id, List<ZtTask> asList) {
|
||||||
|
|
||||||
ZtKanbanlane lane = this.baseMapper.selectOne(new QueryWrapper<ZtKanbanlane>().lambda()
|
ZtKanbanlane lane = this.baseMapper.selectOne(new QueryWrapper<ZtKanbanlane>().lambda()
|
||||||
.eq(ZtKanbanlane::getExecution, id).eq(ZtKanbanlane::getType, KanbanCellType.TASK.getValue()));
|
.eq(ZtKanbanlane::getExecution, id).eq(ZtKanbanlane::getType, "task"));
|
||||||
|
|
||||||
List<ZtKanbancolumnDTO> kanbancolumnList = this.kanbancolumnMapper.listByLaneIds(Arrays.asList(lane.getId()));
|
List<ZtKanbancolumnDTO> kanbancolumnList = this.kanbancolumnMapper.listByLaneIds(Arrays.asList(lane.getId()));
|
||||||
|
|
||||||
@@ -433,13 +408,11 @@ public class ZtKanbanlaneServiceImpl extends ServiceImpl<ZtKanbanlaneMapper, ZtK
|
|||||||
@Override
|
@Override
|
||||||
public void addBug(Integer id, List<ZtBug> asList) {
|
public void addBug(Integer id, List<ZtBug> asList) {
|
||||||
ZtKanbanlane lane = this.baseMapper.selectOne(new QueryWrapper<ZtKanbanlane>().lambda()
|
ZtKanbanlane lane = this.baseMapper.selectOne(new QueryWrapper<ZtKanbanlane>().lambda()
|
||||||
.eq(ZtKanbanlane::getExecution, id).eq(ZtKanbanlane::getType, KanbanCellType.BUG.getValue()));
|
.eq(ZtKanbanlane::getExecution, id).eq(ZtKanbanlane::getType, "bug"));
|
||||||
|
|
||||||
List<ZtKanbancolumnDTO> ztKanbancolumnDTOS = this.kanbancolumnMapper.listByLaneIds(Arrays.asList(lane.getId()));
|
List<ZtKanbancolumnDTO> ztKanbancolumnDTOS = this.kanbancolumnMapper.listByLaneIds(Arrays.asList(lane.getId()));
|
||||||
|
|
||||||
List<ZtKanbancolumnDTO> backlog = ztKanbancolumnDTOS.stream()
|
List<ZtKanbancolumnDTO> backlog = ztKanbancolumnDTOS.stream().filter(o -> o.getType().equals("unconfirmed")).collect(Collectors.toList());
|
||||||
.filter(o -> o.getType().equals(KanbanColumnType.BUG_UNCONFIRMED.getValue()))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
ZtKanbancolumnDTO ztKanbancolumnDTO = backlog.get(0);
|
ZtKanbancolumnDTO ztKanbancolumnDTO = backlog.get(0);
|
||||||
|
|
||||||
ZtKanbancell kanbancell = this.kanbancellService.getOne(new QueryWrapper<ZtKanbancell>().lambda()
|
ZtKanbancell kanbancell = this.kanbancellService.getOne(new QueryWrapper<ZtKanbancell>().lambda()
|
||||||
|
|||||||
@@ -20,7 +20,6 @@ import com.sa.zentao.dao.BusinessException;
|
|||||||
import com.sa.zentao.dao.ZtMeetingDTO;
|
import com.sa.zentao.dao.ZtMeetingDTO;
|
||||||
import com.sa.zentao.dao.ZtStoryDTO;
|
import com.sa.zentao.dao.ZtStoryDTO;
|
||||||
import com.sa.zentao.entity.ZtMeeting;
|
import com.sa.zentao.entity.ZtMeeting;
|
||||||
import com.sa.zentao.entity.ZtFile;
|
|
||||||
import com.sa.zentao.entity.ZtProduct;
|
import com.sa.zentao.entity.ZtProduct;
|
||||||
import com.sa.zentao.entity.ZtStoryUser;
|
import com.sa.zentao.entity.ZtStoryUser;
|
||||||
import com.sa.zentao.entity.ZtUser;
|
import com.sa.zentao.entity.ZtUser;
|
||||||
@@ -89,17 +88,17 @@ public class ZtMeetingServiceImpl extends ServiceImpl<ZtMeetingMapper, ZtMeeting
|
|||||||
Map<Integer,ZtStoryUser> sMap=getUserStoryMap(list);
|
Map<Integer,ZtStoryUser> sMap=getUserStoryMap(list);
|
||||||
|
|
||||||
for (ZtMeetingDTO d:list) {
|
for (ZtMeetingDTO d:list) {
|
||||||
// StringBuilder b=new StringBuilder();
|
StringBuilder b=new StringBuilder();
|
||||||
// if(!StringUtils.isEmpty(d.getUsers())){
|
if(!StringUtils.isEmpty(d.getUsers())){
|
||||||
// String[] split = d.getUsers().split(",");
|
String[] split = d.getUsers().split(",");
|
||||||
// for (String s:split) {
|
for (String s:split) {
|
||||||
// ZtUser ztUser = userMap.get(s);
|
ZtUser ztUser = userMap.get(s);
|
||||||
// if(ztUser!=null){
|
if(ztUser!=null){
|
||||||
// b.append(ztUser.getNickname()).append(",");
|
b.append(ztUser.getNickname()).append(",");
|
||||||
// }
|
}
|
||||||
// }
|
}
|
||||||
// d.setUsersName(b.toString());
|
d.setUsersName(b.toString());
|
||||||
// }
|
}
|
||||||
StringBuilder storyStr=new StringBuilder();
|
StringBuilder storyStr=new StringBuilder();
|
||||||
if(!StringUtils.isEmpty( d.getStoryIds())){
|
if(!StringUtils.isEmpty( d.getStoryIds())){
|
||||||
String[] split = d.getStoryIds().split(",");
|
String[] split = d.getStoryIds().split(",");
|
||||||
@@ -158,7 +157,6 @@ public class ZtMeetingServiceImpl extends ServiceImpl<ZtMeetingMapper, ZtMeeting
|
|||||||
|
|
||||||
this.baseMapper.insert(ztMeeting);
|
this.baseMapper.insert(ztMeeting);
|
||||||
fileService.updateFile(dto.getFiles(),ztMeeting.getId(), FileTypes.meeting);
|
fileService.updateFile(dto.getFiles(),ztMeeting.getId(), FileTypes.meeting);
|
||||||
refreshMeetingUrl(ztMeeting);
|
|
||||||
|
|
||||||
|
|
||||||
//添加action
|
//添加action
|
||||||
@@ -167,25 +165,6 @@ public class ZtMeetingServiceImpl extends ServiceImpl<ZtMeetingMapper, ZtMeeting
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* CHG-020:刷新会议纪要最新一份 MD 访问链接到 zt_meeting.url
|
|
||||||
* (老式两段式上传经 updateFile 绑定时同步刷新,与 uploadBind 的 refreshOwnerUrl 双通道一致)
|
|
||||||
*/
|
|
||||||
private void refreshMeetingUrl(ZtMeeting ztMeeting) {
|
|
||||||
ZtFile latest = fileService.getOne(new QueryWrapper<ZtFile>().lambda()
|
|
||||||
.eq(ZtFile::getObjecttype, FileTypes.meeting.getCode())
|
|
||||||
.eq(ZtFile::getObjectid, ztMeeting.getId())
|
|
||||||
.eq(ZtFile::getDeleted, "0")
|
|
||||||
.orderByDesc(ZtFile::getId).last("limit 1"), false);
|
|
||||||
if (latest != null) {
|
|
||||||
String url = StringUtils.isNotEmpty(latest.getUrl()) ? latest.getUrl() : latest.getPathname();
|
|
||||||
if (StringUtils.isNotEmpty(url)) {
|
|
||||||
ztMeeting.setUrl(url);
|
|
||||||
this.baseMapper.updateById(ztMeeting);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void modify(ZtMeetingDTO dto) {
|
public void modify(ZtMeetingDTO dto) {
|
||||||
@@ -203,7 +182,6 @@ public class ZtMeetingServiceImpl extends ServiceImpl<ZtMeetingMapper, ZtMeeting
|
|||||||
|
|
||||||
this.baseMapper.updateById(ztMeeting);
|
this.baseMapper.updateById(ztMeeting);
|
||||||
fileService.updateFile(dto.getFiles(),ztMeeting.getId(), FileTypes.meeting);
|
fileService.updateFile(dto.getFiles(),ztMeeting.getId(), FileTypes.meeting);
|
||||||
refreshMeetingUrl(ztMeeting);
|
|
||||||
|
|
||||||
//添加action
|
//添加action
|
||||||
actionService.addAction(ActionType.MEET, ActionStatus.BJ, ztMeeting.getId(), ztMeeting.getProductId() + "", null, null,
|
actionService.addAction(ActionType.MEET, ActionStatus.BJ, ztMeeting.getId(), ztMeeting.getProductId() + "", null, null,
|
||||||
|
|||||||
@@ -1,88 +0,0 @@
|
|||||||
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.ZtPerfConfig;
|
|
||||||
import com.sa.zentao.entity.ZtUser;
|
|
||||||
import com.sa.zentao.enums.UserType;
|
|
||||||
import com.sa.zentao.mapper.ZtPerfConfigMapper;
|
|
||||||
import com.sa.zentao.service.IZtPerfConfigService;
|
|
||||||
import com.sa.zentao.service.IZtUserService;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* 绩效指标规则配置 服务实现类(三期)
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
public class ZtPerfConfigServiceImpl extends ServiceImpl<ZtPerfConfigMapper, ZtPerfConfig> implements IZtPerfConfigService {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtUserService userService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public ZtPerfConfig getEnabledConfig(String role, String itemKey) {
|
|
||||||
if (StringUtils.isEmpty(itemKey)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
if (StringUtils.isNotEmpty(role)) {
|
|
||||||
ZtPerfConfig config = this.getOne(new QueryWrapper<ZtPerfConfig>().lambda()
|
|
||||||
.eq(ZtPerfConfig::getRole, role)
|
|
||||||
.eq(ZtPerfConfig::getItemKey, itemKey)
|
|
||||||
.eq(ZtPerfConfig::getEnabled, 1)
|
|
||||||
.last("LIMIT 1"));
|
|
||||||
if (config != null) {
|
|
||||||
return config;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// 回退:该指标任意一条启用配置(岗位未识别/未配置时兜底取规则参数)
|
|
||||||
List<ZtPerfConfig> list = this.list(new QueryWrapper<ZtPerfConfig>().lambda()
|
|
||||||
.eq(ZtPerfConfig::getItemKey, itemKey)
|
|
||||||
.eq(ZtPerfConfig::getEnabled, 1)
|
|
||||||
.last("LIMIT 1"));
|
|
||||||
return CollectionUtils.isEmpty(list) ? null : list.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String resolveRole(String account) {
|
|
||||||
if (StringUtils.isEmpty(account)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
ZtUser user = userService.getOne(new QueryWrapper<ZtUser>().lambda()
|
|
||||||
.eq(ZtUser::getAccount, account).last("LIMIT 1"));
|
|
||||||
if (user == null || user.getUserType() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
UserType userType = user.getUserType();
|
|
||||||
switch (userType) {
|
|
||||||
case XMJL:
|
|
||||||
case XMGLY:
|
|
||||||
return "projectManager";
|
|
||||||
case CPJL:
|
|
||||||
return "productManager";
|
|
||||||
case CP:
|
|
||||||
case XMZL:
|
|
||||||
return "productAssistant";
|
|
||||||
case KFZ:
|
|
||||||
// 前后端经 userType 无法区分,默认后端;后续可按 zt_user.role 细分
|
|
||||||
return "backendDev";
|
|
||||||
case CS:
|
|
||||||
return "tester";
|
|
||||||
case UI:
|
|
||||||
return "uiEngineer";
|
|
||||||
case YW:
|
|
||||||
return "opsEngineer";
|
|
||||||
default:
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,366 +0,0 @@
|
|||||||
package com.sa.zentao.service.impl;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.sa.zentao.conf.RiskUserThreadLocal;
|
|
||||||
import com.sa.zentao.dao.BusinessException;
|
|
||||||
import com.sa.zentao.entity.ZtMonthScore;
|
|
||||||
import com.sa.zentao.entity.ZtPerfConfig;
|
|
||||||
import com.sa.zentao.entity.ZtUser;
|
|
||||||
import com.sa.zentao.qo.ZtPerfQo;
|
|
||||||
import com.sa.zentao.service.IZtPerfConfigService;
|
|
||||||
import com.sa.zentao.service.IZtPerfReportService;
|
|
||||||
import com.sa.zentao.service.IZtMonthScoreService;
|
|
||||||
import com.sa.zentao.service.IZtUserService;
|
|
||||||
import com.sa.zentao.service.perf.PerfIndicatorCalculator;
|
|
||||||
import com.sa.zentao.service.perf.PerfMonthScope;
|
|
||||||
import com.sa.zentao.service.perf.PerfScoreEngine;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Service;
|
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.LinkedHashSet;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* 绩效报表(三期批2:报表引擎+接口) 服务实现类
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
public class ZtPerfReportServiceImpl implements IZtPerfReportService {
|
|
||||||
|
|
||||||
private static final String APPEAL_PENDING = "pending";
|
|
||||||
private static final String APPEAL_UPHELD = "upheld";
|
|
||||||
private static final String APPEAL_OVERTURNED = "overturned";
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtMonthScoreService monthScoreService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtPerfConfigService perfConfigService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtUserService userService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private PerfScoreEngine perfScoreEngine;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 批1指标计算器注册表(对接点;无计算器时 auto 项 score 为 null)
|
|
||||||
*/
|
|
||||||
@Autowired(required = false)
|
|
||||||
private List<PerfIndicatorCalculator> calculators = new ArrayList<>();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public PerfMonthScope report(String month, String role, String account) {
|
|
||||||
if (StringUtils.isBlank(account) && RiskUserThreadLocal.get() != null) {
|
|
||||||
// 未指定账号(前端“全部人员”选项 v1):默认当前登录人
|
|
||||||
account = RiskUserThreadLocal.get().getName();
|
|
||||||
}
|
|
||||||
checkMonthAccount(month, account);
|
|
||||||
if (StringUtils.isBlank(role)) {
|
|
||||||
// 岗位缺省按账号解析(批1 IZtPerfConfigService.resolveRole)
|
|
||||||
role = perfConfigService.resolveRole(account);
|
|
||||||
if (StringUtils.isBlank(role)) {
|
|
||||||
throw new BusinessException("岗位不能为空且无法按账号解析");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ZtMonthScore score = findMonthScore(account, month);
|
|
||||||
if (score != null && StringUtils.isNotBlank(score.getScopeJson())) {
|
|
||||||
return JSON.parseObject(score.getScopeJson(), PerfMonthScope.class);
|
|
||||||
}
|
|
||||||
// 未落库:实时计算(不写库)
|
|
||||||
return computeScope(account, month, role);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void generateMonthScore(String month, String role, String account) {
|
|
||||||
if (StringUtils.isBlank(month)) {
|
|
||||||
throw new BusinessException("月份不能为空");
|
|
||||||
}
|
|
||||||
Set<String> roles = new LinkedHashSet<>();
|
|
||||||
if (StringUtils.isNotBlank(role)) {
|
|
||||||
roles.add(role);
|
|
||||||
} else if (StringUtils.isNotBlank(account)) {
|
|
||||||
// 指定账号未指定岗位:按账号解析(批1 IZtPerfConfigService.resolveRole)
|
|
||||||
String resolved = perfConfigService.resolveRole(account);
|
|
||||||
if (StringUtils.isBlank(resolved)) {
|
|
||||||
throw new BusinessException("无法按账号解析岗位:" + account);
|
|
||||||
}
|
|
||||||
roles.add(resolved);
|
|
||||||
} else {
|
|
||||||
List<ZtPerfConfig> all = perfConfigService.list(new QueryWrapper<ZtPerfConfig>().eq("enabled", 1));
|
|
||||||
for (ZtPerfConfig c : all) {
|
|
||||||
if (StringUtils.isNotBlank(c.getRole())) {
|
|
||||||
roles.add(c.getRole());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (roles.isEmpty()) {
|
|
||||||
throw new BusinessException("未找到启用中的绩效规则配置");
|
|
||||||
}
|
|
||||||
|
|
||||||
List<String> accounts = new ArrayList<>();
|
|
||||||
if (StringUtils.isNotBlank(account)) {
|
|
||||||
accounts.add(account);
|
|
||||||
} else {
|
|
||||||
accounts = userService.list().stream().map(ZtUser::getAccount)
|
|
||||||
.filter(StringUtils::isNotBlank).distinct().collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
Date now = new Date();
|
|
||||||
String operator = RiskUserThreadLocal.get().getName();
|
|
||||||
for (String r : roles) {
|
|
||||||
for (String acc : accounts) {
|
|
||||||
PerfMonthScope scope = computeScope(acc, month, r);
|
|
||||||
ZtMonthScore exist = findMonthScore(acc, month);
|
|
||||||
if (exist == null) {
|
|
||||||
ZtMonthScore entity = new ZtMonthScore();
|
|
||||||
entity.setAccount(acc);
|
|
||||||
entity.setDateStr(month);
|
|
||||||
entity.setScopeJson(JSON.toJSONString(scope));
|
|
||||||
entity.setCreateDate(now);
|
|
||||||
entity.setCreateUser(operator);
|
|
||||||
entity.setUpdateDate(now);
|
|
||||||
entity.setUpdateUser(operator);
|
|
||||||
monthScoreService.save(entity);
|
|
||||||
} else {
|
|
||||||
// 重算:保留已有人工评分与申诉记录
|
|
||||||
if (StringUtils.isNotBlank(exist.getScopeJson())) {
|
|
||||||
mergeManualAndAppeals(scope, JSON.parseObject(exist.getScopeJson(), PerfMonthScope.class));
|
|
||||||
}
|
|
||||||
exist.setScopeJson(JSON.toJSONString(scope));
|
|
||||||
exist.setUpdateDate(now);
|
|
||||||
exist.setUpdateUser(operator);
|
|
||||||
monthScoreService.updateById(exist);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void manualScore(ZtPerfQo qo) {
|
|
||||||
checkMonthAccount(qo.getMonth(), qo.getAccount());
|
|
||||||
if (StringUtils.isBlank(qo.getItemKey()) || qo.getScore() == null) {
|
|
||||||
throw new BusinessException("指标与分值不能为空");
|
|
||||||
}
|
|
||||||
if (qo.getScore() < 0 || qo.getScore() > 100) {
|
|
||||||
throw new BusinessException("分值需在 0~100 之间");
|
|
||||||
}
|
|
||||||
ZtMonthScore entity = requireMonthScore(qo.getAccount(), qo.getMonth());
|
|
||||||
PerfMonthScope scope = JSON.parseObject(entity.getScopeJson(), PerfMonthScope.class);
|
|
||||||
PerfMonthScope.Item item = findItem(scope, qo.getItemKey());
|
|
||||||
if (item == null) {
|
|
||||||
throw new BusinessException("指标不存在:" + qo.getItemKey());
|
|
||||||
}
|
|
||||||
item.setScore(qo.getScore());
|
|
||||||
item.setSource(PerfScoreEngine.SOURCE_MANUAL);
|
|
||||||
item.setRemark(qo.getRemark());
|
|
||||||
perfScoreEngine.recomputeTotal(scope);
|
|
||||||
saveScopeJson(entity, scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void appeal(ZtPerfQo qo) {
|
|
||||||
checkMonthAccount(qo.getMonth(), qo.getAccount());
|
|
||||||
if (StringUtils.isBlank(qo.getItemKey()) || StringUtils.isBlank(qo.getReason())) {
|
|
||||||
throw new BusinessException("申诉指标与原因不能为空");
|
|
||||||
}
|
|
||||||
ZtMonthScore entity = requireMonthScore(qo.getAccount(), qo.getMonth());
|
|
||||||
PerfMonthScope scope = JSON.parseObject(entity.getScopeJson(), PerfMonthScope.class);
|
|
||||||
if (findItem(scope, qo.getItemKey()) == null) {
|
|
||||||
throw new BusinessException("指标不存在:" + qo.getItemKey());
|
|
||||||
}
|
|
||||||
if (scope.getAppeals() == null) {
|
|
||||||
scope.setAppeals(new ArrayList<>());
|
|
||||||
}
|
|
||||||
PerfMonthScope.Appeal appeal = new PerfMonthScope.Appeal();
|
|
||||||
appeal.setId(qo.getAccount() + "@" + qo.getMonth() + "#" + (scope.getAppeals().size() + 1));
|
|
||||||
appeal.setItemKey(qo.getItemKey());
|
|
||||||
appeal.setReason(qo.getReason());
|
|
||||||
appeal.setStatus(APPEAL_PENDING);
|
|
||||||
appeal.setCreateDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
|
|
||||||
scope.getAppeals().add(appeal);
|
|
||||||
saveScopeJson(entity, scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void appealReview(ZtPerfQo qo) {
|
|
||||||
if (StringUtils.isBlank(qo.getId()) || StringUtils.isBlank(qo.getResult())) {
|
|
||||||
throw new BusinessException("申诉 id 与复核结果不能为空");
|
|
||||||
}
|
|
||||||
if (!APPEAL_UPHELD.equals(qo.getResult()) && !APPEAL_OVERTURNED.equals(qo.getResult())) {
|
|
||||||
throw new BusinessException("复核结果仅支持 upheld/overturned");
|
|
||||||
}
|
|
||||||
// 申诉 id 规则:account@month#序号
|
|
||||||
String[] parts = qo.getId().split("[@#]");
|
|
||||||
if (parts.length != 3) {
|
|
||||||
throw new BusinessException("申诉 id 格式非法:" + qo.getId());
|
|
||||||
}
|
|
||||||
ZtMonthScore entity = requireMonthScore(parts[0], parts[1]);
|
|
||||||
PerfMonthScope scope = JSON.parseObject(entity.getScopeJson(), PerfMonthScope.class);
|
|
||||||
PerfMonthScope.Appeal target = null;
|
|
||||||
if (scope.getAppeals() != null) {
|
|
||||||
for (PerfMonthScope.Appeal a : scope.getAppeals()) {
|
|
||||||
if (qo.getId().equals(a.getId())) {
|
|
||||||
target = a;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (target == null) {
|
|
||||||
throw new BusinessException("申诉记录不存在:" + qo.getId());
|
|
||||||
}
|
|
||||||
if (!APPEAL_PENDING.equals(target.getStatus())) {
|
|
||||||
throw new BusinessException("申诉已复核,请勿重复操作");
|
|
||||||
}
|
|
||||||
target.setStatus(qo.getResult());
|
|
||||||
target.setReviewer(qo.getReviewer());
|
|
||||||
target.setReviewDate(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()));
|
|
||||||
|
|
||||||
if (APPEAL_OVERTURNED.equals(qo.getResult())) {
|
|
||||||
// 撤销:回滚该项扣分为满分并重算总分
|
|
||||||
PerfMonthScope.Item item = findItem(scope, target.getItemKey());
|
|
||||||
if (item != null) {
|
|
||||||
item.setScore(100D);
|
|
||||||
item.setDeduct(0D);
|
|
||||||
item.setRemark(StringUtils.isBlank(item.getRemark()) ? "申诉撤销回滚" : item.getRemark() + ";申诉撤销回滚");
|
|
||||||
}
|
|
||||||
perfScoreEngine.recomputeTotal(scope);
|
|
||||||
}
|
|
||||||
saveScopeJson(entity, scope);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实时计算:取岗位配置 + 批1指标计算器(PerfIndicatorCalculator 注册表),不调引擎外服务。
|
|
||||||
* 计算器命中时其返回值已是 0~100 得分(见其接口约定),通过 CTX_PRESCORED_ITEMS 告知引擎直接采信。
|
|
||||||
*/
|
|
||||||
private PerfMonthScope computeScope(String account, String month, String role) {
|
|
||||||
List<ZtPerfConfig> configs = perfConfigService.list(
|
|
||||||
new QueryWrapper<ZtPerfConfig>().eq("role", role).eq("enabled", 1));
|
|
||||||
if (CollectionUtils.isEmpty(configs)) {
|
|
||||||
throw new BusinessException("岗位无启用中的绩效规则配置:" + role);
|
|
||||||
}
|
|
||||||
Map<String, Double> metrics = new HashMap<>();
|
|
||||||
Map<String, String> rawDetails = new HashMap<>();
|
|
||||||
Set<String> preScored = new HashSet<>();
|
|
||||||
Map<String, PerfIndicatorCalculator> registry = new HashMap<>();
|
|
||||||
if (calculators != null) {
|
|
||||||
for (PerfIndicatorCalculator p : calculators) {
|
|
||||||
registry.put(p.key(), p);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (ZtPerfConfig config : configs) {
|
|
||||||
PerfIndicatorCalculator calculator = registry.get(config.getItemKey());
|
|
||||||
if (calculator == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
BigDecimal value = calculator.compute(account, month);
|
|
||||||
metrics.put(config.getItemKey(), value == null ? null : value.doubleValue());
|
|
||||||
preScored.add(config.getItemKey());
|
|
||||||
String detail = calculator.consumeRawDetail();
|
|
||||||
if (detail != null) {
|
|
||||||
rawDetails.put(config.getItemKey(), detail);
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// 单指标取数失败不阻塞整表:raw 置 null,该项 score 为 null
|
|
||||||
metrics.put(config.getItemKey(), null);
|
|
||||||
calculator.consumeRawDetail();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Map<String, Object> context = new HashMap<>();
|
|
||||||
context.put(PerfScoreEngine.CTX_PRESCORED_ITEMS, preScored);
|
|
||||||
PerfMonthScope scope = perfScoreEngine.compute(account, month, role, configs, metrics, context);
|
|
||||||
//CHG-041 过程值明细挂到对应指标项(弹窗「绩效数据」列展示)
|
|
||||||
if (scope.getItems() != null) {
|
|
||||||
for (PerfMonthScope.Item item : scope.getItems()) {
|
|
||||||
String detail = rawDetails.get(item.getItemKey());
|
|
||||||
if (detail != null) {
|
|
||||||
item.setRawDetail(detail);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 重算时把旧 scope 的人工评分(source=manual 且 score 非空)与申诉记录合并进新 scope
|
|
||||||
*/
|
|
||||||
private void mergeManualAndAppeals(PerfMonthScope newScope, PerfMonthScope oldScope) {
|
|
||||||
if (oldScope.getItems() != null) {
|
|
||||||
Map<String, PerfMonthScope.Item> oldItems = oldScope.getItems().stream()
|
|
||||||
.filter(i -> i.getItemKey() != null)
|
|
||||||
.collect(Collectors.toMap(PerfMonthScope.Item::getItemKey, i -> i, (a, b) -> a));
|
|
||||||
for (PerfMonthScope.Item item : newScope.getItems()) {
|
|
||||||
PerfMonthScope.Item old = oldItems.get(item.getItemKey());
|
|
||||||
if (old != null && PerfScoreEngine.SOURCE_MANUAL.equals(old.getSource()) && old.getScore() != null) {
|
|
||||||
item.setScore(old.getScore());
|
|
||||||
item.setSource(old.getSource());
|
|
||||||
item.setRemark(old.getRemark());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
newScope.setAppeals(oldScope.getAppeals() == null ? new ArrayList<>() : oldScope.getAppeals());
|
|
||||||
perfScoreEngine.recomputeTotal(newScope);
|
|
||||||
}
|
|
||||||
|
|
||||||
private PerfMonthScope.Item findItem(PerfMonthScope scope, String itemKey) {
|
|
||||||
if (scope.getItems() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
for (PerfMonthScope.Item item : scope.getItems()) {
|
|
||||||
if (itemKey.equals(item.getItemKey())) {
|
|
||||||
return item;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
private ZtMonthScore findMonthScore(String account, String month) {
|
|
||||||
List<ZtMonthScore> list = monthScoreService.list(new QueryWrapper<ZtMonthScore>()
|
|
||||||
.eq("account", account).eq("date_str", month));
|
|
||||||
return CollectionUtils.isEmpty(list) ? null : list.get(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
private ZtMonthScore requireMonthScore(String account, String month) {
|
|
||||||
ZtMonthScore entity = findMonthScore(account, month);
|
|
||||||
if (entity == null || StringUtils.isBlank(entity.getScopeJson())) {
|
|
||||||
throw new BusinessException("月度得分未生成,请先调用 generateMonthScore");
|
|
||||||
}
|
|
||||||
return entity;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void saveScopeJson(ZtMonthScore entity, PerfMonthScope scope) {
|
|
||||||
entity.setScopeJson(JSON.toJSONString(scope));
|
|
||||||
entity.setUpdateDate(new Date());
|
|
||||||
//无登录态(单测/未来调度线程)兜底 system,防 NPE(CHG-069)
|
|
||||||
entity.setUpdateUser(RiskUserThreadLocal.get() == null ? "system" : RiskUserThreadLocal.get().getName());
|
|
||||||
monthScoreService.updateById(entity);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void checkMonthAccount(String month, String account) {
|
|
||||||
if (StringUtils.isBlank(month) || StringUtils.isBlank(account)) {
|
|
||||||
throw new BusinessException("月份与账号不能为空");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
package com.sa.zentao.service.impl;
|
package com.sa.zentao.service.impl;
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
import com.alibaba.fastjson.JSONObject;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.github.pagehelper.Page;
|
import com.github.pagehelper.Page;
|
||||||
import com.github.pagehelper.PageHelper;
|
import com.github.pagehelper.PageHelper;
|
||||||
@@ -145,17 +144,12 @@ public class ZtProductServiceImpl extends ServiceImpl<ZtProductMapper, ZtProduct
|
|||||||
userList.addAll(result.stream().filter(o->!StringUtils.isEmpty(o.getPo())).map(o->o.getPo()).collect(Collectors.toList()));
|
userList.addAll(result.stream().filter(o->!StringUtils.isEmpty(o.getPo())).map(o->o.getPo()).collect(Collectors.toList()));
|
||||||
Map<String, ZtUser> userMap = this.userService.userMapByIds(null);
|
Map<String, ZtUser> userMap = this.userService.userMapByIds(null);
|
||||||
|
|
||||||
//CHG-070:产品列表统计只要计数,禁止 SELECT *——zt_bug.steps(MEDIUMTEXT,页均 44MB)等大字段拖垮接口(实测 7.3s→0.05s)
|
List<ZtStory> allStoryList = this.storyService.list(new QueryWrapper<ZtStory>().lambda().in(ZtStory::getProduct, result.stream().map(o -> o.getId()).collect(Collectors.toList())));
|
||||||
List<ZtStory> allStoryList = this.storyService.list(new QueryWrapper<ZtStory>().lambda()
|
|
||||||
.select(ZtStory::getId, ZtStory::getProduct, ZtStory::getStatus, ZtStory::getStage)
|
|
||||||
.in(ZtStory::getProduct, result.stream().map(o -> o.getId()).collect(Collectors.toList())));
|
|
||||||
List<ZtBug> allBugList = bugService.list(new QueryWrapper<ZtBug>().lambda()
|
List<ZtBug> allBugList = bugService.list(new QueryWrapper<ZtBug>().lambda()
|
||||||
.select(ZtBug::getId, ZtBug::getProduct, ZtBug::getStatus)
|
|
||||||
.ne(ZtBug::getStatus,"closed")
|
.ne(ZtBug::getStatus,"closed")
|
||||||
.in(ZtBug::getProduct, result.stream().map(o -> o.getId()).collect(Collectors.toList())));
|
.in(ZtBug::getProduct, result.stream().map(o -> o.getId()).collect(Collectors.toList())));
|
||||||
List<ZtStoryUser> allUserStoryList = this.storyUserService.list(new QueryWrapper<ZtStoryUser>().lambda()
|
|
||||||
.select(ZtStoryUser::getId, ZtStoryUser::getProduct, ZtStoryUser::getStatus)
|
List<ZtStoryUser> allUserStoryList = this.storyUserService.list(new QueryWrapper<ZtStoryUser>().lambda().in(ZtStoryUser::getProduct, result.stream().map(o -> o.getId()).collect(Collectors.toList())));
|
||||||
.in(ZtStoryUser::getProduct, result.stream().map(o -> o.getId()).collect(Collectors.toList())));
|
|
||||||
|
|
||||||
for (ZtProductDTO p:result ) {
|
for (ZtProductDTO p:result ) {
|
||||||
List<ZtStory> storyList = allStoryList.stream().filter(o->o.getProduct().equals(p.getId())).collect(Collectors.toList());
|
List<ZtStory> storyList = allStoryList.stream().filter(o->o.getProduct().equals(p.getId())).collect(Collectors.toList());
|
||||||
@@ -693,63 +687,4 @@ public class ZtProductServiceImpl extends ServiceImpl<ZtProductMapper, ZtProduct
|
|||||||
|
|
||||||
return ztProjectDTOS.stream().collect(Collectors.groupingBy(ZtProductDTO::getProgram));
|
return ztProjectDTOS.stream().collect(Collectors.groupingBy(ZtProductDTO::getProgram));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public PageInfo<ZtProjectDTO> getExecutionByProductId(ZtProjectQo qo) {
|
|
||||||
Integer productId = qo.getProductId();
|
|
||||||
if (productId == null || productId == 0) {
|
|
||||||
throw new BusinessException("产品ID不能为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 查询产品关联的项目
|
|
||||||
List<ZtProjectproduct> projectproductlist = this.projectproductService.list(
|
|
||||||
new QueryWrapper<ZtProjectproduct>().lambda()
|
|
||||||
.eq(ZtProjectproduct::getProduct, productId)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (CollectionUtils.isEmpty(projectproductlist)) {
|
|
||||||
return new PageInfo<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取项目ID列表
|
|
||||||
List<Integer> projectIds = projectproductlist.stream()
|
|
||||||
.map(ZtProjectproduct::getProject)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
// 查询项目关联的迭代
|
|
||||||
List<ZtExecutionproject> executionprojectList = this.executionprojectService.list(
|
|
||||||
new QueryWrapper<ZtExecutionproject>().lambda()
|
|
||||||
.in(ZtExecutionproject::getProject, projectIds)
|
|
||||||
);
|
|
||||||
|
|
||||||
if (CollectionUtils.isEmpty(executionprojectList)) {
|
|
||||||
return new PageInfo<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取迭代ID列表
|
|
||||||
List<Integer> executionIds = executionprojectList.stream()
|
|
||||||
.map(ZtExecutionproject::getExecution)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
|
|
||||||
// 分页查询迭代,按ID倒序排列
|
|
||||||
com.github.pagehelper.Page<ZtProjectDTO> page = PageHelper.startPage(qo.getCurrentPage(), qo.getPageSize());
|
|
||||||
LambdaQueryWrapper<ZtProject> queryWrapper = new QueryWrapper<ZtProject>().lambda()
|
|
||||||
.in(ZtProject::getId, executionIds);
|
|
||||||
|
|
||||||
// 添加名称搜索
|
|
||||||
if (!StringUtils.isEmpty(qo.getName())) {
|
|
||||||
queryWrapper.like(ZtProject::getName, qo.getName());
|
|
||||||
}
|
|
||||||
|
|
||||||
queryWrapper.orderByDesc(ZtProject::getId);
|
|
||||||
|
|
||||||
List<ZtProject> executionList = this.projectService.list(queryWrapper);
|
|
||||||
|
|
||||||
// 转换为DTO
|
|
||||||
List<ZtProjectDTO> dtoList = BeanCopyUtil.copyListProperties(executionList, ZtProjectDTO::new);
|
|
||||||
|
|
||||||
PageInfo<ZtProjectDTO> ztProjectDTOPageInfo = new PageInfo<>(dtoList);
|
|
||||||
ztProjectDTOPageInfo.setTotal(page.getTotal());
|
|
||||||
return ztProjectDTOPageInfo;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -260,6 +260,7 @@ public class ZtProjectServiceImpl extends ServiceImpl<ZtProjectMapper, ZtProject
|
|||||||
cell.setColumn(ztKanbancolumn.getId());
|
cell.setColumn(ztKanbancolumn.getId());
|
||||||
cell.setType(ztKanbanlane.getType());
|
cell.setType(ztKanbanlane.getType());
|
||||||
kanbancellService.save(cell);
|
kanbancellService.save(cell);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -313,11 +314,8 @@ public class ZtProjectServiceImpl extends ServiceImpl<ZtProjectMapper, ZtProject
|
|||||||
public void projectClose(ZtProjectDTO dto) {
|
public void projectClose(ZtProjectDTO dto) {
|
||||||
|
|
||||||
ZtProject ztProject = this.baseMapper.selectById(dto.getId());
|
ZtProject ztProject = this.baseMapper.selectById(dto.getId());
|
||||||
if (ztProject == null) {
|
if (ztProject == null || "closed".equalsIgnoreCase(ztProject.getStatus())) {
|
||||||
throw new BusinessException("未查询到项目,项目ID: " + dto.getId());
|
throw new BusinessException("未查询到");
|
||||||
}
|
|
||||||
if ("closed".equalsIgnoreCase(ztProject.getStatus())) {
|
|
||||||
throw new BusinessException("项目已关闭,无法重复关闭,项目ID: " + dto.getId());
|
|
||||||
}
|
}
|
||||||
ztProject.setRealEnd(dto.getClosedDate());
|
ztProject.setRealEnd(dto.getClosedDate());
|
||||||
ztProject.setLastEditedDate(new Date());
|
ztProject.setLastEditedDate(new Date());
|
||||||
@@ -353,7 +351,6 @@ public class ZtProjectServiceImpl extends ServiceImpl<ZtProjectMapper, ZtProject
|
|||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
|
||||||
public void projectStart(ZtProjectDTO dto) {
|
public void projectStart(ZtProjectDTO dto) {
|
||||||
ZtProject ztProject = this.baseMapper.selectById(dto.getId());
|
ZtProject ztProject = this.baseMapper.selectById(dto.getId());
|
||||||
if (ztProject == null || !"wait".equalsIgnoreCase(ztProject.getStatus())) {
|
if (ztProject == null || !"wait".equalsIgnoreCase(ztProject.getStatus())) {
|
||||||
@@ -389,11 +386,8 @@ public class ZtProjectServiceImpl extends ServiceImpl<ZtProjectMapper, ZtProject
|
|||||||
@Override
|
@Override
|
||||||
public void projectDeleted(ZtProjectDTO dto) {
|
public void projectDeleted(ZtProjectDTO dto) {
|
||||||
ZtProject ztProject = this.baseMapper.selectById(dto.getId());
|
ZtProject ztProject = this.baseMapper.selectById(dto.getId());
|
||||||
if (ztProject == null) {
|
if (ztProject == null || "1".equalsIgnoreCase(ztProject.getDeleted())) {
|
||||||
throw new BusinessException("未查询到项目,项目ID: " + dto.getId());
|
throw new BusinessException("未查询到");
|
||||||
}
|
|
||||||
if ("1".equalsIgnoreCase(ztProject.getDeleted())) {
|
|
||||||
throw new BusinessException("项目已删除,无法重复删除,项目ID: " + dto.getId());
|
|
||||||
}
|
}
|
||||||
ztProject.setDeleted("1");
|
ztProject.setDeleted("1");
|
||||||
ztProject.setCanceledBy(RiskUserThreadLocal.get().getName());
|
ztProject.setCanceledBy(RiskUserThreadLocal.get().getName());
|
||||||
@@ -428,11 +422,8 @@ public class ZtProjectServiceImpl extends ServiceImpl<ZtProjectMapper, ZtProject
|
|||||||
@Override
|
@Override
|
||||||
public void projectActivated(ZtProjectDTO dto) {
|
public void projectActivated(ZtProjectDTO dto) {
|
||||||
ZtProject ztProject = this.baseMapper.selectById(dto.getId());
|
ZtProject ztProject = this.baseMapper.selectById(dto.getId());
|
||||||
if (ztProject == null) {
|
if (ztProject == null || !"closed".equalsIgnoreCase(ztProject.getStatus())) {
|
||||||
throw new BusinessException("未查询到项目,项目ID: " + dto.getId());
|
throw new BusinessException("未查询到");
|
||||||
}
|
|
||||||
if (!"closed".equalsIgnoreCase(ztProject.getStatus())) {
|
|
||||||
throw new BusinessException("项目未关闭,无法激活,当前状态: " + ztProject.getStatus() + ",项目ID: " + dto.getId());
|
|
||||||
}
|
}
|
||||||
LambdaUpdateWrapper<ZtProject> updateWrapper = new UpdateWrapper<ZtProject>().lambda().set(ZtProject::getStatus, "doing")
|
LambdaUpdateWrapper<ZtProject> updateWrapper = new UpdateWrapper<ZtProject>().lambda().set(ZtProject::getStatus, "doing")
|
||||||
.set(ZtProject::getRealEnd,null)
|
.set(ZtProject::getRealEnd,null)
|
||||||
@@ -467,11 +458,8 @@ public class ZtProjectServiceImpl extends ServiceImpl<ZtProjectMapper, ZtProject
|
|||||||
@Override
|
@Override
|
||||||
public void projectSuspended(ZtProjectDTO dto) {
|
public void projectSuspended(ZtProjectDTO dto) {
|
||||||
ZtProject ztProject = this.baseMapper.selectById(dto.getId());
|
ZtProject ztProject = this.baseMapper.selectById(dto.getId());
|
||||||
if (ztProject == null) {
|
if (ztProject == null || !"wait".equalsIgnoreCase(ztProject.getStatus())) {
|
||||||
throw new BusinessException("未查询到项目,项目ID: " + dto.getId());
|
throw new BusinessException("未查询到");
|
||||||
}
|
|
||||||
if (!"wait".equalsIgnoreCase(ztProject.getStatus())) {
|
|
||||||
throw new BusinessException("项目状态不是等待状态,无法挂起,当前状态: " + ztProject.getStatus() + ",项目ID: " + dto.getId());
|
|
||||||
}
|
}
|
||||||
ztProject.setStatus("suspended");
|
ztProject.setStatus("suspended");
|
||||||
ztProject.setLastEditedBy(RiskUserThreadLocal.get().getName());
|
ztProject.setLastEditedBy(RiskUserThreadLocal.get().getName());
|
||||||
@@ -1118,7 +1106,7 @@ public class ZtProjectServiceImpl extends ServiceImpl<ZtProjectMapper, ZtProject
|
|||||||
if(project!=null&&project!=0){
|
if(project!=null&&project!=0){
|
||||||
List<ZtExecutionproject> list = this.executionprojectService.list(new QueryWrapper<ZtExecutionproject>().lambda().eq(ZtExecutionproject::getProject, project));
|
List<ZtExecutionproject> list = this.executionprojectService.list(new QueryWrapper<ZtExecutionproject>().lambda().eq(ZtExecutionproject::getProject, project));
|
||||||
if(!CollectionUtils.isEmpty(list)){
|
if(!CollectionUtils.isEmpty(list)){
|
||||||
return this.list(new QueryWrapper<ZtProject>().lambda().in(ZtProject::getId,list.stream().map(o->o.getExecution()).collect(Collectors.toList())).orderByDesc(ZtProject::getId));
|
return this.listByIds(list.stream().map(o->o.getExecution()).collect(Collectors.toList()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1130,7 +1118,7 @@ public class ZtProjectServiceImpl extends ServiceImpl<ZtProjectMapper, ZtProject
|
|||||||
in(ZtExecutionproject::getProject, list.stream().map(o->o.getProject()).collect(Collectors.toList())));
|
in(ZtExecutionproject::getProject, list.stream().map(o->o.getProject()).collect(Collectors.toList())));
|
||||||
if(!CollectionUtils.isEmpty(eList)){
|
if(!CollectionUtils.isEmpty(eList)){
|
||||||
return this.list(new QueryWrapper<ZtProject>().lambda().in(ZtProject::getId,eList.stream().map(o->o.getExecution()).collect(Collectors.toList()))
|
return this.list(new QueryWrapper<ZtProject>().lambda().in(ZtProject::getId,eList.stream().map(o->o.getExecution()).collect(Collectors.toList()))
|
||||||
.ne(ZtProject::getStatus,"closed").ne(ZtProject::getDeleted,"1").orderByDesc(ZtProject::getId)
|
.ne(ZtProject::getStatus,"closed").ne(ZtProject::getDeleted,"1")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1337,10 +1325,8 @@ public class ZtProjectServiceImpl extends ServiceImpl<ZtProjectMapper, ZtProject
|
|||||||
return new HashMap<>();
|
return new HashMap<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
//仅取预计开始在本月的任务:下方按天循环只匹配 estStarted 落在当月的任务,历史任务对结果无贡献(性能优化,口径不变)
|
|
||||||
List<ZtTask> list = this.taskService.list(new QueryWrapper<ZtTask>().lambda().select(SFunctionColums.taskColumes())
|
List<ZtTask> list = this.taskService.list(new QueryWrapper<ZtTask>().lambda().select(SFunctionColums.taskColumes())
|
||||||
.notIn(ZtTask::getStatus,"cancel","closed","reviewing","draft")
|
.notIn(ZtTask::getStatus,"cancel","closed","reviewing","draft")
|
||||||
.ge(ZtTask::getEstStarted, firstDayOfMonth).le(ZtTask::getEstStarted, lastDayOfMonth)
|
|
||||||
.in(ZtTask::getExecution, execList.stream().map(o -> o.getExecution()).collect(Collectors.toList())));
|
.in(ZtTask::getExecution, execList.stream().map(o -> o.getExecution()).collect(Collectors.toList())));
|
||||||
if (CollectionUtils.isEmpty(list)) {
|
if (CollectionUtils.isEmpty(list)) {
|
||||||
return new HashMap<>();
|
return new HashMap<>();
|
||||||
@@ -1371,16 +1357,8 @@ public class ZtProjectServiceImpl extends ServiceImpl<ZtProjectMapper, ZtProject
|
|||||||
|
|
||||||
List<String> accountIds = teams.stream().map(o -> o.getAccount()).distinct().collect(Collectors.toList());
|
List<String> accountIds = teams.stream().map(o -> o.getAccount()).distinct().collect(Collectors.toList());
|
||||||
|
|
||||||
//按需取用户:团队成员 ∪ 任务相关人(原 userService.list() 全表扫描;不在用户表的账号行为不变,仍为 null 跳过)
|
List<ZtUser> userList = this.userService.list();
|
||||||
Set<String> userAccounts = new HashSet<>(accountIds);
|
Map<String, ZtUser> uMap = userList.stream().collect(Collectors.toMap(ZtUser::getAccount, O -> O));
|
||||||
for (ZtTask t : list) {
|
|
||||||
if (!StringUtils.isEmpty(t.getAssignedTo())) userAccounts.add(t.getAssignedTo());
|
|
||||||
if (!StringUtils.isEmpty(t.getOpenedby())) userAccounts.add(t.getOpenedby());
|
|
||||||
if (!StringUtils.isEmpty(t.getFinishedby())) userAccounts.add(t.getFinishedby());
|
|
||||||
if (!StringUtils.isEmpty(t.getCanceledby())) userAccounts.add(t.getCanceledby());
|
|
||||||
if (!StringUtils.isEmpty(t.getClosedby())) userAccounts.add(t.getClosedby());
|
|
||||||
}
|
|
||||||
Map<String, ZtUser> uMap = this.userService.userMapByIds(new ArrayList<>(userAccounts));
|
|
||||||
|
|
||||||
for (ZtTask t : list) {
|
for (ZtTask t : list) {
|
||||||
if (t.getDeadline() != null) {
|
if (t.getDeadline() != null) {
|
||||||
@@ -1408,16 +1386,17 @@ public class ZtProjectServiceImpl extends ServiceImpl<ZtProjectMapper, ZtProject
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//预分组:yyyy-MM-dd#账号 → 任务列表(原 天数×人数×任务数 三层扫描降为 O(任务数);组内顺序与原 stream filter 一致)
|
|
||||||
Map<String, List<ZtTask>> taskByDayAccount = list.stream()
|
|
||||||
.filter(o -> o.getEstStarted() != null && !StringUtils.isEmpty(o.getAssignedTo()))
|
|
||||||
.collect(Collectors.groupingBy(o -> DateUtils.formatDate(o.getEstStarted()) + "#" + o.getAssignedTo()));
|
|
||||||
for (int i = 0; i < DateUtils.getDays(date); i++) {
|
for (int i = 0; i < DateUtils.getDays(date); i++) {
|
||||||
Date d = DateUtils.dateAddDay(firstDayOfMonth, i);
|
Date d = DateUtils.dateAddDay(firstDayOfMonth, i);
|
||||||
List<Map<String, Object>> l = new ArrayList<>();
|
List<Map<String, Object>> l = new ArrayList<>();
|
||||||
for (String s : accountIds) {
|
for (String s : accountIds) {
|
||||||
ZtUser ztUser = uMap.get(s);
|
ZtUser ztUser = uMap.get(s);
|
||||||
List<ZtTask> taskList = taskByDayAccount.getOrDefault(DateUtils.formatDate(d) + "#" + s, Collections.emptyList());
|
// List<ZtEffort> fList = efforts.stream().filter(o -> o.getAccount().equals(s))
|
||||||
|
// .filter(o->DateUtils.formatDate(o.getDate()).equals(DateUtils.formatDate(d)))
|
||||||
|
// .collect(Collectors.toList());
|
||||||
|
List<ZtTask> taskList = list.stream().filter(o -> o.getEstStarted() != null && DateUtils.formatDate(o.getEstStarted()).equals(DateUtils.formatDate(d)))
|
||||||
|
.filter(o -> o.getAssignedTo().equals(ztUser.getAccount()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
for (ZtTask t:taskList ) {
|
for (ZtTask t:taskList ) {
|
||||||
t.setAssignedToName(ztUser.getNickname());
|
t.setAssignedToName(ztUser.getNickname());
|
||||||
@@ -1962,9 +1941,9 @@ public class ZtProjectServiceImpl extends ServiceImpl<ZtProjectMapper, ZtProject
|
|||||||
|
|
||||||
List<ZtTask> allTaskList =this.taskService.taskListByEIdsAndDate(firstDayOfMonth,lastDayOfMonth,products.stream().map(o->o.getId()).collect(Collectors.toList()));
|
List<ZtTask> allTaskList =this.taskService.taskListByEIdsAndDate(firstDayOfMonth,lastDayOfMonth,products.stream().map(o->o.getId()).collect(Collectors.toList()));
|
||||||
|
|
||||||
// if (CollectionUtils.isEmpty(allTaskList)) {
|
if (CollectionUtils.isEmpty(allTaskList)) {
|
||||||
// return new PageInfo<>();
|
return new PageInfo<>();
|
||||||
// }
|
}
|
||||||
|
|
||||||
|
|
||||||
List<ZtTeam> teams = this.teamService.list(new QueryWrapper<ZtTeam>().lambda().eq(ZtTeam::getType, "execution")
|
List<ZtTeam> teams = this.teamService.list(new QueryWrapper<ZtTeam>().lambda().eq(ZtTeam::getType, "execution")
|
||||||
@@ -1986,9 +1965,6 @@ public class ZtProjectServiceImpl extends ServiceImpl<ZtProjectMapper, ZtProject
|
|||||||
Map<String,ZtMonthScore> scoreMap=getAccountScoreMap(date,accountIds);
|
Map<String,ZtMonthScore> scoreMap=getAccountScoreMap(date,accountIds);
|
||||||
|
|
||||||
|
|
||||||
//CHG-074:月度达标工时(团队口径人均,与绩效弹窗 myWorkScore 一致)——在窗执行 KFZ 成员 (工作天数×人数−团队请假÷8)×5÷人数
|
|
||||||
BigDecimal teamPerCapita = countService.teamExamineTime(accountIds, firstDayOfMonth, lastDayOfMonth, date);
|
|
||||||
|
|
||||||
for (String account :accountIds) {
|
for (String account :accountIds) {
|
||||||
ZtUser ztUser = uMap.get(account);
|
ZtUser ztUser = uMap.get(account);
|
||||||
|
|
||||||
@@ -2016,11 +1992,11 @@ public class ZtProjectServiceImpl extends ServiceImpl<ZtProjectMapper, ZtProject
|
|||||||
dto=countService.setUserWorkTime(dto,date,approvalList,firstDayOfMonth);
|
dto=countService.setUserWorkTime(dto,date,approvalList,firstDayOfMonth);
|
||||||
|
|
||||||
d.setQjTime(dto.getApprovalDays());
|
d.setQjTime(dto.getApprovalDays());
|
||||||
//可用工时(月度达标工时=团队口径人均,CHG-074;无 KFZ 成员时退回个人 0.75 口径兜底)
|
//可用工时
|
||||||
d.setHaveTime(teamPerCapita.signum() > 0 ? teamPerCapita : dto.getExamineTime());
|
d.setHaveTime(dto.getExamineTime());
|
||||||
|
|
||||||
//工作饱和度(CHG-074:分子=实绩工时 consumed、分母=团队口径达标工时,与绩效弹窗一致)
|
//工作饱和度
|
||||||
d.setSaturation(BigDecimalUtils.isZero(d.getHaveTime())?BigDecimal.valueOf(1):BigDecimalUtils.isZero(d.getWorkTime())?BigDecimal.ZERO:d.getWorkTime().divide(d.getHaveTime(),2,BigDecimal.ROUND_UP));
|
d.setSaturation(BigDecimalUtils.isZero(d.getHaveTime())?BigDecimal.valueOf(1):BigDecimalUtils.isZero(d.getWorkTime())?BigDecimal.ZERO:d.getStoryTotalTime().divide(d.getHaveTime(),2,BigDecimal.ROUND_UP));
|
||||||
// 任务总量 .filter(o->o.getStatus().equals("done"))
|
// 任务总量 .filter(o->o.getStatus().equals("done"))
|
||||||
d.setTaskCount(BigDecimal.valueOf(taskList.stream().collect(Collectors.toList()).size()));
|
d.setTaskCount(BigDecimal.valueOf(taskList.stream().collect(Collectors.toList()).size()));
|
||||||
|
|
||||||
|
|||||||
@@ -112,7 +112,7 @@ public class ZtReleaseServiceImpl extends ServiceImpl<ZtReleaseMapper, ZtRelease
|
|||||||
List<ZtStory> sList = this.storyService.list(new QueryWrapper<ZtStory>()
|
List<ZtStory> sList = this.storyService.list(new QueryWrapper<ZtStory>()
|
||||||
.lambda().eq(ZtStory::getStatus, "active")
|
.lambda().eq(ZtStory::getStatus, "active")
|
||||||
.in(ZtStory::getId, list.stream().map(o -> o.getStory()).distinct().collect(Collectors.toList()))
|
.in(ZtStory::getId, list.stream().map(o -> o.getStory()).distinct().collect(Collectors.toList()))
|
||||||
.eq(ZtStory::getStage, StoryStageEnums.productVerified.getValue()));
|
.eq(ZtStory::getStage, StoryStageEnums.tested.getValue()));
|
||||||
if (!CollectionUtils.isEmpty(sList)) {
|
if (!CollectionUtils.isEmpty(sList)) {
|
||||||
//没有被关联的需求 测试完成的
|
//没有被关联的需求 测试完成的
|
||||||
List<ZtReleaseDetails> releaseStoryList = this.releaseDetailsService.list(new QueryWrapper<ZtReleaseDetails>().lambda()
|
List<ZtReleaseDetails> releaseStoryList = this.releaseDetailsService.list(new QueryWrapper<ZtReleaseDetails>().lambda()
|
||||||
@@ -368,9 +368,9 @@ public class ZtReleaseServiceImpl extends ServiceImpl<ZtReleaseMapper, ZtRelease
|
|||||||
List<ZtStory> ztStories = CollectionUtils.isEmpty(storyList)?new ArrayList<>():storyService.listByIds(storyList);
|
List<ZtStory> ztStories = CollectionUtils.isEmpty(storyList)?new ArrayList<>():storyService.listByIds(storyList);
|
||||||
|
|
||||||
List<ZtBug> bugs =CollectionUtils.isEmpty(bugList)?new ArrayList<>(): bugService.listByIds(bugList);
|
List<ZtBug> bugs =CollectionUtils.isEmpty(bugList)?new ArrayList<>(): bugService.listByIds(bugList);
|
||||||
List<ZtStory> notProductYsList = ztStories.stream().filter(o -> !o.getStage().equals(StoryStageEnums.productVerified.getValue())).toList();
|
long notTested = ztStories.stream().filter(o -> !o.getStage().equals("tested")).count();
|
||||||
if(!CollectionUtils.isEmpty(notProductYsList)){
|
if(notTested>0){
|
||||||
throw new BusinessException("当前需求存在未产品验收的需求请检查 id="+notProductYsList.get(0).getId()+" name="+notProductYsList.get(0).getTitle());
|
throw new BusinessException("当前需求存在未测试完成的请检查");
|
||||||
}
|
}
|
||||||
//'active','resolved','closed'?
|
//'active','resolved','closed'?
|
||||||
long count = bugs.stream().filter(o -> !Arrays.asList("resolved","closed").contains(o.getStatus())).count();
|
long count = bugs.stream().filter(o -> !Arrays.asList("resolved","closed").contains(o.getStatus())).count();
|
||||||
@@ -378,7 +378,7 @@ public class ZtReleaseServiceImpl extends ServiceImpl<ZtReleaseMapper, ZtRelease
|
|||||||
throw new BusinessException("当前bug存在未完成请检查");
|
throw new BusinessException("当前bug存在未完成请检查");
|
||||||
}
|
}
|
||||||
for (ZtStory st : ztStories) {
|
for (ZtStory st : ztStories) {
|
||||||
if (! StoryStageEnums.productVerified.getValue().equals(st.getStage())) {
|
if (!"tested".equals(st.getStage())) {
|
||||||
throw new BusinessException(st.getTitle() + " 当前需求无法发布,请检查");
|
throw new BusinessException(st.getTitle() + " 当前需求无法发布,请检查");
|
||||||
}
|
}
|
||||||
this.storyService.releaseStory(st.getId());
|
this.storyService.releaseStory(st.getId());
|
||||||
@@ -391,6 +391,8 @@ public class ZtReleaseServiceImpl extends ServiceImpl<ZtReleaseMapper, ZtRelease
|
|||||||
}
|
}
|
||||||
if(!CollectionUtils.isEmpty(ztStories)){
|
if(!CollectionUtils.isEmpty(ztStories)){
|
||||||
sendReleaseMail(ztStories);
|
sendReleaseMail(ztStories);
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
actionService.addAction(ActionType.FB, ActionStatus.FBSB, ztRelease.getId(), ztRelease.getProduct() + "", ztRelease.getProject(), null,
|
actionService.addAction(ActionType.FB, ActionStatus.FBSB, ztRelease.getId(), ztRelease.getProduct() + "", ztRelease.getProject(), null,
|
||||||
|
|||||||
@@ -1,156 +0,0 @@
|
|||||||
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.conf.LoginRiskUser;
|
|
||||||
import com.sa.zentao.conf.RiskUserThreadLocal;
|
|
||||||
import com.sa.zentao.dao.BusinessException;
|
|
||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* 服务实现类
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author gqb
|
|
||||||
* @since 2026-03-30
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
public class ZtStoryExpandServiceImpl extends ServiceImpl<ZtStoryExpandMapper, ZtStoryExpand> implements IZtStoryExpandService {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtStoryMonthWorkloadService ztStoryMonthWorkloadService;
|
|
||||||
@Autowired
|
|
||||||
private IZtStoryExpandService ztStoryExpandService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional(rollbackFor = Exception.class)
|
|
||||||
public void saveOrUpdateExpand(ZtStoryExpand entity) {
|
|
||||||
//仅 AI 框架通道可用:必须携带 ai 账户 token(CHG-061,二期必决项落地)
|
|
||||||
LoginRiskUser aiLoginUser = RiskUserThreadLocal.get();
|
|
||||||
if (aiLoginUser == null || !"ai".equals(aiLoginUser.getName())) {
|
|
||||||
throw new BusinessException("该接口仅AI框架通道可用(需ai账户token)");
|
|
||||||
}
|
|
||||||
if (entity == null || entity.getStoryId() == null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QueryWrapper<ZtStoryExpand> 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<ZtStoryExpand> 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<ZtStoryExpand> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,97 +0,0 @@
|
|||||||
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;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* <p>
|
|
||||||
* 服务实现类
|
|
||||||
* </p>
|
|
||||||
*
|
|
||||||
* @author gqb
|
|
||||||
* @since 2026-03-30
|
|
||||||
*/
|
|
||||||
@Service
|
|
||||||
public class ZtStoryMonthWorkloadServiceImpl extends ServiceImpl<ZtStoryMonthWorkloadMapper, ZtStoryMonthWorkload> 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<ZtStoryMonthWorkload> 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<String> listMonths() {
|
|
||||||
QueryWrapper<ZtStoryMonthWorkload> 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<ZtStoryMonthWorkload> wrapper = new QueryWrapper<>();
|
|
||||||
wrapper.eq("story_id", storyId).eq("time", month);
|
|
||||||
return this.getOne(wrapper);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BigDecimal getMaxWorkloadBefore(Integer storyId, String month) {
|
|
||||||
QueryWrapper<ZtStoryMonthWorkload> 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.sa.zentao.service.impl;
|
package com.sa.zentao.service.impl;
|
||||||
|
|
||||||
import cn.hutool.core.util.ObjectUtil;
|
|
||||||
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.JSON;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
@@ -157,14 +156,6 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
if (ztUser != null) {
|
if (ztUser != null) {
|
||||||
d.setUserStoryCreateUser(ztUser.getNickname());
|
d.setUserStoryCreateUser(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());
|
|
||||||
}
|
|
||||||
List<ZtProject> ztProjectList = executionMapByStory.get(d.getId());
|
List<ZtProject> ztProjectList = executionMapByStory.get(d.getId());
|
||||||
if (!CollectionUtils.isEmpty(ztProjectList)) {
|
if (!CollectionUtils.isEmpty(ztProjectList)) {
|
||||||
// d.setExecution(ztProject.getId());
|
// d.setExecution(ztProject.getId());
|
||||||
@@ -502,8 +493,6 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
Integer id = dto.getId();
|
Integer id = dto.getId();
|
||||||
ZtStory ztStory = this.baseMapper.selectById(id);
|
ZtStory ztStory = this.baseMapper.selectById(id);
|
||||||
String oldAssignedTo = ztStory.getAssignedTo();
|
String oldAssignedTo = ztStory.getAssignedTo();
|
||||||
String title = dto.getTitle();
|
|
||||||
String oldTitle = ztStory.getTitle();
|
|
||||||
if ("closed".equals(ztStory.getStatus())) {
|
if ("closed".equals(ztStory.getStatus())) {
|
||||||
throw new BusinessException("当前已关闭");
|
throw new BusinessException("当前已关闭");
|
||||||
}
|
}
|
||||||
@@ -656,10 +645,7 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
actionService.addAction(ActionType.XQ, ActionStatus.BJ, dto.getId(), dto.getProduct() + "", dto.getProject(), null,
|
actionService.addAction(ActionType.XQ, ActionStatus.BJ, dto.getId(), dto.getProduct() + "", dto.getProject(), null,
|
||||||
RiskUserThreadLocal.get().getName(), dto.getRemark(), "");
|
RiskUserThreadLocal.get().getName(), dto.getRemark(), "");
|
||||||
}
|
}
|
||||||
if(!title.equals(oldTitle)){
|
|
||||||
actionService.addAction(ActionType.XQ, ActionStatus.BJ, dto.getId(), dto.getProduct() + "", dto.getProject(), null,
|
|
||||||
RiskUserThreadLocal.get().getName(), oldTitle +" 修改为 -> "+title, "");
|
|
||||||
}
|
|
||||||
storySendZpMessage(ztStory.getId(), oldAssignedTo, ztStory.getAssignedTo());
|
storySendZpMessage(ztStory.getId(), oldAssignedTo, ztStory.getAssignedTo());
|
||||||
|
|
||||||
}
|
}
|
||||||
@@ -822,8 +808,6 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
for (ZtStoryDTO d : list) {
|
for (ZtStoryDTO d : list) {
|
||||||
d.setRevieweUser(d.getReviewedby().replaceAll(",", ""));
|
d.setRevieweUser(d.getReviewedby().replaceAll(",", ""));
|
||||||
d.setViews(rMap.get(d.getId()));
|
d.setViews(rMap.get(d.getId()));
|
||||||
StoryStageEnums stageEnums = StoryStageEnums.transfer(d.getStage());
|
|
||||||
d.setStageName(stageEnums==null?null:stageEnums.getDesc());
|
|
||||||
List<ZtTask> ztTasks = taskCountMap.get(d.getId());
|
List<ZtTask> ztTasks = taskCountMap.get(d.getId());
|
||||||
ZtStoryCaseDTO ztStoryCaseDTO = caseMap.get(d.getId());
|
ZtStoryCaseDTO ztStoryCaseDTO = caseMap.get(d.getId());
|
||||||
if (ztStoryCaseDTO != null) {
|
if (ztStoryCaseDTO != null) {
|
||||||
@@ -865,14 +849,6 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
if (ztUser != null) {
|
if (ztUser != null) {
|
||||||
d.setYsUserName(ztUser.getNickname());
|
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());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -980,105 +956,6 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
return searchDTOPageInfo;
|
return searchDTOPageInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public void testSubmitVerified(ZtStoryDTO qo) {
|
|
||||||
ZtStory ztStory = this.baseMapper.selectById(qo.getId());
|
|
||||||
if(ztStory==null){
|
|
||||||
throw new BusinessException("未查询到需求,需求ID: " + qo.getId());
|
|
||||||
}
|
|
||||||
if(!StoryStatusEnums.active.getValue().equals(ztStory.getStatus())){
|
|
||||||
throw new BusinessException("需求状态不是激活状态,无法提交验收,当前状态: " + ztStory.getStatus() + ",需求ID: " + qo.getId());
|
|
||||||
}
|
|
||||||
if(!StoryStageEnums.tested.getValue().equals(ztStory.getStage())){
|
|
||||||
throw new BusinessException("需求阶段不是测试完毕,无法提交验收,当前阶段: " + ztStory.getStage() + ",需求ID: " + qo.getId());
|
|
||||||
}
|
|
||||||
if(!ObjectUtil.equal(ztStory.getInnerYsFlag(),1)){
|
|
||||||
throw new BusinessException("无需内部验收,不需要提交,需求ID: " + qo.getId());
|
|
||||||
}
|
|
||||||
//产品验收中
|
|
||||||
ztStory.setStage(StoryStageEnums.productWaitVerified.getValue());
|
|
||||||
ztStory.setTestSubmitYsTime(new Date());
|
|
||||||
ztStory.setLasteditedby(RiskUserThreadLocal.get().getName());
|
|
||||||
ztStory.setLastediteddate(new Date());
|
|
||||||
this.baseMapper.updateById(ztStory);
|
|
||||||
|
|
||||||
List<ZtProjectstory> 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("未查询到需求,需求ID: " + qo.getId());
|
|
||||||
}
|
|
||||||
if(!StoryStatusEnums.active.getValue().equals(ztStory.getStatus())){
|
|
||||||
throw new BusinessException("需求状态不是激活状态,无法验收,当前状态: " + ztStory.getStatus() + ",需求ID: " + qo.getId());
|
|
||||||
}
|
|
||||||
if(!StoryStageEnums.productWaitVerified.getValue().equals(ztStory.getStage())){
|
|
||||||
throw new BusinessException("需求阶段不是待验收状态,无法验收,当前阶段: " + ztStory.getStage() + ",需求ID: " + qo.getId());
|
|
||||||
}
|
|
||||||
//产品验收中
|
|
||||||
ztStory.setStage(StoryStageEnums.productVerified.getValue());
|
|
||||||
ztStory.setLasteditedby(RiskUserThreadLocal.get().getName());
|
|
||||||
ztStory.setLastediteddate(new Date());
|
|
||||||
ztStory.setInnerYsTime(new Date());
|
|
||||||
this.baseMapper.updateById(ztStory);
|
|
||||||
|
|
||||||
List<ZtProjectstory> 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<String, Object> storyReleaseOnTimeByProducts(List<Integer> 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(), "", dto.getAssignedTo());
|
|
||||||
}
|
|
||||||
|
|
||||||
@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(), "", dto.getAssignedTo());
|
|
||||||
}
|
|
||||||
|
|
||||||
private List<SearchDTO> searchBug(SearchQo qo) {
|
private List<SearchDTO> searchBug(SearchQo qo) {
|
||||||
return this.baseMapper.searchBug(qo.getKeyword());
|
return this.baseMapper.searchBug(qo.getKeyword());
|
||||||
}
|
}
|
||||||
@@ -1276,14 +1153,9 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 开发完成
|
|
||||||
* @param id
|
|
||||||
* @param finishBy
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void develFinishStory(Integer id, String finishBy) {
|
public void finishStory(Integer id, String finishBy) {
|
||||||
|
|
||||||
if (id == null || id == 0) {
|
if (id == null || id == 0) {
|
||||||
return;
|
return;
|
||||||
@@ -1335,7 +1207,7 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
throw new BusinessException("未查询到数据-需求");
|
throw new BusinessException("未查询到数据-需求");
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!Arrays.asList("wait", "projected", "developing", "developed", "testing",StoryStageEnums.productWaitVerified.getValue(),StoryStageEnums.productVerified.getValue()).contains(ztStory.getStage())) {
|
if (!Arrays.asList("wait", "projected", "developing", "developed", "testing").contains(ztStory.getStage())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1370,13 +1242,6 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
actionService.addAction(ActionType.XQ, ActionStatus.CSWC, ztStory.getId(), ztStory.getProduct() + "", ztStory.getProject(), null,
|
actionService.addAction(ActionType.XQ, ActionStatus.CSWC, ztStory.getId(), ztStory.getProduct() + "", ztStory.getProject(), null,
|
||||||
RiskUserThreadLocal.get().getName(), 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) {
|
if (ztStory.getStartDate() == null) {
|
||||||
ztStory.setStartDate(new Date());
|
ztStory.setStartDate(new Date());
|
||||||
}
|
}
|
||||||
@@ -1388,7 +1253,7 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
|
|
||||||
for (Integer execId : execIds) {
|
for (Integer execId : execIds) {
|
||||||
if (execId != null) {
|
if (execId != null) {
|
||||||
this.kanbanlaneService.changeStatus(execId, ztStory.getId(), "story", newStatus);
|
this.kanbanlaneService.changeStatus(execId, ztStory.getId(), "story", "tested");
|
||||||
if (!oldStatus.equalsIgnoreCase(newStatus) && ztStory.getProduct() != 0) {
|
if (!oldStatus.equalsIgnoreCase(newStatus) && ztStory.getProduct() != 0) {
|
||||||
|
|
||||||
ProductStoryStatus oldPStatus = null;
|
ProductStoryStatus oldPStatus = null;
|
||||||
@@ -1405,6 +1270,7 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//发布
|
//发布
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -1414,25 +1280,22 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
}
|
}
|
||||||
|
|
||||||
ZtStory ztStory = this.baseMapper.selectById(story);
|
ZtStory ztStory = this.baseMapper.selectById(story);
|
||||||
if (ztStory == null) {
|
|
||||||
throw new BusinessException("未查询到数据-需求");
|
|
||||||
}
|
|
||||||
if (ztStory.getFeedback() != null && ztStory.getFeedback() != 0) {
|
if (ztStory.getFeedback() != null && ztStory.getFeedback() != 0) {
|
||||||
this.storyFeedbackService.feedbackFinished(ztStory.getFeedback(), null);
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ("closed".equals(ztStory.getStatus())) {
|
if ("closed".equals(ztStory.getStatus())) {
|
||||||
throw new BusinessException("当前已关闭");
|
throw new BusinessException("当前已关闭");
|
||||||
}
|
}
|
||||||
if (!StoryStageEnums.productVerified.getValue().equals(ztStory.getStage())) {
|
if (!"tested".equals(ztStory.getStage())) {
|
||||||
throw new BusinessException("当前无法更改发布状态");
|
throw new BusinessException("当前无法更改发布状态");
|
||||||
}
|
}
|
||||||
//这个需求被多少执行关联
|
//这个需求被多少执行关联
|
||||||
@@ -1495,10 +1358,16 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
@Override
|
@Override
|
||||||
public void taskFinishChangeStatus(Integer story, String finishBy, TaskType type, Boolean cancelFlag) {
|
public void taskFinishChangeStatus(Integer story, String finishBy, TaskType type, Boolean cancelFlag) {
|
||||||
ZtStory ztStory = this.getById(story);
|
ZtStory ztStory = this.getById(story);
|
||||||
if(ztStory == null|| story == 0) {
|
if(ztStory == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (ztStory.getStatus().equals("closed")) {
|
if (ztStory != null && ztStory.getStatus().equals("closed")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (type != TaskType.test && type != TaskType.devel) {
|
||||||
|
// return;
|
||||||
|
}
|
||||||
|
if (story == null || story == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (cancelFlag && type == TaskType.devel) {
|
if (cancelFlag && type == TaskType.devel) {
|
||||||
@@ -1528,7 +1397,7 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
if (!CollectionUtils.isEmpty(testedList)) {
|
if (!CollectionUtils.isEmpty(testedList)) {
|
||||||
testedStory(story, finishBy);
|
testedStory(story, finishBy);
|
||||||
} else {
|
} else {
|
||||||
this.develFinishStory(story, finishBy);
|
this.finishStory(story, finishBy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1569,7 +1438,7 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
.eq(ZtTask::getType, TaskType.devel.getCode())
|
.eq(ZtTask::getType, TaskType.devel.getCode())
|
||||||
.in(ZtTask::getStatus, Arrays.asList("done")));
|
.in(ZtTask::getStatus, Arrays.asList("done")));
|
||||||
if (!CollectionUtils.isEmpty(develedList)) {
|
if (!CollectionUtils.isEmpty(develedList)) {
|
||||||
this.develFinishStory(story, finishBy);
|
this.finishStory(story, finishBy);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
@@ -1617,7 +1486,7 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
if (done < 1) {
|
if (done < 1) {
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
this.develFinishStory(story, finishBy);
|
this.finishStory(story, finishBy);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1772,10 +1641,14 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
if (execId != null) {
|
if (execId != null) {
|
||||||
this.kanbanlaneService.changeStatus(execId, i, "story", newStatus);
|
this.kanbanlaneService.changeStatus(execId, i, "story", newStatus);
|
||||||
if (!oldStatus.equalsIgnoreCase(newStatus) && ztStory.getProduct() != 0) {
|
if (!oldStatus.equalsIgnoreCase(newStatus) && ztStory.getProduct() != 0) {
|
||||||
|
|
||||||
ProductStoryStatus oldPStatus = null;
|
ProductStoryStatus oldPStatus = null;
|
||||||
ProductStoryStatus newPStatus = null;
|
ProductStoryStatus newPStatus = null;
|
||||||
|
|
||||||
oldPStatus = getProductStatus(oldStatus);
|
oldPStatus = getProductStatus(oldStatus);
|
||||||
|
|
||||||
newPStatus = getProductStatus(newStatus);
|
newPStatus = getProductStatus(newStatus);
|
||||||
|
|
||||||
this.productService.productChangeStatus(ztStory.getProduct(), oldPStatus, newPStatus);
|
this.productService.productChangeStatus(ztStory.getProduct(), oldPStatus, newPStatus);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1979,8 +1852,7 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
//CHG-067:需求关联 Bug 走 toStory 列(story 列全库未用)——关闭需求时联动关闭其未关闭 Bug
|
List<ZtBug> bugList = this.bugService.list(new QueryWrapper<ZtBug>().lambda().eq(ZtBug::getStory, i).ne(ZtBug::getStatus, "closed"));
|
||||||
List<ZtBug> bugList = this.bugService.list(new QueryWrapper<ZtBug>().lambda().eq(ZtBug::getTostory, i).ne(ZtBug::getStatus, "closed"));
|
|
||||||
for (ZtBug bug : bugList) {
|
for (ZtBug bug : bugList) {
|
||||||
ZtBugQo qo = new ZtBugQo();
|
ZtBugQo qo = new ZtBugQo();
|
||||||
qo.setId(bug.getId());
|
qo.setId(bug.getId());
|
||||||
@@ -2278,14 +2150,6 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
if (ztUser != null) {
|
if (ztUser != null) {
|
||||||
d.setYsUserName(ztUser.getNickname());
|
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<ZtStoryspec>().lambda().eq(ZtStoryspec::getStory, d.getId()));
|
ZtStoryspec storyspec = this.storyspecService.getOne(new QueryWrapper<ZtStoryspec>().lambda().eq(ZtStoryspec::getStory, d.getId()));
|
||||||
|
|
||||||
if (storyspec != null) {
|
if (storyspec != null) {
|
||||||
@@ -2585,7 +2449,6 @@ public class ZtStoryServiceImpl extends ServiceImpl<ZtStoryMapper, ZtStory> impl
|
|||||||
ProductStoryStatus oldPStatus = null;
|
ProductStoryStatus oldPStatus = null;
|
||||||
if (status.equalsIgnoreCase("developing") || status.equalsIgnoreCase("testing")
|
if (status.equalsIgnoreCase("developing") || status.equalsIgnoreCase("testing")
|
||||||
|| status.equalsIgnoreCase("tested") || status.equalsIgnoreCase("developed")
|
|| status.equalsIgnoreCase("tested") || status.equalsIgnoreCase("developed")
|
||||||
|| status.equalsIgnoreCase(StoryStageEnums.productWaitVerified.getValue())|| status.equalsIgnoreCase(StoryStageEnums.productVerified.getValue())
|
|
||||||
) {
|
) {
|
||||||
|
|
||||||
oldPStatus = ProductStoryStatus.JH;
|
oldPStatus = ProductStoryStatus.JH;
|
||||||
|
|||||||
@@ -19,7 +19,10 @@ import com.sa.zentao.qo.StoryQo;
|
|||||||
import com.sa.zentao.qo.ZtProjectQo;
|
import com.sa.zentao.qo.ZtProjectQo;
|
||||||
import com.sa.zentao.service.*;
|
import com.sa.zentao.service.*;
|
||||||
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
|
||||||
import com.sa.zentao.utils.*;
|
import com.sa.zentao.utils.BeanCopyUtil;
|
||||||
|
import com.sa.zentao.utils.BeanCopyUtilCallBack;
|
||||||
|
import com.sa.zentao.utils.SendEmail;
|
||||||
|
import com.sa.zentao.utils.VxMessageUtils;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
@@ -78,9 +81,6 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
@Autowired
|
@Autowired
|
||||||
private VxService vxService;
|
private VxService vxService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtProjectstoryService projectstoryService;
|
|
||||||
|
|
||||||
@Value("${file.backUrl}")
|
@Value("${file.backUrl}")
|
||||||
private String url;
|
private String url;
|
||||||
|
|
||||||
@@ -92,7 +92,7 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
this.baseMapper.insert(s);
|
this.baseMapper.insert(s);
|
||||||
fileService.updateFile(dto.getFiles(), s.getId(), FileTypes.userStory);
|
fileService.updateFile(dto.getFiles(), s.getId(), FileTypes.userStory);
|
||||||
|
|
||||||
ZtStoryUserspec spec = buildSpec(dto, s);
|
ZtStoryUserspec spec= buildSpec(dto,s);
|
||||||
storyUserspecService.save(spec);
|
storyUserspecService.save(spec);
|
||||||
|
|
||||||
actionService.addAction(ActionType.USERXQ, ActionStatus.XJ, s.getId(), dto.getProduct() + "", null, null,
|
actionService.addAction(ActionType.USERXQ, ActionStatus.XJ, s.getId(), dto.getProduct() + "", null, null,
|
||||||
@@ -125,7 +125,6 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
spec.setSpec(dto.getSpec());
|
spec.setSpec(dto.getSpec());
|
||||||
spec.setVerify(dto.getVerify());
|
spec.setVerify(dto.getVerify());
|
||||||
spec.setFiles(dto.getFileUrl());
|
spec.setFiles(dto.getFileUrl());
|
||||||
spec.setStoryBackground(dto.getStoryBackground());
|
|
||||||
return spec;
|
return spec;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,8 +150,7 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
public void editStory(ZtStoryUserDTO dto) {
|
public void editStory(ZtStoryUserDTO dto) {
|
||||||
Integer id = dto.getId();
|
Integer id = dto.getId();
|
||||||
ZtStoryUser ztStory = this.baseMapper.selectById(id);
|
ZtStoryUser ztStory = this.baseMapper.selectById(id);
|
||||||
String title = dto.getTitle();
|
|
||||||
String oldTitle = ztStory.getTitle();
|
|
||||||
String storyStatus = ztStory.getStatus();
|
String storyStatus = ztStory.getStatus();
|
||||||
|
|
||||||
|
|
||||||
@@ -168,10 +166,7 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
|
|
||||||
actionService.addAction(ActionType.USERXQ, ActionStatus.BJ, dto.getId(), dto.getProduct() + "", dto.getProject(), null,
|
actionService.addAction(ActionType.USERXQ, ActionStatus.BJ, dto.getId(), dto.getProduct() + "", dto.getProject(), null,
|
||||||
RiskUserThreadLocal.get().getName(), dto.getRemark(), "");
|
RiskUserThreadLocal.get().getName(), dto.getRemark(), "");
|
||||||
if(!title.equals(oldTitle)) {
|
|
||||||
actionService.addAction(ActionType.USERXQ, ActionStatus.BJ, dto.getId(), dto.getProduct() + "", dto.getProject(), null,
|
|
||||||
RiskUserThreadLocal.get().getName(), oldTitle +"修改为 -> "+title, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
BeanUtils.copyProperties(dto, ztStory, "status");
|
BeanUtils.copyProperties(dto, ztStory, "status");
|
||||||
ztStory.setLasteditedby(RiskUserThreadLocal.get().getName());
|
ztStory.setLasteditedby(RiskUserThreadLocal.get().getName());
|
||||||
@@ -225,7 +220,7 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
ztStory.setVerify(null);
|
ztStory.setVerify(null);
|
||||||
this.baseMapper.updateById(ztStory);
|
this.baseMapper.updateById(ztStory);
|
||||||
|
|
||||||
ZtStoryUserspec spec = this.storyUserspecService.getOne(new QueryWrapper<ZtStoryUserspec>().lambda().eq(ZtStoryUserspec::getStory, id).last(" limit 1"));
|
ZtStoryUserspec spec = this.storyUserspecService.getOne(new QueryWrapper<ZtStoryUserspec>().lambda().eq(ZtStoryUserspec::getStory, id));
|
||||||
if (spec == null) {
|
if (spec == null) {
|
||||||
spec = new ZtStoryUserspec();
|
spec = new ZtStoryUserspec();
|
||||||
spec.setStory(id);
|
spec.setStory(id);
|
||||||
@@ -234,7 +229,6 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
spec.setSpec(dto.getSpec());
|
spec.setSpec(dto.getSpec());
|
||||||
spec.setVerify(dto.getVerify());
|
spec.setVerify(dto.getVerify());
|
||||||
spec.setFiles(dto.getFileUrl());
|
spec.setFiles(dto.getFileUrl());
|
||||||
spec.setStoryBackground(dto.getStoryBackground());
|
|
||||||
storyUserspecService.save(spec);
|
storyUserspecService.save(spec);
|
||||||
} else {
|
} else {
|
||||||
spec.setVersion(1);
|
spec.setVersion(1);
|
||||||
@@ -242,11 +236,9 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
spec.setSpec(dto.getSpec());
|
spec.setSpec(dto.getSpec());
|
||||||
spec.setVerify(dto.getVerify());
|
spec.setVerify(dto.getVerify());
|
||||||
spec.setFiles(dto.getFileUrl());
|
spec.setFiles(dto.getFileUrl());
|
||||||
spec.setStoryBackground(dto.getStoryBackground());
|
|
||||||
storyUserspecService.update(new UpdateWrapper<ZtStoryUserspec>().lambda().eq(ZtStoryUserspec::getStory, id)
|
storyUserspecService.update(new UpdateWrapper<ZtStoryUserspec>().lambda().eq(ZtStoryUserspec::getStory, id)
|
||||||
.set(ZtStoryUserspec::getSpec, dto.getSpec())
|
.set(ZtStoryUserspec::getSpec, dto.getSpec())
|
||||||
.set(ZtStoryUserspec::getVerify, dto.getVerify())
|
.set(ZtStoryUserspec::getVerify, dto.getVerify())
|
||||||
.set(ZtStoryUserspec::getStoryBackground, dto.getStoryBackground())
|
|
||||||
.set(ZtStoryUserspec::getFiles, dto.getFileUrl())
|
.set(ZtStoryUserspec::getFiles, dto.getFileUrl())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -254,42 +246,38 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
fileService.updateFile(dto.getFiles(), ztStory.getId(), FileTypes.userStory);
|
fileService.updateFile(dto.getFiles(), ztStory.getId(), FileTypes.userStory);
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, List<ZtStoryUserDTO>> getKanban(Integer productId) {
|
public Map<String, List<ZtStoryUserDTO>> getKanban(Integer productId) {
|
||||||
List<ZtStoryUser> ztStoryUsers = this.baseMapper.selectList(new QueryWrapper<ZtStoryUser>()
|
List<ZtStoryUser> ztStoryUsers = this.baseMapper.selectList(new QueryWrapper<ZtStoryUser>()
|
||||||
.lambda().select(ZtStoryUser::getId, ZtStoryUser::getPri, ZtStoryUser::getTitle, ZtStoryUser::getStage, ZtStoryUser::getStatus, ZtStoryUser::getOpenedby, ZtStoryUser::getOpeneddate, ZtStoryUser::getLastediteddate, ZtStoryUser::getProductUser)
|
.lambda().select(ZtStoryUser::getId,ZtStoryUser::getPri,ZtStoryUser::getTitle,ZtStoryUser::getStage,ZtStoryUser::getStatus,ZtStoryUser::getOpenedby,ZtStoryUser::getOpeneddate,ZtStoryUser::getLastediteddate)
|
||||||
.eq(ZtStoryUser::getProduct, productId).orderByDesc(ZtStoryUser::getId));
|
.eq(ZtStoryUser::getProduct, productId).orderByDesc(ZtStoryUser::getId));
|
||||||
Map<String, List<ZtStoryUserDTO>> map = new HashMap<>();
|
Map<String, List<ZtStoryUserDTO>> map = new HashMap<>();
|
||||||
Map<String, ZtUser> stringZtUserMap = this.userService.userMapByIds(null);
|
Map<String, ZtUser> stringZtUserMap = this.userService.userMapByIds(ztStoryUsers.stream().map(o -> o.getOpenedby()).collect(Collectors.toList()));
|
||||||
|
|
||||||
List<ZtStoryUserDTO> ztStoryUserDTOS = BeanCopyUtil.copyListProperties(ztStoryUsers, ZtStoryUserDTO::new, new BeanCopyUtilCallBack<ZtStoryUser, ZtStoryUserDTO>() {
|
List<ZtStoryUserDTO> ztStoryUserDTOS = BeanCopyUtil.copyListProperties(ztStoryUsers, ZtStoryUserDTO::new, new BeanCopyUtilCallBack<ZtStoryUser, ZtStoryUserDTO>() {
|
||||||
@Override
|
@Override
|
||||||
public void callBack(ZtStoryUser t, ZtStoryUserDTO s) {
|
public void callBack(ZtStoryUser t, ZtStoryUserDTO s) {
|
||||||
ZtUser ztUser = stringZtUserMap.get(t.getOpenedby());
|
ZtUser ztUser = stringZtUserMap.get(t.getOpenedby());
|
||||||
if (ztUser != null) {
|
if(ztUser!=null){
|
||||||
s.setOpenedbyName(ztUser.getNickname());
|
s.setOpenedbyName(ztUser.getNickname());
|
||||||
s.setColor(ztUser.getColor());
|
s.setColor(ztUser.getColor());
|
||||||
}
|
}
|
||||||
ztUser = stringZtUserMap.get(t.getProductUser());
|
|
||||||
if (ztUser != null) {
|
|
||||||
s.setProductUserName(ztUser.getNickname());
|
|
||||||
s.setProductUserColor(ztUser.getColor());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
map.put("active", ztStoryUserDTOS.stream().filter(o -> "active".equals(o.getStatus())).collect(Collectors.toList()));
|
map.put("active",ztStoryUserDTOS.stream().filter(o->"active".equals(o.getStatus())).collect(Collectors.toList()));
|
||||||
map.put("unconfirmed", ztStoryUserDTOS.stream().filter(o -> "unconfirmed".equals(o.getStatus())).collect(Collectors.toList()));
|
map.put("unconfirmed",ztStoryUserDTOS.stream().filter(o->"unconfirmed".equals(o.getStatus())).collect(Collectors.toList()));
|
||||||
map.put("waitcommunicate", ztStoryUserDTOS.stream().filter(o -> "waitcommunicate".equals(o.getStatus())).collect(Collectors.toList()));
|
map.put("waitcommunicate",ztStoryUserDTOS.stream().filter(o->"waitcommunicate".equals(o.getStatus())).collect(Collectors.toList()));
|
||||||
map.put("waitdesign", ztStoryUserDTOS.stream().filter(o -> "waitdesign".equals(o.getStatus())).collect(Collectors.toList()));
|
map.put("waitdesign",ztStoryUserDTOS.stream().filter(o->"waitdesign".equals(o.getStatus())).collect(Collectors.toList()));
|
||||||
map.put("designdoing", ztStoryUserDTOS.stream().filter(o -> "designdoing".equals(o.getStatus())).collect(Collectors.toList()));
|
map.put("designdoing",ztStoryUserDTOS.stream().filter(o->"designdoing".equals(o.getStatus())).collect(Collectors.toList()));
|
||||||
|
|
||||||
map.put("designdone", ztStoryUserDTOS.stream().filter(o -> "designdone".equals(o.getStatus())).collect(Collectors.toList()));
|
map.put("designdone",ztStoryUserDTOS.stream().filter(o->"designdone".equals(o.getStatus())).collect(Collectors.toList()));
|
||||||
map.put("storyunconfirmed", ztStoryUserDTOS.stream().filter(o -> "storyunconfirmed".equals(o.getStatus())).collect(Collectors.toList()));
|
map.put("storyunconfirmed",ztStoryUserDTOS.stream().filter(o->"storyunconfirmed".equals(o.getStatus())).collect(Collectors.toList()));
|
||||||
map.put("confirmed", ztStoryUserDTOS.stream().filter(o -> "confirmed".equals(o.getStatus())).collect(Collectors.toList()));
|
map.put("confirmed",ztStoryUserDTOS.stream().filter(o->"confirmed".equals(o.getStatus())).collect(Collectors.toList()));
|
||||||
return map;
|
return map;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -305,18 +293,18 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
}
|
}
|
||||||
|
|
||||||
List<Integer> pIds = this.projectService.authProductList();
|
List<Integer> pIds = this.projectService.authProductList();
|
||||||
if (CollectionUtils.isEmpty(pIds) && CollectionUtils.isEmpty(qo.getStoryIds())) {
|
if(CollectionUtils.isEmpty(pIds)&&CollectionUtils.isEmpty(qo.getStoryIds())){
|
||||||
return new PageInfo<ZtStoryUserDTO>();
|
return new PageInfo<ZtStoryUserDTO>();
|
||||||
}
|
}
|
||||||
if (qo.getProductId() != null && qo.getProductId() != 0) {
|
if(qo.getProductId()!=null&&qo.getProductId()!=0){
|
||||||
qo.setProductIds(Arrays.asList(qo.getProductId()));
|
qo.setProductIds(Arrays.asList(qo.getProductId()));
|
||||||
} else {
|
}else{
|
||||||
qo.setProductIds(pIds);
|
qo.setProductIds(pIds);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!CollectionUtils.isEmpty(qo.getStatusList()) && qo.getStatusList().contains("unconfirmed")) {
|
if(!CollectionUtils.isEmpty(qo.getStatusList())&&qo.getStatusList().contains("unconfirmed")) {
|
||||||
qo.getStatusList().addAll(Arrays.asList("unconfirmed",
|
qo.getStatusList().addAll(Arrays.asList("unconfirmed",
|
||||||
"waitcommunicate", "waitdesign", "designdoing", "designdone", "storyunconfirmed"));
|
"waitcommunicate","waitdesign","designdoing","designdone","storyunconfirmed"));
|
||||||
}
|
}
|
||||||
|
|
||||||
Page<ZtStoryUserDTO> page = PageHelper.startPage(qo.getCurrentPage(), qo.getPageSize());
|
Page<ZtStoryUserDTO> page = PageHelper.startPage(qo.getCurrentPage(), qo.getPageSize());
|
||||||
@@ -327,9 +315,7 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
|
|
||||||
Map<String, ZtUser> userMap = this.userService.userMapByIds(null);
|
Map<String, ZtUser> userMap = this.userService.userMapByIds(null);
|
||||||
|
|
||||||
Map<Integer, List<ZtStory>> storyMap = getStoryMap(list);
|
Map<Integer, List<ZtStory>> storyUserMap = getStoryUserMap(list);
|
||||||
//story - exec
|
|
||||||
Map<Integer,List<ZtProject>> execMap= getExecMap(storyMap);
|
|
||||||
Map<Integer, List<ZtStoryreviewDTO>> rMap = getReviewMap(list);
|
Map<Integer, List<ZtStoryreviewDTO>> rMap = getReviewMap(list);
|
||||||
for (ZtStoryUserDTO d : list) {
|
for (ZtStoryUserDTO d : list) {
|
||||||
|
|
||||||
@@ -373,101 +359,32 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
if (ztUser != null) {
|
if (ztUser != null) {
|
||||||
d.setYsUserName(ztUser.getNickname());
|
d.setYsUserName(ztUser.getNickname());
|
||||||
}
|
}
|
||||||
ztUser = userMap.get(d.getProductUser());
|
List<ZtStory> ztStories = storyUserMap.get(d.getId());
|
||||||
if (ztUser != null) {
|
if (!CollectionUtils.isEmpty(ztStories)) {
|
||||||
d.setProductUserName(ztUser.getNickname());
|
d.setStoryList(ztStories.stream().map(o ->o.getId()+":"+o.getTitle()).collect(Collectors.joining(",")));
|
||||||
|
d.setSList(CollectionUtils.isEmpty(ztStories)?null:BeanCopyUtil.copyListProperties(ztStories,ZtStoryDTO::new));
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ZtStory> ztStories = storyMap.get(d.getId());
|
|
||||||
if (!CollectionUtils.isEmpty(ztStories)) {
|
String reName = d.getTitle();
|
||||||
for (ZtStory story:ztStories){
|
String reUrl = url+"/#/product-user-story-info/"+d.getId();
|
||||||
List<ZtProject> execList = execMap.get(story.getId());
|
WriteCellData<String> hyperlink = new WriteCellData<>(reName);
|
||||||
if(!CollectionUtils.isEmpty(execList)){
|
HyperlinkData hyperlinkData = new HyperlinkData();
|
||||||
if(CollectionUtils.isEmpty(d.getExecList())){
|
try {
|
||||||
d.setExecList(new HashSet<>());
|
hyperlinkData.setAddress(reUrl.replace(" ", "%20"));
|
||||||
|
hyperlinkData.setHyperlinkType(HyperlinkData.HyperlinkType.URL);
|
||||||
|
hyperlink.setHyperlinkData(hyperlinkData);
|
||||||
|
d.setUrl(hyperlink );
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error(e.getMessage(), e);
|
||||||
|
|
||||||
|
d.setUrl(null );
|
||||||
}
|
}
|
||||||
d.getExecList().addAll(execList);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
d.setStoryList(ztStories.stream().map(o -> o.getId() + ":" + o.getTitle()).collect(Collectors.joining(",")));
|
|
||||||
d.setSList(CollectionUtils.isEmpty(ztStories) ? null : BeanCopyUtil.copyListProperties(ztStories, ZtStoryDTO::new));
|
|
||||||
}
|
|
||||||
d.setExecNames(buildExecNames(d.getExecList()));
|
|
||||||
d.setLinkUrl(url);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return new PageInfo<ZtStoryUserDTO>(list);
|
return new PageInfo<ZtStoryUserDTO>(list);
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<Integer, List<ZtProject>> getExecMap(Map<Integer, List<ZtStory>> storyMap) {
|
|
||||||
if(storyMap==null||storyMap.isEmpty()){
|
|
||||||
return new HashMap<>();
|
|
||||||
}
|
|
||||||
// 收集到指定类型的列表(比如ArrayList),还可扩展去重、排序等
|
|
||||||
List<ZtStory> storyList = storyMap.values()
|
|
||||||
.stream()
|
|
||||||
.filter(list -> list != null)
|
|
||||||
.flatMap(List::stream)
|
|
||||||
// 自定义收集器:指定ArrayList,还可加.distinct()去重
|
|
||||||
.collect(Collectors.toCollection(ArrayList::new));
|
|
||||||
if(CollectionUtils.isEmpty(storyList)){
|
|
||||||
return new HashMap<>();
|
|
||||||
}
|
|
||||||
List<ZtProjectstory> list = this.projectstoryService.list(new QueryWrapper<ZtProjectstory>().lambda()
|
|
||||||
.eq(ZtProjectstory::getType,ProjectTypeEnums.execution.getValue())
|
|
||||||
.in(ZtProjectstory::getStory, storyList.stream().map(o -> o.getId()).collect(Collectors.toUnmodifiableList())));
|
|
||||||
if(CollectionUtils.isEmpty(list)){
|
|
||||||
return new HashMap<>();
|
|
||||||
}
|
|
||||||
List<ZtProject> ztExecList = this.projectService.listByIds(list.stream().map(o -> o.getProject()).collect(Collectors.toUnmodifiableList()));
|
|
||||||
if(CollectionUtils.isEmpty(list)){
|
|
||||||
return new HashMap<>();
|
|
||||||
}
|
|
||||||
// 第一步:先构建 projectId -> ZtProject 的映射(方便快速查找)
|
|
||||||
Map<Integer, ZtProject> projectIdToProjectMap = ztExecList.stream()
|
|
||||||
.collect(Collectors.toMap(
|
|
||||||
ZtProject::getId, // key:项目ID
|
|
||||||
project -> project, // value:项目对象
|
|
||||||
(existing, replacement) -> existing // 处理重复projectId的情况,保留原有值
|
|
||||||
));
|
|
||||||
|
|
||||||
// 第二步:按story分组,收集对应的ZtProject列表
|
|
||||||
Map<Integer, List<ZtProject>> storyToProjectsMap = list.stream()
|
|
||||||
.collect(Collectors.groupingBy(
|
|
||||||
ZtProjectstory::getStory, // 分组key:story字段
|
|
||||||
// 分组value:将每个ZtProjectstory对应的project转换成ZtProject,并收集为列表
|
|
||||||
Collectors.mapping(
|
|
||||||
projectStory -> projectIdToProjectMap.get(projectStory.getProject()),
|
|
||||||
// 过滤掉null(防止projectId不存在的情况),并收集为List
|
|
||||||
Collectors.filtering(
|
|
||||||
project -> project != null,
|
|
||||||
Collectors.toList()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
));
|
|
||||||
|
|
||||||
// 最终返回这个Map(你可以根据需要调整返回逻辑)
|
|
||||||
return storyToProjectsMap;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CHG-078:迭代版本列取值——execList 中迭代名称去重、排序后逗号拼接;空/无有效名称返回 null。
|
|
||||||
*/
|
|
||||||
static String buildExecNames(Set<ZtProject> execList) {
|
|
||||||
if (CollectionUtils.isEmpty(execList)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String names = execList.stream()
|
|
||||||
.filter(Objects::nonNull)
|
|
||||||
.map(ZtProject::getName)
|
|
||||||
.filter(StringUtils::isNotEmpty)
|
|
||||||
.distinct()
|
|
||||||
.sorted()
|
|
||||||
.collect(Collectors.joining(","));
|
|
||||||
return StringUtils.isEmpty(names) ? null : names;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void changeStatus(ZtStoryUserDTO dto) {
|
public void changeStatus(ZtStoryUserDTO dto) {
|
||||||
@@ -494,8 +411,8 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
.eq(ZtStoryUser::getDeleted, "0")
|
.eq(ZtStoryUser::getDeleted, "0")
|
||||||
|
|
||||||
.orderByDesc(ZtStoryUser::getId);
|
.orderByDesc(ZtStoryUser::getId);
|
||||||
eq.select(ZtStoryUser::getId, ZtStoryUser::getTitle, ZtStoryUser::getProduct,
|
eq.select(ZtStoryUser::getId,ZtStoryUser::getTitle,ZtStoryUser::getProduct,
|
||||||
ZtStoryUser::getOpenedby, ZtStoryUser::getAssignedto
|
ZtStoryUser::getOpenedby,ZtStoryUser::getAssignedto
|
||||||
);
|
);
|
||||||
List<ZtStoryUser> ztStories = this.baseMapper.selectList(eq);
|
List<ZtStoryUser> ztStories = this.baseMapper.selectList(eq);
|
||||||
if (CollectionUtils.isEmpty(ztStories)) {
|
if (CollectionUtils.isEmpty(ztStories)) {
|
||||||
@@ -535,7 +452,7 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
actionStatus = ActionStatus.PSBTG;
|
actionStatus = ActionStatus.PSBTG;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dto.getRevieweResult() != 1) {
|
if (dto.getRevieweResult() != 1){
|
||||||
ZtUser u = this.userService.getByAccount(ztStory.getOpenedby());
|
ZtUser u = this.userService.getByAccount(ztStory.getOpenedby());
|
||||||
if (u != null && !StringUtils.isEmpty(u.getEmail())) {
|
if (u != null && !StringUtils.isEmpty(u.getEmail())) {
|
||||||
StringBuilder b = new StringBuilder();
|
StringBuilder b = new StringBuilder();
|
||||||
@@ -560,6 +477,7 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
lambda.set(ZtStoryreview::getReviewdate, new Date());
|
lambda.set(ZtStoryreview::getReviewdate, new Date());
|
||||||
lambda.eq(ZtStoryreview::getStory, ztStory.getId());
|
lambda.eq(ZtStoryreview::getStory, ztStory.getId());
|
||||||
lambda.eq(ZtStoryreview::getReviewer, RiskUserThreadLocal.get().getName());
|
lambda.eq(ZtStoryreview::getReviewer, RiskUserThreadLocal.get().getName());
|
||||||
@@ -583,9 +501,6 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
ztStory.setRevieweddate(
|
ztStory.setRevieweddate(
|
||||||
new Date()
|
new Date()
|
||||||
);
|
);
|
||||||
ztStory.setActivateddate(
|
|
||||||
new Date()
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (dto.getRevieweResult() == 0) {
|
if (dto.getRevieweResult() == 0) {
|
||||||
@@ -603,6 +518,10 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
//添加action
|
//添加action
|
||||||
actionService.addAction(ActionType.USERXQ, actionStatus, ztStory.getId(), ztStory.getProduct() + "", ztStory.getProject(), null,
|
actionService.addAction(ActionType.USERXQ, actionStatus, ztStory.getId(), ztStory.getProduct() + "", ztStory.getProject(), null,
|
||||||
RiskUserThreadLocal.get().getName(), dto.getDesc(), ztStory.getAssignedto());
|
RiskUserThreadLocal.get().getName(), dto.getDesc(), ztStory.getAssignedto());
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -630,11 +549,7 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
if (ztUser != null) {
|
if (ztUser != null) {
|
||||||
dto.setYsUserName(ztUser.getNickname());
|
dto.setYsUserName(ztUser.getNickname());
|
||||||
}
|
}
|
||||||
ztUser = userMap.get(dto.getProductUser());
|
Map<Integer, List<ZtStory>> storyUserMap = getStoryUserMap(Arrays.asList(dto));
|
||||||
if (ztUser != null) {
|
|
||||||
dto.setProductUserName(ztUser.getNickname());
|
|
||||||
}
|
|
||||||
Map<Integer, List<ZtStory>> storyUserMap = getStoryMap(Arrays.asList(dto));
|
|
||||||
List<ZtStory> ztStories = storyUserMap.get(d.getId());
|
List<ZtStory> ztStories = storyUserMap.get(d.getId());
|
||||||
if (!CollectionUtils.isEmpty(ztStories)) {
|
if (!CollectionUtils.isEmpty(ztStories)) {
|
||||||
dto.setStoryList(ztStories.stream().map(o -> o.getTitle()).collect(Collectors.joining(",")));
|
dto.setStoryList(ztStories.stream().map(o -> o.getTitle()).collect(Collectors.joining(",")));
|
||||||
@@ -687,9 +602,8 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
List<ZtStory> list = this.storyService.list(new QueryWrapper<ZtStory>().lambda().eq(ZtStory::getUserStory, dto.getId()));
|
List<ZtStory> list = this.storyService.list(new QueryWrapper<ZtStory>().lambda().eq(ZtStory::getUserStory, dto.getId()));
|
||||||
if (!CollectionUtils.isEmpty(list)) {
|
if (!CollectionUtils.isEmpty(list)) {
|
||||||
List<ZtStoryDTO> storyDTOList = BeanCopyUtil.copyListProperties(list, ZtStoryDTO::new);
|
List<ZtStoryDTO> storyDTOList = BeanCopyUtil.copyListProperties(list, ZtStoryDTO::new);
|
||||||
Map<Integer,List<ZtProject>> execMap= getExecMap(list.stream().collect(Collectors.groupingBy(ZtStory::getId)));
|
|
||||||
|
|
||||||
if (!CollectionUtils.isEmpty(storyDTOList)) {
|
if (!CollectionUtils.isEmpty(storyDTOList)) {
|
||||||
|
|
||||||
for (ZtStoryDTO storyDTO : storyDTOList) {
|
for (ZtStoryDTO storyDTO : storyDTOList) {
|
||||||
ztUser = userMap.get(storyDTO.getAssignedTo());
|
ztUser = userMap.get(storyDTO.getAssignedTo());
|
||||||
if (ztUser != null) {
|
if (ztUser != null) {
|
||||||
@@ -699,20 +613,10 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
if (ztUser != null) {
|
if (ztUser != null) {
|
||||||
storyDTO.setOpenedbyName(ztUser.getNickname());
|
storyDTO.setOpenedbyName(ztUser.getNickname());
|
||||||
}
|
}
|
||||||
List<ZtProject> execList = execMap.get(storyDTO.getId());
|
|
||||||
if(!CollectionUtils.isEmpty(execList)){
|
|
||||||
if(CollectionUtils.isEmpty(dto.getExecList())){
|
|
||||||
dto.setExecList(new HashSet<>());
|
|
||||||
}
|
|
||||||
dto.getExecList().addAll(execList);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
dto.setSList(storyDTOList);
|
dto.setSList(storyDTOList);
|
||||||
}
|
}
|
||||||
//story - exec
|
|
||||||
dto.setExecNames(buildExecNames(dto.getExecList()));
|
|
||||||
|
|
||||||
}
|
}
|
||||||
List<ZtStoryUserTask> tasks = this.storyUserTaskService.list(new QueryWrapper<ZtStoryUserTask>().lambda().eq(ZtStoryUserTask::getUserStoryId, dto.getId()));
|
List<ZtStoryUserTask> tasks = this.storyUserTaskService.list(new QueryWrapper<ZtStoryUserTask>().lambda().eq(ZtStoryUserTask::getUserStoryId, dto.getId()));
|
||||||
if (!CollectionUtils.isEmpty(tasks)) {
|
if (!CollectionUtils.isEmpty(tasks)) {
|
||||||
@@ -734,11 +638,10 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
dto.setViews(rMap.get(d.getId()));
|
dto.setViews(rMap.get(d.getId()));
|
||||||
|
|
||||||
List<ZtStoryUserspec> specList = this.storyUserspecService.list(new QueryWrapper<ZtStoryUserspec>().lambda().eq(ZtStoryUserspec::getStory, dto.getId()));
|
List<ZtStoryUserspec> specList = this.storyUserspecService.list(new QueryWrapper<ZtStoryUserspec>().lambda().eq(ZtStoryUserspec::getStory, dto.getId()));
|
||||||
if (!CollectionUtils.isEmpty(specList)) {
|
if(!CollectionUtils.isEmpty(specList)){
|
||||||
ZtStoryUserspec ztStoryUserspec = specList.get(0);
|
ZtStoryUserspec ztStoryUserspec = specList.get(0);
|
||||||
dto.setSpec(ztStoryUserspec.getSpec());
|
dto.setSpec(ztStoryUserspec.getSpec());
|
||||||
dto.setVerify(ztStoryUserspec.getVerify());
|
dto.setVerify(ztStoryUserspec.getVerify());
|
||||||
dto.setStoryBackground(ztStoryUserspec.getStoryBackground());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return dto;
|
return dto;
|
||||||
@@ -791,6 +694,7 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void changeStatus(Integer id, UserStoryEnums status) {
|
public void changeStatus(Integer id, UserStoryEnums status) {
|
||||||
ZtStoryUser storyUser = this.baseMapper.selectById(id);
|
ZtStoryUser storyUser = this.baseMapper.selectById(id);
|
||||||
@@ -813,24 +717,24 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
ZtProduct ztProduct = this.productService.getById(storyUser.getProduct());
|
ZtProduct ztProduct = this.productService.getById(storyUser.getProduct());
|
||||||
|
|
||||||
//需求已确认
|
//需求已确认
|
||||||
this.sendDesigndoneMessage(storyUser.getId(), ztProduct.getPo());
|
this.sendDesigndoneMessage(storyUser.getId(),ztProduct.getPo());
|
||||||
}
|
}
|
||||||
if (UserStoryEnums.DQR == status) {
|
if (UserStoryEnums.DQR == status) {
|
||||||
//提交交付物发送给创建人
|
//提交交付物发送给创建人
|
||||||
this.sendSubmitMessage(storyUser.getId(), storyUser.getOpenedby());
|
this.sendSubmitMessage(storyUser.getId(),storyUser.getOpenedby());
|
||||||
}
|
}
|
||||||
if (UserStoryEnums.CFM == status && oldStatus.equals("storyunconfirmed")) {
|
if (UserStoryEnums.CFM == status&&oldStatus.equals("storyunconfirmed")) {
|
||||||
ZtProduct ztProduct = this.productService.getById(storyUser.getProduct());
|
ZtProduct ztProduct = this.productService.getById(storyUser.getProduct());
|
||||||
//交付物确认
|
//交付物确认
|
||||||
this.sendConfirmMessage(storyUser.getId(), ztProduct.getPo());
|
this.sendConfirmMessage(storyUser.getId(),ztProduct.getPo());
|
||||||
}
|
}
|
||||||
if (UserStoryEnums.RELEASED == status) {
|
if(UserStoryEnums.RELEASED == status){
|
||||||
//如果研发需求已经全部发布,那么就发布
|
//如果研发需求已经全部发布,那么就发布
|
||||||
// wait 初始化 projected 已立项 developing 研发中 developed 研发完毕 testing 测试中 tested
|
// wait 初始化 projected 已立项 developing 研发中 developed 研发完毕 testing 测试中 tested
|
||||||
List<ZtStory> storyList = this.storyService.list(new QueryWrapper<ZtStory>().lambda()
|
List<ZtStory> storyList = this.storyService.list(new QueryWrapper<ZtStory>().lambda()
|
||||||
.ne(ZtStory::getStatus, "closed").in(ZtStory::getStage, StoryStageEnums.beforeStatus(7)).eq(ZtStory::getUserStory, id));
|
.ne(ZtStory::getStatus, "closed").in(ZtStory::getStage, StoryStageEnums.beforeStatus(7)).eq(ZtStory::getUserStory, id));
|
||||||
if (!CollectionUtils.isEmpty(storyList)) {
|
if(!CollectionUtils.isEmpty(storyList)){
|
||||||
return;
|
return ;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
storyUser.setStatus(status.getCode());
|
storyUser.setStatus(status.getCode());
|
||||||
@@ -906,7 +810,7 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
|
|
||||||
this.baseMapper.updateById(storyUser);
|
this.baseMapper.updateById(storyUser);
|
||||||
|
|
||||||
if (status == UserStoryEnums.CPYSJ) {
|
if(status==UserStoryEnums.CPYSJ){
|
||||||
//发送消息
|
//发送消息
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -963,19 +867,7 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
private Map<Integer, List<ZtStory>> getStoryUserMap(List<ZtStoryUserDTO> list) {
|
||||||
public void assignedToProductUser(ZtStoryUserDTO dto) {
|
|
||||||
ZtStoryUser ztStoryUser = this.baseMapper.selectById(dto.getId());
|
|
||||||
if (ztStoryUser == null) {
|
|
||||||
throw new BusinessException("未查询到数据");
|
|
||||||
}
|
|
||||||
ztStoryUser.setProductUser(dto.getAssignedto());
|
|
||||||
this.baseMapper.updateById(ztStoryUser);
|
|
||||||
actionService.addAction(ActionType.USERXQ, ActionStatus.ASSIGNTOPRODUCTUSER, ztStoryUser.getId(), ztStoryUser.getProduct() + "", null, null,
|
|
||||||
RiskUserThreadLocal.get().getName(), "", "");
|
|
||||||
}
|
|
||||||
|
|
||||||
private Map<Integer, List<ZtStory>> getStoryMap(List<ZtStoryUserDTO> list) {
|
|
||||||
List<Integer> ids = list.stream().map(o -> o.getId()).collect(Collectors.toList());
|
List<Integer> ids = list.stream().map(o -> o.getId()).collect(Collectors.toList());
|
||||||
|
|
||||||
List<ZtStory> list1 = storyService.list(new QueryWrapper<ZtStory>().lambda().in(ZtStory::getUserStory, ids));
|
List<ZtStory> list1 = storyService.list(new QueryWrapper<ZtStory>().lambda().in(ZtStory::getUserStory, ids));
|
||||||
@@ -1013,19 +905,16 @@ public class ZtStoryUserServiceImpl extends ServiceImpl<ZtStoryUserMapper, ZtSto
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//发送消息给微信 产品已经设计
|
//发送消息给微信 产品已经设计
|
||||||
private void sendDesigndoneMessage(Integer id, String account) {
|
private void sendDesigndoneMessage(Integer id,String account) {
|
||||||
this.vxService.sendMessageToVx(account, VxMessageUtils.storyUserMessage.designdoneMessage(id), new Date());
|
this.vxService.sendMessageToVx(account, VxMessageUtils.storyUserMessage.designdoneMessage(id),new Date());
|
||||||
}
|
}
|
||||||
|
|
||||||
//发送消息给微信 提交交付物
|
//发送消息给微信 提交交付物
|
||||||
private void sendSubmitMessage(Integer id, String account) {
|
private void sendSubmitMessage(Integer id,String account) {
|
||||||
this.vxService.sendMessageToVx(account, VxMessageUtils.storyUserMessage.submitMessage(id), new Date());
|
this.vxService.sendMessageToVx(account, VxMessageUtils.storyUserMessage.submitMessage(id),new Date());
|
||||||
}
|
}
|
||||||
|
|
||||||
//确认交付物
|
//确认交付物
|
||||||
private void sendConfirmMessage(Integer id, String account) {
|
private void sendConfirmMessage(Integer id,String account) {
|
||||||
this.vxService.sendMessageToVx(account, VxMessageUtils.storyUserMessage.sendConfirmMessage(id), new Date());
|
this.vxService.sendMessageToVx(account, VxMessageUtils.storyUserMessage.sendConfirmMessage(id),new Date());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.sa.zentao.service.impl;
|
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.LambdaQueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
|
||||||
@@ -27,8 +26,6 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.text.ParseException;
|
|
||||||
import java.text.SimpleDateFormat;
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@@ -234,7 +231,6 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
ZtStory ztStory = this.storyService.getById(story);
|
ZtStory ztStory = this.storyService.getById(story);
|
||||||
if (ztStory != null) {
|
if (ztStory != null) {
|
||||||
dto.setStoryName(ztStory.getTitle());
|
dto.setStoryName(ztStory.getTitle());
|
||||||
dto.setInnerYsFlag(ztStory.getInnerYsFlag());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -330,14 +326,6 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
return this.baseMapper.itApprovalByUserName(s, firstDayOfMonth, lastDayOfMonth);
|
return this.baseMapper.itApprovalByUserName(s, firstDayOfMonth, lastDayOfMonth);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public List<ItApproval> itApprovalsByNames(List<String> names, Date firstDayOfMonth, Date lastDayOfMonth) {
|
|
||||||
if (CollectionUtils.isEmpty(names)) {
|
|
||||||
return new ArrayList<>();
|
|
||||||
}
|
|
||||||
return this.baseMapper.itApprovalsByNames(names, firstDayOfMonth, lastDayOfMonth);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public List<ZtTask> taskListByEIdsAndDate(Date firstDayOfMonth, Date lastDayOfMonth, List<Integer> pids) {
|
public List<ZtTask> taskListByEIdsAndDate(Date firstDayOfMonth, Date lastDayOfMonth, List<Integer> pids) {
|
||||||
if (CollectionUtils.isEmpty(pids)) {
|
if (CollectionUtils.isEmpty(pids)) {
|
||||||
@@ -530,7 +518,7 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
List<String> userIds = list.stream().map(o -> o.getFinishedby()).collect(Collectors.toList());
|
List<String> userIds = list.stream().map(o -> o.getFinishedby()).collect(Collectors.toList());
|
||||||
userIds.addAll(list.stream().map(o -> o.getAssignedTo()).collect(Collectors.toList()));
|
userIds.addAll(list.stream().map(o -> o.getAssignedTo()).collect(Collectors.toList()));
|
||||||
Map<String, ZtUser> userMap = this.userService.userMapByIds(null);
|
Map<String, ZtUser> userMap = this.userService.userMapByIds(null);
|
||||||
Date d = new Date();
|
Date d=new Date();
|
||||||
for (ZtTaskDTO task : list) {
|
for (ZtTaskDTO task : list) {
|
||||||
ZtUser ztUser = userMap.get(task.getFinishedby());
|
ZtUser ztUser = userMap.get(task.getFinishedby());
|
||||||
if (ztUser != null) {
|
if (ztUser != null) {
|
||||||
@@ -543,16 +531,16 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
if (task.getDeadline() != null) {
|
if (task.getDeadline() != null) {
|
||||||
task.setDeadline(DateUtils.getDayLast(task.getDeadline()));
|
task.setDeadline(DateUtils.getDayLast(task.getDeadline()));
|
||||||
}
|
}
|
||||||
if (task.getFinishedDate() != null) {
|
if(task.getFinishedDate()!=null){
|
||||||
if (task.getFinishedDate().getTime() > DateUtils.getDayLast(task.getDeadline()).getTime()) {
|
if(task.getFinishedDate().getTime()>DateUtils.getDayLast(task.getDeadline()).getTime()){
|
||||||
task.setDelayFlag(1);
|
task.setDelayFlag(1);
|
||||||
} else {
|
}else{
|
||||||
task.setDelayFlag(2);
|
task.setDelayFlag(2);
|
||||||
}
|
}
|
||||||
} else {
|
}else{
|
||||||
if (d.getTime() > DateUtils.getDayLast(task.getDeadline()).getTime()) {
|
if(d.getTime()>DateUtils.getDayLast(task.getDeadline()).getTime()){
|
||||||
task.setDelayFlag(1);
|
task.setDelayFlag(1);
|
||||||
} else {
|
}else{
|
||||||
task.setDelayFlag(2);
|
task.setDelayFlag(2);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -589,8 +577,7 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
if (story != null && story != 0) {
|
if (story != null && story != 0) {
|
||||||
ZtStory ztStory = this.storyService.getById(story);
|
ZtStory ztStory = this.storyService.getById(story);
|
||||||
// wait 初始化 projected 已立项 developing 研发中 developed 研发完毕 testing 测试中 tested
|
// wait 初始化 projected 已立项 developing 研发中 developed 研发完毕 testing 测试中 tested
|
||||||
if (!Arrays.asList("wait", "projected", "developing", "developed", "testing", "tested", StoryStageEnums.productWaitVerified.getValue()
|
if (!Arrays.asList("wait", "projected", "developing", "developed", "testing", "tested").contains(ztStory.getStage())) {
|
||||||
, StoryStageEnums.productVerified.getValue()).contains(ztStory.getStage())) {
|
|
||||||
throw new BusinessException("当前状态无法添加任务");
|
throw new BusinessException("当前状态无法添加任务");
|
||||||
}
|
}
|
||||||
if (ztStory.getStatus().equals("closed")) {
|
if (ztStory.getStatus().equals("closed")) {
|
||||||
@@ -657,19 +644,16 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
ztTask.setEstimate(ztTask.getUseTime().floatValue());
|
ztTask.setEstimate(ztTask.getUseTime().floatValue());
|
||||||
ztTask.setLeft(ztTask.getUseTime().floatValue());
|
ztTask.setLeft(ztTask.getUseTime().floatValue());
|
||||||
}
|
}
|
||||||
if (ztTask.getDeadline() == null) {
|
if(ztTask.getDeadline()==null){
|
||||||
throw new BusinessException("当前环境异常请联系管理员");
|
throw new BusinessException("当前环境异常请联系管理员");
|
||||||
}
|
}
|
||||||
if("test".equals(ztTask.getType())&&"reviewing".equals(ztTask.getStatus())){
|
|
||||||
ztTask.setStatus("wait");
|
|
||||||
}
|
|
||||||
this.baseMapper.insert(ztTask);
|
this.baseMapper.insert(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());
|
ZtStory ztStory = this.storyService.getById(ztTask.getStory());
|
||||||
if (ztTask.getType().equals("devel") && (ztStory.getDevelPlanEndTime() == null || ztTask.getDeadline().after(ztStory.getDevelPlanEndTime()))) {
|
if(ztTask.getType().equals("devel")&&(ztStory.getDevelPlanEndTime()==null||ztTask.getDeadline().after(ztStory.getDevelPlanEndTime()))){
|
||||||
ztStory.setDevelPlanEndTime(ztTask.getDeadline());
|
ztStory.setDevelPlanEndTime(ztTask.getDeadline());
|
||||||
}
|
}
|
||||||
if (ztTask.getType().equals("test") && (ztStory.getTestPlanEndTime() == null || ztTask.getDeadline().after(ztStory.getTestPlanEndTime()))) {
|
if(ztTask.getType().equals("test")&&(ztStory.getTestPlanEndTime()==null||ztTask.getDeadline().after(ztStory.getTestPlanEndTime()))){
|
||||||
ztStory.setTestPlanEndTime(ztTask.getDeadline());
|
ztStory.setTestPlanEndTime(ztTask.getDeadline());
|
||||||
}
|
}
|
||||||
this.storyService.updateById(ztStory);
|
this.storyService.updateById(ztStory);
|
||||||
@@ -719,7 +703,7 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void modifyTask(ZtTaskDTO dto) {
|
public void modifyTask(ZtTaskDTO dto) {
|
||||||
if (dto.getDeadline() == null || dto.getEstStarted() == null) {
|
if(dto.getDeadline()==null||dto.getEstStarted()==null){
|
||||||
throw new BusinessException("请录入开始结束日期");
|
throw new BusinessException("请录入开始结束日期");
|
||||||
}
|
}
|
||||||
ZtTask ztTask = this.baseMapper.selectById(dto.getId());
|
ZtTask ztTask = this.baseMapper.selectById(dto.getId());
|
||||||
@@ -866,7 +850,7 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
this.storyService.taskFinishChangeStatus(ztTask.getStory(), null, TaskType.transferType(ztTask.getType()), false);
|
this.storyService.taskFinishChangeStatus(ztTask.getStory(), null, TaskType.transferType(ztTask.getType()), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ObjectUtils.notEqual(oldExecution, newExecution)) {
|
if(ObjectUtils.notEqual(oldExecution,newExecution)){
|
||||||
//迭代切换了
|
//迭代切换了
|
||||||
this.kanbanlaneService.removeKanbanCell(oldExecution, ztTask.getId(), KanbanCellType.TASK);
|
this.kanbanlaneService.removeKanbanCell(oldExecution, ztTask.getId(), KanbanCellType.TASK);
|
||||||
//添加新的看板迭代
|
//添加新的看板迭代
|
||||||
@@ -943,26 +927,26 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
|
|
||||||
actionService.addAction(ActionType.RW, ActionStatus.KS, dto.getId(), projectproduct == null ? null : projectproduct.getProduct().toString(), projectproduct == null ? null : projectproduct.getProject(), ztTask.getExecution(),
|
actionService.addAction(ActionType.RW, ActionStatus.KS, dto.getId(), projectproduct == null ? null : projectproduct.getProduct().toString(), projectproduct == null ? null : projectproduct.getProject(), ztTask.getExecution(),
|
||||||
RiskUserThreadLocal.get().getName(), "", null);
|
RiskUserThreadLocal.get().getName(), "", null);
|
||||||
if ("done".equals(ztTask.getStatus())) {
|
if("done".equals(ztTask.getStatus())){
|
||||||
String finishBy = StringUtils.isEmpty(ztTask.getFinishedby()) ? RiskUserThreadLocal.get().getName() : ztTask.getFinishedby();
|
String finishBy = StringUtils.isEmpty(ztTask.getFinishedby())?RiskUserThreadLocal.get().getName():ztTask.getFinishedby();
|
||||||
//如果是测试任务 提交交付物
|
//如果是测试任务 提交交付物
|
||||||
if ("test".equals(ztTask.getType()) && !StringUtils.isEmpty(ztTask.getDeliverContent())) {
|
if("test".equals(ztTask.getType())&&!StringUtils.isEmpty(ztTask.getDeliverContent())){
|
||||||
actionService.addAction(ActionType.RW, ActionStatus.TJJFW, ztTask.getId(), projectproduct == null ? null : projectproduct.getProduct().toString(), projectproduct == null ? null : projectproduct.getProject(), ztTask.getExecution(),
|
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);
|
StringUtils.isEmpty(finishBy) ? RiskUserThreadLocal.get().getName() : finishBy, dto.getDeliverContent(), null);
|
||||||
if (ztTask.getStory() != null && ztTask.getStory() != 0) {
|
if(ztTask.getStory()!=null&&ztTask.getStory()!=0){
|
||||||
actionService.addAction(ActionType.XQ, ActionStatus.TJJFW, ztTask.getStory(), projectproduct == null ? null : projectproduct.getProduct().toString(), projectproduct == null ? null : projectproduct.getProject(), ztTask.getExecution(),
|
actionService.addAction(ActionType.XQ, 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);
|
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());
|
ZtStory ztStory = this.storyService.getById(ztTask.getStory());
|
||||||
if (ztStory != null) {
|
if(ztStory!=null){
|
||||||
ztStory.setDeliverContent(ztTask.getDeliverContent());
|
ztStory.setDeliverContent(ztTask.getDeliverContent());
|
||||||
this.storyService.updateById(ztStory);
|
this.storyService.updateById(ztStory);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!StringUtils.isEmpty(dto.getRemark())) {
|
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(),
|
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);
|
StringUtils.isEmpty(finishBy) ? RiskUserThreadLocal.get().getName() : finishBy, dto.getRemark(), null);
|
||||||
}
|
}
|
||||||
@@ -1033,7 +1017,6 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
ztTask.setDeliverContent(dto.getDeliverContent());
|
ztTask.setDeliverContent(dto.getDeliverContent());
|
||||||
ztTask.setLasteditedby(RiskUserThreadLocal.get().getName());
|
ztTask.setLasteditedby(RiskUserThreadLocal.get().getName());
|
||||||
ztTask.setLastediteddate(new Date());
|
ztTask.setLastediteddate(new Date());
|
||||||
ztTask.setFeedbackRemark(dto.getFeedbackRemark());
|
|
||||||
this.baseMapper.updateById(ztTask);
|
this.baseMapper.updateById(ztTask);
|
||||||
|
|
||||||
ZtEffortDTO e = new ZtEffortDTO();
|
ZtEffortDTO e = new ZtEffortDTO();
|
||||||
@@ -1047,22 +1030,21 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
//添加action
|
//添加action
|
||||||
ZtProjectproduct projectproduct = projectproductService.getOne(new QueryWrapper<ZtProjectproduct>().lambda().eq(ZtProjectproduct::getProject, ztTask.getProject()));
|
ZtProjectproduct projectproduct = projectproductService.getOne(new QueryWrapper<ZtProjectproduct>().lambda().eq(ZtProjectproduct::getProject, ztTask.getProject()));
|
||||||
//如果是测试任务 提交交付物
|
//如果是测试任务 提交交付物
|
||||||
if ("test".equals(ztTask.getType()) && !StringUtils.isEmpty(ztTask.getDeliverContent())) {
|
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(),
|
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);
|
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());
|
ZtStory ztStory = this.storyService.getById(ztTask.getStory());
|
||||||
if (ztStory != null) {
|
if(ztStory!=null){
|
||||||
ztStory.setDeliverContent(dto.getDeliverContent());
|
ztStory.setDeliverContent(ztTask.getDeliverContent());
|
||||||
ztStory.setLasteditedby(RiskUserThreadLocal.get().getName());
|
|
||||||
ztStory.setLastediteddate(new Date());
|
|
||||||
this.storyService.updateById(ztStory);
|
this.storyService.updateById(ztStory);
|
||||||
}
|
}
|
||||||
actionService.addAction(ActionType.XQ, ActionStatus.TJJFW, ztTask.getStory(), projectproduct == null ? null : projectproduct.getProduct().toString(), projectproduct == null ? null : projectproduct.getProject(), ztTask.getExecution(),
|
actionService.addAction(ActionType.XQ, 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);
|
StringUtils.isEmpty(finishBy) ? RiskUserThreadLocal.get().getName() : finishBy, dto.getDeliverContent(), null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (!StringUtils.isEmpty(dto.getRemark())) {
|
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(),
|
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);
|
StringUtils.isEmpty(finishBy) ? RiskUserThreadLocal.get().getName() : finishBy, dto.getRemark(), null);
|
||||||
}
|
}
|
||||||
@@ -1095,13 +1077,9 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
this.storyService.taskFinishChangeStatus(ztTask.getStory(), finishBy, TaskType.transferType(type), false);
|
this.storyService.taskFinishChangeStatus(ztTask.getStory(), finishBy, TaskType.transferType(type), false);
|
||||||
}
|
}
|
||||||
if (ztTask.getFeedback() != null && ztTask.getFeedback() != 0) {
|
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
|
@Override
|
||||||
@@ -1118,9 +1096,9 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
ztTask.setLastediteddate(new Date());
|
ztTask.setLastediteddate(new Date());
|
||||||
this.baseMapper.updateById(ztTask);
|
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());
|
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());
|
ztStory.setDevelPlanEndTime(ztTask.getDeadline());
|
||||||
List<ZtTask> ztTasks = this.baseMapper.selectList(new QueryWrapper<ZtTask>().lambda()
|
List<ZtTask> ztTasks = this.baseMapper.selectList(new QueryWrapper<ZtTask>().lambda()
|
||||||
@@ -1132,9 +1110,9 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
.eq(ZtTask::getStory, ztTask.getStory()).orderByDesc(ZtTask::getDeadline));
|
.eq(ZtTask::getStory, ztTask.getStory()).orderByDesc(ZtTask::getDeadline));
|
||||||
this.storyService.update(new UpdateWrapper<ZtStory>().lambda()
|
this.storyService.update(new UpdateWrapper<ZtStory>().lambda()
|
||||||
.eq(ZtStory::getId, ztTask.getStory())
|
.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());
|
ztStory.setDevelPlanEndTime(ztTask.getDeadline());
|
||||||
List<ZtTask> ztTasks = this.baseMapper.selectList(new QueryWrapper<ZtTask>().lambda()
|
List<ZtTask> ztTasks = this.baseMapper.selectList(new QueryWrapper<ZtTask>().lambda()
|
||||||
@@ -1146,11 +1124,12 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
.eq(ZtTask::getStory, ztTask.getStory()).orderByDesc(ZtTask::getDeadline));
|
.eq(ZtTask::getStory, ztTask.getStory()).orderByDesc(ZtTask::getDeadline));
|
||||||
this.storyService.update(new UpdateWrapper<ZtStory>().lambda()
|
this.storyService.update(new UpdateWrapper<ZtStory>().lambda()
|
||||||
.eq(ZtStory::getId, ztTask.getStory())
|
.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<ZtProjectproduct>().lambda().eq(ZtProjectproduct::getProject, ztTask.getProject()));
|
ZtProjectproduct projectproduct = projectproductService.getOne(new QueryWrapper<ZtProjectproduct>().lambda().eq(ZtProjectproduct::getProject, ztTask.getProject()));
|
||||||
|
|
||||||
//添加action
|
//添加action
|
||||||
@@ -1196,9 +1175,9 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
//添加action
|
//添加action
|
||||||
actionService.addAction(ActionType.RW, ActionStatus.QX, dto.getId(), projectproduct == null ? null : projectproduct.getProduct().toString(), ztTask.getProject(), ztTask.getExecution(),
|
actionService.addAction(ActionType.RW, ActionStatus.QX, dto.getId(), projectproduct == null ? null : projectproduct.getProduct().toString(), ztTask.getProject(), ztTask.getExecution(),
|
||||||
RiskUserThreadLocal.get().getName(), dto.getDesc(), null);
|
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());
|
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());
|
ztStory.setDevelPlanEndTime(ztTask.getDeadline());
|
||||||
List<ZtTask> ztTasks = this.baseMapper.selectList(new QueryWrapper<ZtTask>().lambda()
|
List<ZtTask> ztTasks = this.baseMapper.selectList(new QueryWrapper<ZtTask>().lambda()
|
||||||
@@ -1210,9 +1189,9 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
.eq(ZtTask::getStory, ztTask.getStory()).orderByDesc(ZtTask::getDeadline));
|
.eq(ZtTask::getStory, ztTask.getStory()).orderByDesc(ZtTask::getDeadline));
|
||||||
this.storyService.update(new UpdateWrapper<ZtStory>().lambda()
|
this.storyService.update(new UpdateWrapper<ZtStory>().lambda()
|
||||||
.eq(ZtStory::getId, ztTask.getStory())
|
.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());
|
ztStory.setDevelPlanEndTime(ztTask.getDeadline());
|
||||||
List<ZtTask> ztTasks = this.baseMapper.selectList(new QueryWrapper<ZtTask>().lambda()
|
List<ZtTask> ztTasks = this.baseMapper.selectList(new QueryWrapper<ZtTask>().lambda()
|
||||||
@@ -1224,7 +1203,7 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
.eq(ZtTask::getStory, ztTask.getStory()).orderByDesc(ZtTask::getDeadline));
|
.eq(ZtTask::getStory, ztTask.getStory()).orderByDesc(ZtTask::getDeadline));
|
||||||
this.storyService.update(new UpdateWrapper<ZtStory>().lambda()
|
this.storyService.update(new UpdateWrapper<ZtStory>().lambda()
|
||||||
.eq(ZtStory::getId, ztTask.getStory())
|
.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) {
|
if (ztTask.getExecution() != null && ztTask.getExecution() != 0) {
|
||||||
@@ -1272,7 +1251,7 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
List<ZtTaskDTO> list = dto.getList();
|
List<ZtTaskDTO> list = dto.getList();
|
||||||
long nullDeadlineCount = list.stream().filter(o -> o.getDeadline() == null || o.getEstStarted() == null).count();
|
long nullDeadlineCount = list.stream().filter(o -> o.getDeadline() == null || o.getEstStarted() == null).count();
|
||||||
|
|
||||||
if (nullDeadlineCount > 0) {
|
if(nullDeadlineCount>0){
|
||||||
throw new BusinessException("请录入开始结束日期");
|
throw new BusinessException("请录入开始结束日期");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1319,13 +1298,13 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
}
|
}
|
||||||
saveList.add(ztTask);
|
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());
|
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());
|
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());
|
ztStory.setTestPlanEndTime(ztTask.getDeadline());
|
||||||
}
|
}
|
||||||
this.storyService.updateById(ztStory);
|
this.storyService.updateById(ztStory);
|
||||||
@@ -1340,7 +1319,7 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
this.storyService.taskFinishChangeStatus(ztTask.getStory(), null, TaskType.transferType(ztTask.getType()), false);
|
this.storyService.taskFinishChangeStatus(ztTask.getStory(), null, TaskType.transferType(ztTask.getType()), false);
|
||||||
taskSendZpMessage(ztTask.getId(), null, ztTask.getAssignedTo());
|
taskSendZpMessage(ztTask.getId(), null, ztTask.getAssignedTo());
|
||||||
} else {
|
} else {
|
||||||
taskSendPsMessage(ztTask.getId(), ztProject.getPm(), ztTask.getOpenedby());
|
taskSendPsMessage(ztTask.getId(), ztProject.getPm(),ztTask.getOpenedby());
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1362,167 +1341,6 @@ public class ZtTaskServiceImpl extends ServiceImpl<ZtTaskMapper, ZtTask> impleme
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@Transactional
|
|
||||||
public Map<String, Object> aiBatchAdd(ZtTaskAiBatchDTO dto) {
|
|
||||||
//仅 AI 框架通道可用:必须携带 ai 账户 token,创建人/留痕 actor 取自 token 身份(CHG-061)
|
|
||||||
LoginRiskUser aiLoginUser = RiskUserThreadLocal.get();
|
|
||||||
if (aiLoginUser == null || !"ai".equals(aiLoginUser.getName())) {
|
|
||||||
throw new BusinessException("aiBatchAdd仅AI框架通道可用(需ai账户token)");
|
|
||||||
}
|
|
||||||
String aiActor = aiLoginUser.getName();
|
|
||||||
if (dto.getStoryId() == null) {
|
|
||||||
throw new BusinessException("storyId不能为空");
|
|
||||||
}
|
|
||||||
ZtStory ztStory = this.storyService.getById(dto.getStoryId());
|
|
||||||
if (ztStory == null) {
|
|
||||||
throw new BusinessException("需求不存在:" + dto.getStoryId());
|
|
||||||
}
|
|
||||||
//已发布(含待验收/验收不通过:storyYs验收不通过status仍保持released)、已完成(验收通过)、已关闭的需求不允许AI拆任务
|
|
||||||
UserStoryEnums storyStatus = UserStoryEnums.transfer(ztStory.getStatus());
|
|
||||||
if (storyStatus == UserStoryEnums.RELEASED || storyStatus == UserStoryEnums.YWC || storyStatus == UserStoryEnums.CLOSED) {
|
|
||||||
throw new BusinessException("需求" + storyStatus.getValue() + ",不允许创建任务:" + dto.getStoryId());
|
|
||||||
}
|
|
||||||
List<ZtTaskAiBatchDTO.AiTaskItem> tasks = dto.getTasks();
|
|
||||||
if (CollectionUtils.isEmpty(tasks)) {
|
|
||||||
throw new BusinessException("tasks不能为空");
|
|
||||||
}
|
|
||||||
//type 非 devel/test/design/affair 整批拒绝(design CHG-081 放行、affair CHG-082 放行:均参与AI拆批;不回写需求计划时间、不进绩效专项公式)
|
|
||||||
for (ZtTaskAiBatchDTO.AiTaskItem item : tasks) {
|
|
||||||
if (StringUtils.isEmpty(item.getName())) {
|
|
||||||
throw new BusinessException("任务名称不能为空");
|
|
||||||
}
|
|
||||||
if (!TaskType.devel.getCode().equals(item.getType()) && !TaskType.test.getCode().equals(item.getType())
|
|
||||||
&& !TaskType.design.getCode().equals(item.getType()) && !TaskType.affair.getCode().equals(item.getType())) {
|
|
||||||
throw new BusinessException("任务类型仅支持devel/test/design/affair:" + item.getType());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//参数补全:推导 execution/project(老链路 batchAddTask 的看板挂载、project 回填依赖,缺失会NPE)
|
|
||||||
//需求关联多个迭代时取最后关联的(迭代id自增,project 倒序第一条即最新迭代)
|
|
||||||
ZtProjectstory execStory = this.projectstoryService.getOne(new QueryWrapper<ZtProjectstory>().lambda()
|
|
||||||
.eq(ZtProjectstory::getStory, dto.getStoryId())
|
|
||||||
.eq(ZtProjectstory::getType, ProjectTypeEnums.execution.getValue())
|
|
||||||
.orderByDesc(ZtProjectstory::getProject)
|
|
||||||
.last("limit 1"));
|
|
||||||
if (execStory == null) {
|
|
||||||
throw new BusinessException("需求未关联迭代,无法拆分任务");
|
|
||||||
}
|
|
||||||
Integer execution = execStory.getProject();
|
|
||||||
ZtProjectstory projectStoryCheck = this.projectstoryService.getOne(new QueryWrapper<ZtProjectstory>().lambda()
|
|
||||||
.eq(ZtProjectstory::getStory, dto.getStoryId())
|
|
||||||
.eq(ZtProjectstory::getType, ProjectTypeEnums.project.getValue())
|
|
||||||
.last("limit 1"));
|
|
||||||
if (projectStoryCheck == null) {
|
|
||||||
throw new BusinessException("需求未关联项目,无法拆分任务");
|
|
||||||
}
|
|
||||||
//同 storyId+name+type 已存在 → 跳过记 skipped
|
|
||||||
List<ZtTask> existList = this.list(new QueryWrapper<ZtTask>().lambda().eq(ZtTask::getStory, dto.getStoryId()));
|
|
||||||
Set<String> existKeys = existList.stream()
|
|
||||||
.filter(o -> o.getDeleted() == null || "0".equals(o.getDeleted()))
|
|
||||||
.map(o -> o.getName() + "#" + o.getType())
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
//调用前已有任务 id 快照(含已删除),用于调用后按 id 差集精确识别本次新建(openeddate 精度到秒会截断,时间比较不可靠)
|
|
||||||
Set<Integer> existIds = existList.stream().map(ZtTask::getId).collect(Collectors.toSet());
|
|
||||||
|
|
||||||
List<String> skipped = new ArrayList<>();
|
|
||||||
List<String> createdDescs = new ArrayList<>();
|
|
||||||
List<ZtTaskDTO> taskDtos = new ArrayList<>();
|
|
||||||
Set<String> createdKeys = new HashSet<>();
|
|
||||||
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
|
||||||
Date today = new Date();
|
|
||||||
for (ZtTaskAiBatchDTO.AiTaskItem item : tasks) {
|
|
||||||
String key = item.getName() + "#" + item.getType();
|
|
||||||
if (existKeys.contains(key) || createdKeys.contains(key)) {
|
|
||||||
skipped.add(item.getName());
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
//对齐老接口规则:工时必须录入
|
|
||||||
if (item.getAiEvaluationTime() == null) {
|
|
||||||
throw new BusinessException("请检查工时:" + item.getName());
|
|
||||||
}
|
|
||||||
//CHG-076:assignedTo 支持中文姓名——不是既有账号时按昵称查库映射账号(AI 框架传中文名场景)
|
|
||||||
String assignedTo = item.getAssignedTo();
|
|
||||||
if (StringUtils.isNotEmpty(assignedTo) && userService.getByAccount(assignedTo) == null) {
|
|
||||||
ZtUser byNickname = userService.getOne(new QueryWrapper<ZtUser>().lambda()
|
|
||||||
.eq(ZtUser::getNickname, assignedTo).ne(ZtUser::getDeleted, "1").last("limit 1"));
|
|
||||||
if (byNickname != null) {
|
|
||||||
assignedTo = byNickname.getAccount();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
//补全老接口必传字段:estimate/left/estStarted/deadline/execution(日期缺省补当天,deadline缺省取estStarted)
|
|
||||||
ZtTaskDTO taskDto = new ZtTaskDTO();
|
|
||||||
taskDto.setStory(dto.getStoryId());
|
|
||||||
taskDto.setExecution(execution);
|
|
||||||
taskDto.setName(item.getName());
|
|
||||||
taskDto.setType(item.getType());
|
|
||||||
taskDto.setAssignedTo(assignedTo);
|
|
||||||
taskDto.setDesc(item.getDesc());
|
|
||||||
taskDto.setEstimate(item.getAiEvaluationTime());
|
|
||||||
taskDto.setLeft(item.getAiEvaluationTime());
|
|
||||||
try {
|
|
||||||
taskDto.setEstStarted(StringUtils.isNotEmpty(item.getPlanStartDate()) ? sdf.parse(item.getPlanStartDate()) : today);
|
|
||||||
taskDto.setDeadline(StringUtils.isNotEmpty(item.getDeadline()) ? sdf.parse(item.getDeadline()) : taskDto.getEstStarted());
|
|
||||||
} catch (ParseException e) {
|
|
||||||
throw new BusinessException("日期格式错误,应为yyyy-MM-dd:" + item.getName());
|
|
||||||
}
|
|
||||||
taskDtos.add(taskDto);
|
|
||||||
createdKeys.add(key);
|
|
||||||
//需求级汇总文案收集(一次批拆合并为一条需求动态,CHG-062)
|
|
||||||
String assigneeSeg = "未指派";
|
|
||||||
if (StringUtils.isNotEmpty(assignedTo)) {
|
|
||||||
ZtUser assigneeUser = userService.getByAccount(assignedTo);
|
|
||||||
String nick = assigneeUser != null && StringUtils.isNotEmpty(assigneeUser.getNickname())
|
|
||||||
? assigneeUser.getNickname() : item.getAssignedTo();
|
|
||||||
assigneeSeg = "指派:" + nick;
|
|
||||||
}
|
|
||||||
//CHG-081:类型中文名按 TaskType 枚举取值(原写死 test→测试/其他→开发,design 放行后会被误记为「开发」)
|
|
||||||
createdDescs.add(item.getName() + "(" + TaskType.transferType(item.getType()).getValue()
|
|
||||||
+ ",工时" + item.getAiEvaluationTime() + "h"
|
|
||||||
+ "," + assigneeSeg + ")");
|
|
||||||
}
|
|
||||||
|
|
||||||
//走老批量创建链路:project/product回填、需求计划时间回写、消息推送、看板挂载、任务级留痕(RW+XJ)
|
|
||||||
List<Integer> taskIds = new ArrayList<>();
|
|
||||||
if (!taskDtos.isEmpty()) {
|
|
||||||
ZtTaskDTO batchDto = new ZtTaskDTO();
|
|
||||||
batchDto.setList(taskDtos);
|
|
||||||
this.batchAddTask(batchDto);
|
|
||||||
//batchAddTask无返回值:按 id 差集 + name#type 精确匹配取本次新建任务 id
|
|
||||||
List<ZtTask> createdList = this.list(new QueryWrapper<ZtTask>().lambda()
|
|
||||||
.eq(ZtTask::getStory, dto.getStoryId()));
|
|
||||||
taskIds = createdList.stream()
|
|
||||||
.filter(o -> !existIds.contains(o.getId()))
|
|
||||||
.filter(o -> createdKeys.contains(o.getName() + "#" + o.getType()))
|
|
||||||
.map(ZtTask::getId)
|
|
||||||
.sorted()
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
//需求级留痕:一次批拆合并写一条(CHG-062)
|
|
||||||
if (!createdDescs.isEmpty()) {
|
|
||||||
String[] nums = {"①", "②", "③", "④", "⑤", "⑥", "⑦", "⑧", "⑨", "⑩"};
|
|
||||||
StringBuilder sb = new StringBuilder("AI拆分任务 ").append(createdDescs.size()).append(" 个:");
|
|
||||||
for (int i = 0; i < createdDescs.size(); i++) {
|
|
||||||
sb.append(i < 10 ? nums[i] : ("(" + (i + 1) + ")")).append(createdDescs.get(i));
|
|
||||||
if (i < createdDescs.size() - 1) {
|
|
||||||
sb.append(";");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (!skipped.isEmpty()) {
|
|
||||||
sb.append(";重复跳过 ").append(skipped.size()).append(" 个");
|
|
||||||
}
|
|
||||||
actionService.addAction(com.sa.zentao.enums.ActionType.XQ, com.sa.zentao.enums.ActionStatus.BJ,
|
|
||||||
dto.getStoryId(), ztStory.getProduct() == null ? null : ztStory.getProduct() + "", null, null,
|
|
||||||
aiActor, sb.toString(), null);
|
|
||||||
}
|
|
||||||
|
|
||||||
Map<String, Object> result = new HashMap<>();
|
|
||||||
result.put("created", taskIds.size());
|
|
||||||
result.put("taskIds", taskIds);
|
|
||||||
result.put("skipped", skipped);
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public void approval(ZtTaskDTO dto) {
|
public void approval(ZtTaskDTO dto) {
|
||||||
|
|||||||
@@ -354,10 +354,6 @@ public class ZtUserServiceImpl extends ServiceImpl<ZtUserMapper, ZtUser> impleme
|
|||||||
}
|
}
|
||||||
ZtUser ztUser = new ZtUser();
|
ZtUser ztUser = new ZtUser();
|
||||||
BeanUtils.copyProperties(user,ztUser);
|
BeanUtils.copyProperties(user,ztUser);
|
||||||
//开发方向(CHG-038):仅 KFZ 保留,其余属性置空
|
|
||||||
if (ztUser.getUserType() != com.sa.zentao.enums.UserType.KFZ) {
|
|
||||||
ztUser.setDevDirection(null);
|
|
||||||
}
|
|
||||||
List<String> productList = user.getProductList();
|
List<String> productList = user.getProductList();
|
||||||
if(!CollectionUtils.isEmpty(productList)){
|
if(!CollectionUtils.isEmpty(productList)){
|
||||||
ztUser.setProductIds(StringUtils.join(productList,","));
|
ztUser.setProductIds(StringUtils.join(productList,","));
|
||||||
@@ -405,8 +401,6 @@ public class ZtUserServiceImpl extends ServiceImpl<ZtUserMapper, ZtUser> impleme
|
|||||||
|
|
||||||
ztUser.setPassword(user.getPassword());
|
ztUser.setPassword(user.getPassword());
|
||||||
ztUser.setUserType(user.getUserType());
|
ztUser.setUserType(user.getUserType());
|
||||||
//开发方向(CHG-038):非 KFZ 置空,KFZ 按表单值
|
|
||||||
ztUser.setDevDirection(user.getUserType() == com.sa.zentao.enums.UserType.KFZ ? user.getDevDirection() : null);
|
|
||||||
ztUser.setEmail(user.getEmail());
|
ztUser.setEmail(user.getEmail());
|
||||||
ztUser.setPhone(user.getPhone());
|
ztUser.setPhone(user.getPhone());
|
||||||
if(!CollectionUtils.isEmpty(user.getProductList())){
|
if(!CollectionUtils.isEmpty(user.getProductList())){
|
||||||
|
|||||||
@@ -1,74 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.sa.zentao.entity.ZtDocCheck;
|
|
||||||
import com.sa.zentao.entity.ZtStory;
|
|
||||||
import com.sa.zentao.service.IZtDocCheckService;
|
|
||||||
import com.sa.zentao.service.IZtStoryService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文档齐备扣分基类(FR-014 消费 zt_doc_check 快照):
|
|
||||||
* score = 100 − 缺失数×每份扣分,扣完截止;复核通过(appealResult=pass)的缺失不计。
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
public abstract class AbstractDocReadyCalculator extends AbstractPerfIndicatorCalculator {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected IZtDocCheckService docCheckService;
|
|
||||||
@Autowired
|
|
||||||
protected IZtStoryService storyService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 纳入统计的文档类型
|
|
||||||
*/
|
|
||||||
protected abstract Set<String> docTypes();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 每缺失一份扣分(缺省值,rule_json.deductPerStep 可覆盖)
|
|
||||||
*/
|
|
||||||
protected abstract int defaultDeductPerMissing();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 该核查记录是否归属此账号
|
|
||||||
*/
|
|
||||||
protected abstract boolean ownRow(ZtDocCheck row, Map<Integer, ZtStory> storyMap, String account);
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected BigDecimal doCompute(String account, String month, JSONObject rule) {
|
|
||||||
List<ZtDocCheck> rows = docCheckService.listMonthRows(month);
|
|
||||||
if (CollectionUtils.isEmpty(rows)) {
|
|
||||||
rawDetail("当月无核查记录");
|
|
||||||
return FULL_SCORE;
|
|
||||||
}
|
|
||||||
Set<String> types = docTypes();
|
|
||||||
List<ZtDocCheck> missing = rows.stream()
|
|
||||||
.filter(o -> types.contains(o.getDocType()))
|
|
||||||
.filter(ZtDocCheck::countsAsMissing)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
if (CollectionUtils.isEmpty(missing)) {
|
|
||||||
rawDetail("缺失 0 份");
|
|
||||||
return FULL_SCORE;
|
|
||||||
}
|
|
||||||
Set<Integer> storyIds = missing.stream().map(ZtDocCheck::getStoryId).collect(Collectors.toSet());
|
|
||||||
Map<Integer, ZtStory> storyMap = storyService.listByIds(storyIds).stream()
|
|
||||||
.collect(Collectors.toMap(ZtStory::getId, o -> o, (a, b) -> a));
|
|
||||||
long mine = missing.stream().filter(o -> ownRow(o, storyMap, account)).count();
|
|
||||||
if (rule == null) {
|
|
||||||
// 无配置:返回原始值(缺失份数)
|
|
||||||
return BigDecimal.valueOf(mine);
|
|
||||||
}
|
|
||||||
int perDeduct = rule.getIntValue("deductPerStep") == 0 ? defaultDeductPerMissing() : rule.getIntValue("deductPerStep");
|
|
||||||
rawDetail("缺失 " + mine + " 份 × 扣 " + perDeduct + " 分");
|
|
||||||
return clampScore(FULL_SCORE.subtract(BigDecimal.valueOf(mine * perDeduct)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 运维周频次指标基类(监控/巡检/备份):
|
|
||||||
* 应做次数 = 当月周数 × 每周次数(timesPerWeek) × 项目数(projects,备份用);
|
|
||||||
* 缺失 = max(0, 应做 − 实做);score = 100 − 缺失×deductPerMissing,扣完截止。
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
public abstract class AbstractOpsCountCalculator extends AbstractPerfIndicatorCalculator {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 实做次数(zt_yw* 记录数,当月 + 该账号)
|
|
||||||
*/
|
|
||||||
protected abstract long actualCount(String account, String month);
|
|
||||||
|
|
||||||
protected abstract int defaultTimesPerWeek();
|
|
||||||
|
|
||||||
protected abstract int defaultDeductPerMissing();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected BigDecimal doCompute(String account, String month, JSONObject rule) {
|
|
||||||
long actual = actualCount(account, month);
|
|
||||||
if (rule == null) {
|
|
||||||
// 无配置:返回原始值(实做次数)
|
|
||||||
return BigDecimal.valueOf(actual);
|
|
||||||
}
|
|
||||||
int timesPerWeek = rule.getIntValue("timesPerWeek") == 0 ? defaultTimesPerWeek() : rule.getIntValue("timesPerWeek");
|
|
||||||
int projects = rule.getIntValue("projects") == 0 ? 1 : rule.getIntValue("projects");
|
|
||||||
int deductPerMissing = rule.getIntValue("deductPerMissing") == 0 ? defaultDeductPerMissing() : rule.getIntValue("deductPerMissing");
|
|
||||||
long expected = (long) weeksOfMonth(month) * timesPerWeek * projects;
|
|
||||||
long missing = Math.max(0, expected - actual);
|
|
||||||
rawDetail("实做 " + actual + " / 应做 " + expected + "(缺 " + missing + " 次)");
|
|
||||||
return clampScore(FULL_SCORE.subtract(scaleDeduct(rule, BigDecimal.valueOf(missing * deductPerMissing))));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,298 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import com.alibaba.fastjson.JSONArray;
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.sa.zentao.entity.ZtPerfConfig;
|
|
||||||
import com.sa.zentao.service.IZtPerfConfigService;
|
|
||||||
import com.sa.zentao.utils.DateUtils;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.math.RoundingMode;
|
|
||||||
import java.util.Calendar;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绩效指标计算器基类:配置加载 + 规则引擎(linear/threshold/manual 三类)。
|
|
||||||
* rule_json 说明见 sql/20260728_init_zt_perf_config.sql 说明头。
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
public abstract class AbstractPerfIndicatorCalculator implements PerfIndicatorCalculator {
|
|
||||||
|
|
||||||
protected static final BigDecimal FULL_SCORE = BigDecimal.valueOf(100);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CHG-041 指标过程值(分子/分母/率,如 "Σ指数314.03 / 达标105h = 299.0%"),
|
|
||||||
* 供弹窗「绩效数据」列展示;ThreadLocal 保证单例计算器并发安全,compute 消费后清除。
|
|
||||||
*/
|
|
||||||
private static final ThreadLocal<String> RAW_DETAIL = new ThreadLocal<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CHG-053 当前选中的产品集(program id,月报下拉):myWorkScore 入口设置,
|
|
||||||
* 各项目口径指标按该产品集过滤;null = 按本人项目(旧行为)。
|
|
||||||
*/
|
|
||||||
private static final ThreadLocal<Integer> PROGRAM = new ThreadLocal<>();
|
|
||||||
|
|
||||||
public static void setCurrentProgram(Integer program) {
|
|
||||||
if (program == null) {
|
|
||||||
PROGRAM.remove();
|
|
||||||
} else {
|
|
||||||
PROGRAM.set(program);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public static void clearCurrentProgram() {
|
|
||||||
PROGRAM.remove();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Integer currentProgram() {
|
|
||||||
return PROGRAM.get();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected IZtPerfConfigService perfConfigService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 子类在 doCompute 内记录过程值
|
|
||||||
*/
|
|
||||||
protected void rawDetail(String detail) {
|
|
||||||
RAW_DETAIL.set(detail);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String consumeRawDetail() {
|
|
||||||
String detail = RAW_DETAIL.get();
|
|
||||||
RAW_DETAIL.remove();
|
|
||||||
return detail;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public BigDecimal compute(String account, String month) {
|
|
||||||
RAW_DETAIL.remove();
|
|
||||||
String role = perfConfigService.resolveRole(account);
|
|
||||||
ZtPerfConfig config = perfConfigService.getEnabledConfig(role, key());
|
|
||||||
JSONObject rule = parseRule(config);
|
|
||||||
//CHG-051 扣分尺度统一为加权尺度(用户拍板"10分满分 10-2"):
|
|
||||||
//把配置权重放进 rule,扣分按 1/权重 放大到 100 分制再扣(最终 ×权重 = xlsx 字面加权扣分)
|
|
||||||
if (rule != null && config != null && config.getWeight() != null
|
|
||||||
&& config.getWeight().compareTo(BigDecimal.ZERO) > 0) {
|
|
||||||
rule.put("__w", config.getWeight());
|
|
||||||
}
|
|
||||||
return doCompute(account, month, rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CHG-051 加权尺度扣分换算:xlsx 的"扣 X 分"是权重分值尺度(如 10 分项缺 1 份扣 2),
|
|
||||||
* 100 分制下需按 1/权重 放大;无权重配置时按原 100 分制扣分(兼容旧行为)。
|
|
||||||
*/
|
|
||||||
protected BigDecimal scaleDeduct(JSONObject rule, BigDecimal deduct) {
|
|
||||||
if (rule == null || deduct == null) {
|
|
||||||
return deduct;
|
|
||||||
}
|
|
||||||
Object w = rule.get("__w");
|
|
||||||
if (w instanceof BigDecimal && ((BigDecimal) w).compareTo(BigDecimal.ZERO) > 0) {
|
|
||||||
return deduct.divide((BigDecimal) w, 2, RoundingMode.HALF_UP);
|
|
||||||
}
|
|
||||||
return deduct;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 子类实现:先算原始值,再按 rule 套分;rule 为空时返回原始值
|
|
||||||
*/
|
|
||||||
protected abstract BigDecimal doCompute(String account, String month, JSONObject rule);
|
|
||||||
|
|
||||||
protected JSONObject parseRule(ZtPerfConfig config) {
|
|
||||||
if (config == null || StringUtils.isEmpty(config.getRuleJson())) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return JSON.parseObject(config.getRuleJson());
|
|
||||||
} catch (Exception e) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 规则引擎 ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* linear:每减/增 X% 扣 Y 分,扣完截止;支持 segments 分段线性。
|
|
||||||
* 单段参数:direction=below/above、target、step、deductPerStep;
|
|
||||||
* 分段参数:segments=[{"ge":95,"from":100,"deductPer":1},...](ge 为段下界含,from 为段上界不含,扣分按 from-max(actual,ge) 计)
|
|
||||||
*
|
|
||||||
* @param actual 实际值(百分比数值,如 97 表示 97%)
|
|
||||||
* @return 得分 0~100
|
|
||||||
*/
|
|
||||||
protected BigDecimal applyLinear(JSONObject rule, BigDecimal actual) {
|
|
||||||
if (rule == null || actual == null) {
|
|
||||||
return actual == null ? BigDecimal.ZERO : actual;
|
|
||||||
}
|
|
||||||
JSONArray segments = rule.getJSONArray("segments");
|
|
||||||
BigDecimal deduct;
|
|
||||||
if (segments != null && !segments.isEmpty()) {
|
|
||||||
deduct = BigDecimal.ZERO;
|
|
||||||
for (int i = 0; i < segments.size(); i++) {
|
|
||||||
JSONObject seg = segments.getJSONObject(i);
|
|
||||||
BigDecimal ge = seg.getBigDecimal("ge");
|
|
||||||
BigDecimal from = seg.getBigDecimal("from");
|
|
||||||
BigDecimal deductPer = seg.getBigDecimal("deductPer");
|
|
||||||
if (ge == null || from == null || deductPer == null) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (actual.compareTo(from) < 0) {
|
|
||||||
BigDecimal covered = from.subtract(actual.max(ge));
|
|
||||||
if (covered.compareTo(BigDecimal.ZERO) > 0) {
|
|
||||||
deduct = deduct.add(covered.multiply(deductPer));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
BigDecimal target = rule.getBigDecimal("target");
|
|
||||||
BigDecimal step = rule.getBigDecimal("step");
|
|
||||||
BigDecimal deductPerStep = rule.getBigDecimal("deductPerStep");
|
|
||||||
if (target == null || step == null || deductPerStep == null || step.compareTo(BigDecimal.ZERO) == 0) {
|
|
||||||
return actual;
|
|
||||||
}
|
|
||||||
String direction = rule.getString("direction");
|
|
||||||
BigDecimal diff;
|
|
||||||
if ("above".equals(direction)) {
|
|
||||||
diff = actual.subtract(target);
|
|
||||||
} else {
|
|
||||||
diff = target.subtract(actual);
|
|
||||||
}
|
|
||||||
if (diff.compareTo(BigDecimal.ZERO) <= 0) {
|
|
||||||
return FULL_SCORE;
|
|
||||||
}
|
|
||||||
BigDecimal steps = diff.divide(step, 0, RoundingMode.UP);
|
|
||||||
deduct = steps.multiply(deductPerStep);
|
|
||||||
}
|
|
||||||
return clampScore(FULL_SCORE.subtract(scaleDeduct(rule, deduct)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* threshold:阈值分档,bands=[{"ge":100,"score":100},{"ge":90,"score":40},{"ge":0,"score":0}],取首个 actual>=ge 档
|
|
||||||
*/
|
|
||||||
protected BigDecimal applyThreshold(JSONObject rule, BigDecimal actual) {
|
|
||||||
if (rule == null || actual == null) {
|
|
||||||
return actual == null ? BigDecimal.ZERO : actual;
|
|
||||||
}
|
|
||||||
JSONArray bands = rule.getJSONArray("bands");
|
|
||||||
if (bands == null || bands.isEmpty()) {
|
|
||||||
return actual;
|
|
||||||
}
|
|
||||||
for (int i = 0; i < bands.size(); i++) {
|
|
||||||
JSONObject band = bands.getJSONObject(i);
|
|
||||||
BigDecimal ge = band.getBigDecimal("ge");
|
|
||||||
BigDecimal score = band.getBigDecimal("score");
|
|
||||||
if (ge != null && score != null && actual.compareTo(ge) >= 0) {
|
|
||||||
return clampScore(score);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 按规则类型套分:linear/threshold;manual 或其他 → 返回原始值
|
|
||||||
*/
|
|
||||||
protected BigDecimal applyRule(JSONObject rule, BigDecimal actual) {
|
|
||||||
if (rule == null) {
|
|
||||||
return actual;
|
|
||||||
}
|
|
||||||
String type = rule.getString("type");
|
|
||||||
if ("linear".equals(type)) {
|
|
||||||
return applyLinear(rule, actual);
|
|
||||||
}
|
|
||||||
if ("threshold".equals(type)) {
|
|
||||||
return applyThreshold(rule, actual);
|
|
||||||
}
|
|
||||||
return actual;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected BigDecimal clampScore(BigDecimal score) {
|
|
||||||
if (score == null) {
|
|
||||||
return BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
if (score.compareTo(BigDecimal.ZERO) < 0) {
|
|
||||||
return BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
if (score.compareTo(FULL_SCORE) > 0) {
|
|
||||||
return FULL_SCORE;
|
|
||||||
}
|
|
||||||
return score;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==================== 取数辅助 ====================
|
|
||||||
|
|
||||||
/**
|
|
||||||
* yyyy-MM → 当月第一天
|
|
||||||
*/
|
|
||||||
protected Date monthToDate(String month) {
|
|
||||||
if (StringUtils.isEmpty(month)) {
|
|
||||||
throw new IllegalArgumentException("月份不能为空");
|
|
||||||
}
|
|
||||||
String[] parts = month.split("-");
|
|
||||||
Calendar calendar = Calendar.getInstance();
|
|
||||||
calendar.clear();
|
|
||||||
calendar.set(Integer.parseInt(parts[0]), Integer.parseInt(parts[1]) - 1, 1);
|
|
||||||
return calendar.getTime();
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Date firstDayOfMonth(String month) {
|
|
||||||
return DateUtils.getFirstDayOfMonth(monthToDate(month));
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Date lastDayOfMonth(String month) {
|
|
||||||
return DateUtils.getLastDayOfMonth(monthToDate(month));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 当月周数(按自然天数/7 向上取整,运维周频次指标基准)
|
|
||||||
*/
|
|
||||||
protected int weeksOfMonth(String month) {
|
|
||||||
Date d = monthToDate(month);
|
|
||||||
Calendar calendar = Calendar.getInstance();
|
|
||||||
calendar.setTime(d);
|
|
||||||
int days = calendar.getActualMaximum(Calendar.DAY_OF_MONTH);
|
|
||||||
return (int) Math.ceil(days / 7.0);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Float 集合求和(null 跳过)
|
|
||||||
*/
|
|
||||||
protected BigDecimal sumFloat(List<Float> values) {
|
|
||||||
BigDecimal sum = BigDecimal.ZERO;
|
|
||||||
if (values == null) {
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
for (Float v : values) {
|
|
||||||
if (v != null) {
|
|
||||||
sum = sum.add(BigDecimal.valueOf(v));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return sum;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 百分比:分子/分母×100,两位小数;分母为 0 返回 ZERO
|
|
||||||
*/
|
|
||||||
protected BigDecimal percent(BigDecimal numerator, BigDecimal denominator) {
|
|
||||||
if (denominator == null || denominator.compareTo(BigDecimal.ZERO) == 0) {
|
|
||||||
return BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
return numerator.multiply(FULL_SCORE).divide(denominator, 2, RoundingMode.HALF_UP);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 千分比:分子/分母×1000,两位小数;分母为 0 返回 ZERO
|
|
||||||
*/
|
|
||||||
protected BigDecimal permille(BigDecimal numerator, BigDecimal denominator) {
|
|
||||||
if (denominator == null || denominator.compareTo(BigDecimal.ZERO) == 0) {
|
|
||||||
return BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
return numerator.multiply(BigDecimal.valueOf(1000)).divide(denominator, 2, RoundingMode.HALF_UP);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,55 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
||||||
import com.sa.zentao.entity.ZtTask;
|
|
||||||
import com.sa.zentao.service.IZtTaskService;
|
|
||||||
import com.sa.zentao.utils.DateUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 任务及时完成率基类:Σ(按时完成任务的分配工时) ÷ Σ(当月完成任务分配工时)。
|
|
||||||
* 按时 = finishedDate ≤ deadline(当日末);typeFilter 区分全部任务(开发/UI)与测试任务(type=test)。
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
public abstract class AbstractTaskOnTimeRateCalculator extends AbstractPerfIndicatorCalculator {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected IZtTaskService taskService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 任务类型过滤,null = 全部类型
|
|
||||||
*/
|
|
||||||
protected abstract String typeFilter();
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected BigDecimal doCompute(String account, String month, JSONObject rule) {
|
|
||||||
LambdaQueryWrapper<ZtTask> wrapper = new QueryWrapper<ZtTask>().lambda()
|
|
||||||
.eq(ZtTask::getAssignedTo, account)
|
|
||||||
.eq(ZtTask::getStatus, "done")
|
|
||||||
.ge(ZtTask::getFinishedDate, firstDayOfMonth(month))
|
|
||||||
.le(ZtTask::getFinishedDate, lastDayOfMonth(month));
|
|
||||||
if (typeFilter() != null) {
|
|
||||||
wrapper.eq(ZtTask::getType, typeFilter());
|
|
||||||
}
|
|
||||||
List<ZtTask> tasks = taskService.list(wrapper);
|
|
||||||
if (CollectionUtils.isEmpty(tasks)) {
|
|
||||||
return BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
BigDecimal totalEstimate = sumFloat(tasks.stream().map(ZtTask::getEstimate).collect(Collectors.toList()));
|
|
||||||
BigDecimal onTimeEstimate = sumFloat(tasks.stream()
|
|
||||||
.filter(o -> o.getDeadline() != null && o.getFinishedDate() != null
|
|
||||||
&& o.getFinishedDate().getTime() <= DateUtils.getDayLast(o.getDeadline()).getTime())
|
|
||||||
.map(ZtTask::getEstimate).collect(Collectors.toList()));
|
|
||||||
BigDecimal rate = percent(onTimeEstimate, totalEstimate);
|
|
||||||
return applyRule(rule, rate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,120 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.sa.zentao.entity.ZtBug;
|
|
||||||
import com.sa.zentao.entity.ZtStory;
|
|
||||||
import com.sa.zentao.entity.ZtTask;
|
|
||||||
import com.sa.zentao.service.IZtBugService;
|
|
||||||
import com.sa.zentao.service.IZtTaskService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 线上 Bug 加权扣分基类(线上Bug率/产品缺陷率,同口径)。
|
|
||||||
* 已锁口径:线上 Bug = zt_bug.bug_type=prod;severity 1 重大、2/3/4 普通(以老弹窗 getBugFindScore 为准);率按 ‰ 计。
|
|
||||||
* rule mode=perBug:score = 100 − (普通×normalDeduct + 重大×majorDeduct),扣完截止。
|
|
||||||
* rule 含 exemptPerMille(PM/产品系 4 条规则):先按 PRD §7.3 算
|
|
||||||
* Σ(当月上线需求 Bug 数) ÷ Σ(上线需求开发任务工时) ‰,≤ exemptPerMille 满分豁免,> 才按 perBug 扣分。
|
|
||||||
* CHG-050 ①分母由 zt_story.estimate 改 devel 任务 estimate 合计(用户拍板"需求工时是任务sum",estimate 全线未填);
|
|
||||||
* ②四角色(项目经理/王宇航/产品经理/产品助理)改项目口径(∩当月窗口内执行关联产品的上线需求)。
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
public abstract class AbstractWeightedBugCalculator extends AbstractWorkloadRateCalculator {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected IZtBugService bugService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected IZtTaskService taskService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Set<Integer> scopeStoryIds(String account, String month) {
|
|
||||||
// 不走完成率管道,钩子占位
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int producerCount(String account, String month) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected BigDecimal doCompute(String account, String month, JSONObject rule) {
|
|
||||||
if (rule != null && rule.getBigDecimal("exemptPerMille") != null) {
|
|
||||||
return computeWithExempt(account, month, rule);
|
|
||||||
}
|
|
||||||
List<ZtBug> bugs = bugService.list(new QueryWrapper<ZtBug>().lambda()
|
|
||||||
.eq(ZtBug::getBugType, "prod")
|
|
||||||
.eq(ZtBug::getAssignedTo, account)
|
|
||||||
.ge(ZtBug::getOpeneddate, firstDayOfMonth(month))
|
|
||||||
.le(ZtBug::getOpeneddate, lastDayOfMonth(month)));
|
|
||||||
rawDetail("Bug " + (countMajor(bugs) + countNormal(bugs)) + " 个(重大 " + countMajor(bugs) + "/普通 " + countNormal(bugs) + "),按个扣分");
|
|
||||||
return perBugScore(bugs, rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ≤5‰ 豁免口径(PRD §7.3):分子=当月上线需求(releaseddate 当月,四角色时∩项目关联产品)的 prod Bug 数,
|
|
||||||
* 分母=当月上线需求的 devel 任务 estimate 合计(CHG-050);率 ≤ exemptPerMille 得满分,否则按个扣分。
|
|
||||||
*/
|
|
||||||
private BigDecimal computeWithExempt(String account, String month, JSONObject rule) {
|
|
||||||
List<ZtStory> stories = storyService.list(new QueryWrapper<ZtStory>().lambda()
|
|
||||||
.ge(ZtStory::getReleaseddate, firstDayOfMonth(month))
|
|
||||||
.le(ZtStory::getReleaseddate, lastDayOfMonth(month)));
|
|
||||||
boolean projectScoped = isProjectScopedRole(account);
|
|
||||||
if (projectScoped) {
|
|
||||||
Set<Integer> projectStoryIds = projectScopeStoryIds(account, month);
|
|
||||||
stories = stories.stream().filter(o -> projectStoryIds.contains(o.getId())).collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
if (CollectionUtils.isEmpty(stories)) {
|
|
||||||
rawDetail("当月无上线需求" + (projectScoped ? "(项目范围)" : ""));
|
|
||||||
return FULL_SCORE;
|
|
||||||
}
|
|
||||||
List<Integer> storyIds = stories.stream().map(ZtStory::getId).collect(Collectors.toList());
|
|
||||||
BigDecimal totalEstimate = sumFloat(taskService.list(new QueryWrapper<ZtTask>().lambda()
|
|
||||||
.in(ZtTask::getStory, storyIds)
|
|
||||||
.eq(ZtTask::getType, "devel")
|
|
||||||
.eq(ZtTask::getDeleted, "0")
|
|
||||||
.notIn(ZtTask::getStatus, "cancel", "closed"))
|
|
||||||
.stream().map(ZtTask::getEstimate).collect(Collectors.toList()));
|
|
||||||
//CHG-067:zt_bug.story 为死字段(全库 prod/dev Bug 均 story=0),需求关联实际走 toStory(用户拍板"启用的是toStory")
|
|
||||||
List<ZtBug> bugs = bugService.list(new QueryWrapper<ZtBug>().lambda()
|
|
||||||
.eq(ZtBug::getBugType, "prod")
|
|
||||||
.in(ZtBug::getTostory, storyIds));
|
|
||||||
BigDecimal rate = permille(BigDecimal.valueOf(countMajor(bugs) + countNormal(bugs)), totalEstimate);
|
|
||||||
rawDetail("Bug " + (countMajor(bugs) + countNormal(bugs)) + " 个(重大 " + countMajor(bugs) + "/普通 " + countNormal(bugs) + ")/ Σ工时 " + totalEstimate.stripTrailingZeros().toPlainString() + "h = " + rate.stripTrailingZeros().toPlainString() + "‰(豁免线 " + rule.getBigDecimal("exemptPerMille") + "‰)");
|
|
||||||
if (rate.compareTo(rule.getBigDecimal("exemptPerMille")) <= 0) {
|
|
||||||
return FULL_SCORE;
|
|
||||||
}
|
|
||||||
return perBugScore(bugs, rule);
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigDecimal perBugScore(List<ZtBug> bugs, JSONObject rule) {
|
|
||||||
long major = countMajor(bugs);
|
|
||||||
long normal = countNormal(bugs);
|
|
||||||
if (rule == null) {
|
|
||||||
// 无配置:返回原始值(Bug 总数)
|
|
||||||
return BigDecimal.valueOf(major + normal);
|
|
||||||
}
|
|
||||||
int normalDeduct = rule.getIntValue("normalDeduct") == 0 ? 3 : rule.getIntValue("normalDeduct");
|
|
||||||
int majorDeduct = rule.getIntValue("majorDeduct") == 0 ? 10 : rule.getIntValue("majorDeduct");
|
|
||||||
BigDecimal deduct = BigDecimal.valueOf(normal * normalDeduct + major * majorDeduct);
|
|
||||||
return clampScore(FULL_SCORE.subtract(scaleDeduct(rule, deduct)));
|
|
||||||
}
|
|
||||||
|
|
||||||
private long countMajor(List<ZtBug> bugs) {
|
|
||||||
return bugs.stream().filter(o -> o.getSeverity() != null && o.getSeverity() == 1).count();
|
|
||||||
}
|
|
||||||
|
|
||||||
private long countNormal(List<ZtBug> bugs) {
|
|
||||||
return bugs.stream().filter(o -> o.getSeverity() != null
|
|
||||||
&& (o.getSeverity() == 2 || o.getSeverity() == 3 || o.getSeverity() == 4)).count();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,249 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.sa.zentao.entity.ZtStoryMonthWorkload;
|
|
||||||
import com.sa.zentao.entity.ZtUser;
|
|
||||||
import com.sa.zentao.enums.UserType;
|
|
||||||
import com.sa.zentao.service.IZtStoryMonthWorkloadService;
|
|
||||||
import com.sa.zentao.service.IZtUserService;
|
|
||||||
import com.sa.zentao.utils.DateUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.Date;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 工作量指标完成率基类(FR-012)。
|
|
||||||
* 已锁口径:完成率 = Σ(zt_story_month_workload 当月 workload 增量) ÷ (可用工作天数×5×产出人数),
|
|
||||||
* workload 增量即 zt_story_month_workload.workload_index 列(写入时已按 指数×完成度增量/100 计算),
|
|
||||||
* 测试人员不计入产出方。
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
public abstract class AbstractWorkloadRateCalculator extends AbstractPerfIndicatorCalculator {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
protected IZtStoryMonthWorkloadService storyMonthWorkloadService;
|
|
||||||
@Autowired
|
|
||||||
protected IZtUserService userService;
|
|
||||||
@Autowired
|
|
||||||
protected com.sa.zentao.service.IZtTeamService teamService;
|
|
||||||
@Autowired
|
|
||||||
protected com.sa.zentao.service.IZtProjectService projectService;
|
|
||||||
@Autowired
|
|
||||||
protected com.sa.zentao.service.IZtExecutionprojectService executionprojectService;
|
|
||||||
@Autowired
|
|
||||||
protected com.sa.zentao.service.IZtProjectproductService projectproductService;
|
|
||||||
@Autowired
|
|
||||||
protected com.sa.zentao.service.IZtProductService productService;
|
|
||||||
@Autowired
|
|
||||||
protected com.sa.zentao.service.IZtStoryService storyService;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 需求范围过滤;返回 null 表示不限制(团队口径)
|
|
||||||
*/
|
|
||||||
protected abstract Set<Integer> scopeStoryIds(String account, String month);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 产出方人数(分母 = 工作天数×5×人数;个人口径返回 1)
|
|
||||||
*/
|
|
||||||
protected abstract int producerCount(String account, String month);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 是否项目经理(含王宇航变体 projectManagerWyh)
|
|
||||||
*/
|
|
||||||
protected boolean isProjectManager(String account) {
|
|
||||||
String role = perfConfigService.resolveRole(account);
|
|
||||||
return role != null && role.startsWith("projectManager");
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CHG-048 项目口径四角色:项目经理(含王宇航变体)、产品经理、产品助理
|
|
||||||
*/
|
|
||||||
protected boolean isProjectScopedRole(String account) {
|
|
||||||
String role = perfConfigService.resolveRole(account);
|
|
||||||
return "projectManager".equals(role) || "projectManagerWyh".equals(role)
|
|
||||||
|| "productManager".equals(role) || "productAssistant".equals(role);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CHG-043 项目经理项目口径:他当月窗口内(begin 或 end 落当月)参与的执行列表
|
|
||||||
*/
|
|
||||||
protected List<com.sa.zentao.entity.ZtProject> projectExecs(String account, String month) {
|
|
||||||
List<com.sa.zentao.entity.ZtTeam> teams = teamService.list(new QueryWrapper<com.sa.zentao.entity.ZtTeam>().lambda()
|
|
||||||
.eq(com.sa.zentao.entity.ZtTeam::getType, "execution")
|
|
||||||
.eq(com.sa.zentao.entity.ZtTeam::getAccount, account));
|
|
||||||
if (teams.isEmpty()) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
Date first = firstDayOfMonth(month);
|
|
||||||
Date last = lastDayOfMonth(month);
|
|
||||||
return projectService.list(new QueryWrapper<com.sa.zentao.entity.ZtProject>().lambda()
|
|
||||||
.in(com.sa.zentao.entity.ZtProject::getId, teams.stream().map(com.sa.zentao.entity.ZtTeam::getRoot).collect(Collectors.toList())))
|
|
||||||
.stream().filter(o -> (o.getBegin() != null && !o.getBegin().before(first) && !o.getBegin().after(last))
|
|
||||||
|| (o.getEnd() != null && !o.getEnd().before(first) && !o.getEnd().after(last)))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CHG-043 项目口径需求范围:他当月执行 → 关联产品的需求 id 集(无执行/无产品 → 空集,分子为 0)。
|
|
||||||
* CHG-053 优先按当前选中产品集(program)圈产品;未选中退回本人执行路径。
|
|
||||||
*/
|
|
||||||
protected Set<Integer> projectScopeStoryIds(String account, String month) {
|
|
||||||
Set<Integer> productIds = currentProgramProductIds();
|
|
||||||
if (productIds == null) {
|
|
||||||
List<com.sa.zentao.entity.ZtProject> execs = projectExecs(account, month);
|
|
||||||
if (execs.isEmpty()) {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
List<com.sa.zentao.entity.ZtExecutionproject> eps = executionprojectService.list(new QueryWrapper<com.sa.zentao.entity.ZtExecutionproject>().lambda()
|
|
||||||
.in(com.sa.zentao.entity.ZtExecutionproject::getExecution, execs.stream().map(com.sa.zentao.entity.ZtProject::getId).collect(Collectors.toList())));
|
|
||||||
if (eps.isEmpty()) {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
List<com.sa.zentao.entity.ZtProjectproduct> pps = projectproductService.list(new QueryWrapper<com.sa.zentao.entity.ZtProjectproduct>().lambda()
|
|
||||||
.in(com.sa.zentao.entity.ZtProjectproduct::getProject, eps.stream().map(com.sa.zentao.entity.ZtExecutionproject::getProject).collect(Collectors.toList())));
|
|
||||||
productIds = pps.stream().map(com.sa.zentao.entity.ZtProjectproduct::getProduct).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
if (productIds.isEmpty()) {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
return storyService.list(new QueryWrapper<com.sa.zentao.entity.ZtStory>().lambda()
|
|
||||||
.in(com.sa.zentao.entity.ZtStory::getProduct, productIds)).stream()
|
|
||||||
.map(com.sa.zentao.entity.ZtStory::getId).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CHG-043 项目口径产出人数:他当月窗口内执行的 KFZ 成员数(测试不计;他本人为 XMGLY 非 KFZ,天然不含)。
|
|
||||||
* CHG-053 优先按选中产品集下在窗执行的成员数;未选中退回本人执行路径。
|
|
||||||
*/
|
|
||||||
protected int projectProducerCount(String account, String month) {
|
|
||||||
List<com.sa.zentao.entity.ZtProject> execs;
|
|
||||||
Set<Integer> productIds = currentProgramProductIds();
|
|
||||||
if (productIds != null) {
|
|
||||||
if (productIds.isEmpty()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
execs = execsOfProducts(productIds, month);
|
|
||||||
} else {
|
|
||||||
execs = projectExecs(account, month);
|
|
||||||
}
|
|
||||||
if (execs.isEmpty()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
List<com.sa.zentao.entity.ZtTeam> members = teamService.list(new QueryWrapper<com.sa.zentao.entity.ZtTeam>().lambda()
|
|
||||||
.eq(com.sa.zentao.entity.ZtTeam::getType, "execution")
|
|
||||||
.in(com.sa.zentao.entity.ZtTeam::getRoot, execs.stream().map(com.sa.zentao.entity.ZtProject::getId).collect(Collectors.toList())));
|
|
||||||
List<String> accounts = members.stream().map(com.sa.zentao.entity.ZtTeam::getAccount).distinct().collect(Collectors.toList());
|
|
||||||
if (accounts.isEmpty()) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return (int) userService.count(new QueryWrapper<ZtUser>().lambda()
|
|
||||||
.eq(ZtUser::getUserType, UserType.KFZ)
|
|
||||||
.in(ZtUser::getAccount, accounts));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CHG-053 当前选中产品集(program id)→ 产品 id 集;未选中(null)返回 null
|
|
||||||
*/
|
|
||||||
protected Set<Integer> currentProgramProductIds() {
|
|
||||||
Integer program = currentProgram();
|
|
||||||
if (program == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return productService.list(new QueryWrapper<com.sa.zentao.entity.ZtProduct>().lambda()
|
|
||||||
.eq(com.sa.zentao.entity.ZtProduct::getProgram, program)).stream()
|
|
||||||
.map(com.sa.zentao.entity.ZtProduct::getId).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CHG-053 产品 id 集 → 当月窗口内(begin 或 end 落当月)的执行列表
|
|
||||||
*/
|
|
||||||
protected List<com.sa.zentao.entity.ZtProject> execsOfProducts(Set<Integer> productIds, String month) {
|
|
||||||
List<com.sa.zentao.entity.ZtProjectproduct> pps = projectproductService.list(new QueryWrapper<com.sa.zentao.entity.ZtProjectproduct>().lambda()
|
|
||||||
.in(com.sa.zentao.entity.ZtProjectproduct::getProduct, productIds));
|
|
||||||
if (pps.isEmpty()) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
List<com.sa.zentao.entity.ZtExecutionproject> eps = executionprojectService.list(new QueryWrapper<com.sa.zentao.entity.ZtExecutionproject>().lambda()
|
|
||||||
.in(com.sa.zentao.entity.ZtExecutionproject::getProject, pps.stream().map(com.sa.zentao.entity.ZtProjectproduct::getProject).collect(Collectors.toList())));
|
|
||||||
if (eps.isEmpty()) {
|
|
||||||
return Collections.emptyList();
|
|
||||||
}
|
|
||||||
Date first = firstDayOfMonth(month);
|
|
||||||
Date last = lastDayOfMonth(month);
|
|
||||||
return projectService.list(new QueryWrapper<com.sa.zentao.entity.ZtProject>().lambda()
|
|
||||||
.in(com.sa.zentao.entity.ZtProject::getId, eps.stream().map(com.sa.zentao.entity.ZtExecutionproject::getExecution).collect(Collectors.toList())))
|
|
||||||
.stream().filter(o -> (o.getBegin() != null && !o.getBegin().before(first) && !o.getBegin().after(last))
|
|
||||||
|| (o.getEnd() != null && !o.getEnd().before(first) && !o.getEnd().after(last)))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected BigDecimal doCompute(String account, String month, JSONObject rule) {
|
|
||||||
List<ZtStoryMonthWorkload> records = storyMonthWorkloadService.list(
|
|
||||||
new QueryWrapper<ZtStoryMonthWorkload>().lambda().eq(ZtStoryMonthWorkload::getTime, month));
|
|
||||||
Set<Integer> scope = scopeStoryIds(account, month);
|
|
||||||
BigDecimal total = BigDecimal.ZERO;
|
|
||||||
if (!CollectionUtils.isEmpty(records)) {
|
|
||||||
for (ZtStoryMonthWorkload record : records) {
|
|
||||||
if (scope != null && !scope.contains(record.getStoryId())) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (record.getWorkloadIndex() != null) {
|
|
||||||
total = total.add(record.getWorkloadIndex());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int workDays = DateUtils.getWorkDaysInCurrentMonth(monthToDate(month));
|
|
||||||
int producers = producerCount(account, month);
|
|
||||||
// 写死团队月达标总工时(如飞侠车服=525):选中产品集内有配置产品时,替换 工作天数×5×人数 公式算出的基数,其余逻辑不变
|
|
||||||
BigDecimal denominator;
|
|
||||||
BigDecimal fixedTeamTime = fixedTeamMonthTimeByProductIds(currentProgramProductIds());
|
|
||||||
if (fixedTeamTime != null) {
|
|
||||||
denominator = fixedTeamTime;
|
|
||||||
} else {
|
|
||||||
denominator = BigDecimal.valueOf((long) workDays * 5L * producers);
|
|
||||||
}
|
|
||||||
BigDecimal rate = percent(total, denominator);
|
|
||||||
rawDetail("Σ指数 " + total.stripTrailingZeros().toPlainString() + " / 达标 " + denominator.stripTrailingZeros().toPlainString() + "h = " + rate.stripTrailingZeros().toPlainString() + "%");
|
|
||||||
return applyRule(rule, rate);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 选中产品集内写死的团队月达标总工时合计(如 飞侠车服=525);未选中产品集或无配置返回 null
|
|
||||||
*/
|
|
||||||
private BigDecimal fixedTeamMonthTimeByProductIds(Set<Integer> productIds) {
|
|
||||||
if (CollectionUtils.isEmpty(productIds) || FixedTeamMonthTime.MAP.isEmpty()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
List<com.sa.zentao.entity.ZtProduct> products = productService.listByIds(productIds);
|
|
||||||
BigDecimal total = BigDecimal.ZERO;
|
|
||||||
for (com.sa.zentao.entity.ZtProduct p : products) {
|
|
||||||
BigDecimal fixed = FixedTeamMonthTime.MAP.get(p.getName());
|
|
||||||
if (fixed != null) {
|
|
||||||
total = total.add(fixed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return total.compareTo(BigDecimal.ZERO) > 0 ? total : null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 开发产出人数(userType=KFZ,测试 CS 不计入产出方)
|
|
||||||
*/
|
|
||||||
protected int devProducerCount() {
|
|
||||||
long count = userService.count(new QueryWrapper<ZtUser>().lambda().eq(ZtUser::getUserType, UserType.KFZ));
|
|
||||||
return (int) count;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Set<Integer> toStoryIds(List<com.sa.zentao.entity.ZtStoryExpand> expands) {
|
|
||||||
return expands.stream().map(com.sa.zentao.entity.ZtStoryExpand::getStoryId).collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,58 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.sa.zentao.entity.ZtBug;
|
|
||||||
import com.sa.zentao.entity.ZtTask;
|
|
||||||
import com.sa.zentao.service.IZtBugService;
|
|
||||||
import com.sa.zentao.service.IZtTaskService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bug密度(后端/前端 0.3):Σ(当月完成任务Bug数) ÷ Σ(完成任务分配工时),≤15% 满分、每增1%扣3分。
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class BugDensityCalculator extends AbstractPerfIndicatorCalculator {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtTaskService taskService;
|
|
||||||
@Autowired
|
|
||||||
private IZtBugService bugService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String key() {
|
|
||||||
return "bugDensity";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected BigDecimal doCompute(String account, String month, JSONObject rule) {
|
|
||||||
List<ZtTask> tasks = taskService.list(new QueryWrapper<ZtTask>().lambda()
|
|
||||||
.eq(ZtTask::getAssignedTo, account)
|
|
||||||
.eq(ZtTask::getStatus, "done")
|
|
||||||
.ge(ZtTask::getFinishedDate, firstDayOfMonth(month))
|
|
||||||
.le(ZtTask::getFinishedDate, lastDayOfMonth(month)));
|
|
||||||
if (CollectionUtils.isEmpty(tasks)) {
|
|
||||||
return BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
List<Integer> taskIds = tasks.stream().map(ZtTask::getId).collect(Collectors.toList());
|
|
||||||
long bugCount = bugService.count(new QueryWrapper<ZtBug>().lambda().in(ZtBug::getTask, taskIds));
|
|
||||||
BigDecimal estimateSum = sumFloat(tasks.stream().map(ZtTask::getEstimate).collect(Collectors.toList()));
|
|
||||||
BigDecimal density;
|
|
||||||
if (estimateSum.compareTo(BigDecimal.ZERO) == 0) {
|
|
||||||
// 除零边界:有 Bug 视为 100%,无 Bug 为 0
|
|
||||||
density = bugCount > 0 ? FULL_SCORE : BigDecimal.ZERO;
|
|
||||||
} else {
|
|
||||||
density = percent(BigDecimal.valueOf(bugCount), estimateSum);
|
|
||||||
}
|
|
||||||
return applyRule(rule, density);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,116 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSON;
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.sa.zentao.entity.ZtFile;
|
|
||||||
import com.sa.zentao.entity.ZtStory;
|
|
||||||
import com.sa.zentao.service.IZtFileService;
|
|
||||||
import com.sa.zentao.service.IZtStoryService;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 代码质量(后端/前端 0.1):解析 zt_file(objectType=aiCodeReview) MD 头部「严重:N/错误:N」计数扣分。
|
|
||||||
* rule mode=round(后端,默认):初审(round≤1) 严重1处扣3分、错误超6处扣3分;复审(round≥2) 严重1处扣5分、错误1处扣1分;
|
|
||||||
* rule mode=flat(前端):每发现1问题扣3分(severeDeduct/errorDeduct 可配)。
|
|
||||||
* 轮次取 zt_file.extra {"round":N},缺省按初审。
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class CodeQualityCalculator extends AbstractPerfIndicatorCalculator {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtStoryService storyService;
|
|
||||||
@Autowired
|
|
||||||
private IZtFileService fileService;
|
|
||||||
@Autowired
|
|
||||||
private MdContentResolver mdContentResolver;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String key() {
|
|
||||||
return "codeQuality";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected BigDecimal doCompute(String account, String month, JSONObject rule) {
|
|
||||||
List<ZtStory> stories = storyService.list(new QueryWrapper<ZtStory>().lambda()
|
|
||||||
.eq(ZtStory::getAssignedTo, account));
|
|
||||||
if (CollectionUtils.isEmpty(stories)) {
|
|
||||||
// 无负责需求:无审查对象,不扣分
|
|
||||||
return FULL_SCORE;
|
|
||||||
}
|
|
||||||
List<ZtFile> files = fileService.list(new QueryWrapper<ZtFile>().lambda()
|
|
||||||
.eq(ZtFile::getObjecttype, "aiCodeReview")
|
|
||||||
.in(ZtFile::getObjectid, stories.stream().map(ZtStory::getId).collect(Collectors.toList()))
|
|
||||||
.and(w -> w.isNull(ZtFile::getDeleted).or().eq(ZtFile::getDeleted, "0"))
|
|
||||||
.ge(ZtFile::getAddeddate, firstDayOfMonth(month))
|
|
||||||
.le(ZtFile::getAddeddate, lastDayOfMonth(month)));
|
|
||||||
if (CollectionUtils.isEmpty(files)) {
|
|
||||||
return FULL_SCORE;
|
|
||||||
}
|
|
||||||
String mode = rule == null ? "round" : rule.getString("mode");
|
|
||||||
BigDecimal deduct = BigDecimal.ZERO;
|
|
||||||
int totalProblems = 0;
|
|
||||||
for (ZtFile file : files) {
|
|
||||||
String content = mdContentResolver.readContent(file);
|
|
||||||
int[] counts = MdContentResolver.parseReviewCounts(content);
|
|
||||||
totalProblems += counts[0] + counts[1];
|
|
||||||
if ("flat".equals(mode)) {
|
|
||||||
int severeDeduct = rule.getIntValue("severeDeduct") == 0 ? 3 : rule.getIntValue("severeDeduct");
|
|
||||||
int errorDeduct = rule.getIntValue("errorDeduct") == 0 ? 3 : rule.getIntValue("errorDeduct");
|
|
||||||
deduct = deduct.add(BigDecimal.valueOf((long) counts[0] * severeDeduct + (long) counts[1] * errorDeduct));
|
|
||||||
} else {
|
|
||||||
deduct = deduct.add(roundDeduct(rule, parseRound(file), counts));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (rule == null) {
|
|
||||||
// 无配置:返回原始值(问题总数)
|
|
||||||
return BigDecimal.valueOf(totalProblems);
|
|
||||||
}
|
|
||||||
return clampScore(FULL_SCORE.subtract(deduct));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 后端轮次扣分:初审严重1处扣3、错误超6处扣3;复审严重1处扣5、错误1处扣1
|
|
||||||
*/
|
|
||||||
private BigDecimal roundDeduct(JSONObject rule, int round, int[] counts) {
|
|
||||||
if (round >= 2) {
|
|
||||||
int severeDeduct = rule.getIntValue("reRoundSevereDeduct") == 0 ? 5 : rule.getIntValue("reRoundSevereDeduct");
|
|
||||||
int errorDeduct = rule.getIntValue("reRoundErrorDeduct") == 0 ? 1 : rule.getIntValue("reRoundErrorDeduct");
|
|
||||||
return BigDecimal.valueOf((long) counts[0] * severeDeduct + (long) counts[1] * errorDeduct);
|
|
||||||
}
|
|
||||||
int severeDeduct = rule.getIntValue("firstRoundSevereDeduct") == 0 ? 3 : rule.getIntValue("firstRoundSevereDeduct");
|
|
||||||
int threshold = rule.getIntValue("firstRoundErrorThreshold") == 0 ? 6 : rule.getIntValue("firstRoundErrorThreshold");
|
|
||||||
int errorDeduct = rule.getIntValue("firstRoundErrorDeduct") == 0 ? 3 : rule.getIntValue("firstRoundErrorDeduct");
|
|
||||||
long deduct = (long) counts[0] * severeDeduct;
|
|
||||||
if (counts[1] > threshold) {
|
|
||||||
deduct += errorDeduct;
|
|
||||||
}
|
|
||||||
return BigDecimal.valueOf(deduct);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 审查轮次:zt_file.extra {"round":N},缺省 1(初审)
|
|
||||||
*/
|
|
||||||
private int parseRound(ZtFile file) {
|
|
||||||
if (StringUtils.isEmpty(file.getExtra())) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
JSONObject extra = JSON.parseObject(file.getExtra());
|
|
||||||
Integer round = extra.getInteger("round");
|
|
||||||
return round == null || round < 1 ? 1 : round;
|
|
||||||
} catch (Exception e) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,65 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.sa.zentao.entity.ZtBug;
|
|
||||||
import com.sa.zentao.entity.ZtTask;
|
|
||||||
import com.sa.zentao.service.IZtBugService;
|
|
||||||
import com.sa.zentao.service.IZtTaskService;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 缺陷检出率(测试工程师 0.3):(普通Bug×1+重大Bug×5) ÷ 测试需求开发工时,>20% 满分、每减1%扣2分。
|
|
||||||
* 已锁口径:severity 1 重大、2/3/4 普通(以老弹窗 getBugFindScore 为准)。
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class DefectFindRateCalculator extends AbstractPerfIndicatorCalculator {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtBugService bugService;
|
|
||||||
@Autowired
|
|
||||||
private IZtTaskService taskService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String key() {
|
|
||||||
return "defectFindRate";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected BigDecimal doCompute(String account, String month, JSONObject rule) {
|
|
||||||
List<ZtBug> bugs = bugService.list(new QueryWrapper<ZtBug>().lambda()
|
|
||||||
.eq(ZtBug::getOpenedby, account)
|
|
||||||
.ge(ZtBug::getOpeneddate, firstDayOfMonth(month))
|
|
||||||
.le(ZtBug::getOpeneddate, lastDayOfMonth(month)));
|
|
||||||
// 与 IZtCountService.getBugFindScore 同口径:closed/cancel 且无解决人 不计
|
|
||||||
bugs = bugs.stream()
|
|
||||||
.filter(o -> ("closed".equals(o.getStatus()) || "cancel".equals(o.getStatus()))
|
|
||||||
? StringUtils.isNotEmpty(o.getResolvedby()) : true)
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
long major = bugs.stream().filter(o -> o.getSeverity() != null && o.getSeverity() == 1).count();
|
|
||||||
long normal = bugs.stream().filter(o -> o.getSeverity() != null
|
|
||||||
&& (o.getSeverity() == 2 || o.getSeverity() == 3 || o.getSeverity() == 4)).count();
|
|
||||||
long weighted = major * 5 + normal;
|
|
||||||
List<Integer> storyIds = bugs.stream().map(ZtBug::getTostory).filter(o -> o != null).distinct().collect(Collectors.toList());
|
|
||||||
BigDecimal devHours = BigDecimal.ZERO;
|
|
||||||
if (!CollectionUtils.isEmpty(storyIds)) {
|
|
||||||
List<ZtTask> devTasks = taskService.list(new QueryWrapper<ZtTask>().lambda()
|
|
||||||
.eq(ZtTask::getType, "devel")
|
|
||||||
.notIn(ZtTask::getStatus, "cancel", "closed")
|
|
||||||
.in(ZtTask::getStory, storyIds));
|
|
||||||
devHours = sumFloat(devTasks.stream().map(ZtTask::getEstimate).collect(Collectors.toList()));
|
|
||||||
}
|
|
||||||
BigDecimal rate = percent(BigDecimal.valueOf(weighted), devHours);
|
|
||||||
return applyRule(rule, rate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,123 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.sa.zentao.entity.ZtStory;
|
|
||||||
import com.sa.zentao.entity.ZtStoryExpand;
|
|
||||||
import com.sa.zentao.entity.ZtStoryMonthWorkload;
|
|
||||||
import com.sa.zentao.service.IZtStoryExpandService;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 文档齐备(大型需求五类,项目经理 0.1):每缺失一份扣2分,扣完截止。
|
|
||||||
* CHG-047 改实时判定(用户拍板):直接读 zt_story 五个 url 字段非空即在,
|
|
||||||
* 不再依赖 zt_doc_check 快照/月末 job;归属改项目口径(项目经理=他当月窗口内执行关联产品的大型需求)。
|
|
||||||
* 大型需求 = 当月有工作量指数记录且 AI 评估指数 >20(与原快照范围一致)。
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class DocReadyScoreCalculator extends AbstractWorkloadRateCalculator {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtStoryExpandService storyExpandService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String key() {
|
|
||||||
return "docReadyScore";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected Set<Integer> scopeStoryIds(String account, String month) {
|
|
||||||
// 本计算器不走完成率管道(doCompute 整体重写),钩子占位
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int producerCount(String account, String month) {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected BigDecimal doCompute(String account, String month, JSONObject rule) {
|
|
||||||
// 大型需求:当月有指数记录且指数>20
|
|
||||||
List<ZtStoryMonthWorkload> records = storyMonthWorkloadService.list(
|
|
||||||
new QueryWrapper<ZtStoryMonthWorkload>().lambda().eq(ZtStoryMonthWorkload::getTime, month));
|
|
||||||
if (CollectionUtils.isEmpty(records)) {
|
|
||||||
rawDetail("当月无大型需求");
|
|
||||||
return FULL_SCORE;
|
|
||||||
}
|
|
||||||
Set<Integer> monthStoryIds = records.stream().map(ZtStoryMonthWorkload::getStoryId).collect(Collectors.toSet());
|
|
||||||
Set<Integer> largeIds = storyExpandService.list(new QueryWrapper<ZtStoryExpand>().lambda()
|
|
||||||
.in(ZtStoryExpand::getStoryId, monthStoryIds)).stream()
|
|
||||||
.filter(o -> parseIndex(o.getWorkloadIndex()).compareTo(BigDecimal.valueOf(20)) > 0)
|
|
||||||
.map(ZtStoryExpand::getStoryId).collect(Collectors.toSet());
|
|
||||||
if (largeIds.isEmpty()) {
|
|
||||||
rawDetail("当月无大型需求");
|
|
||||||
return FULL_SCORE;
|
|
||||||
}
|
|
||||||
// 项目经理归属:∩ 他当月窗口内执行关联产品的需求(CHG-043 项目口径)
|
|
||||||
Set<Integer> scopeIds = largeIds;
|
|
||||||
if (isProjectManager(account)) {
|
|
||||||
Set<Integer> projectStoryIds = projectScopeStoryIds(account, month);
|
|
||||||
scopeIds = largeIds.stream().filter(projectStoryIds::contains).collect(Collectors.toSet());
|
|
||||||
if (scopeIds.isEmpty()) {
|
|
||||||
rawDetail("当月无大型需求(项目范围)");
|
|
||||||
return FULL_SCORE;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Map<Integer, ZtStory> storyMap = storyService.listByIds(scopeIds).stream()
|
|
||||||
.collect(Collectors.toMap(ZtStory::getId, o -> o, (a, b) -> a));
|
|
||||||
// 五类 url 字段非空即在:用例/报告提交/更新记录/审查/日志
|
|
||||||
int missing = 0;
|
|
||||||
StringBuilder missDesc = new StringBuilder();
|
|
||||||
for (ZtStory s : storyMap.values()) {
|
|
||||||
List<String> lack = new ArrayList<>();
|
|
||||||
if (StringUtils.isBlank(s.getTestCaseUrl())) {
|
|
||||||
lack.add("用例");
|
|
||||||
}
|
|
||||||
if (StringUtils.isBlank(s.getTestReportSubmitUrl())) {
|
|
||||||
lack.add("报告");
|
|
||||||
}
|
|
||||||
if (StringUtils.isBlank(s.getAiDocUpdateUrl())) {
|
|
||||||
lack.add("更新记录");
|
|
||||||
}
|
|
||||||
if (StringUtils.isBlank(s.getCodeReviewUrl())) {
|
|
||||||
lack.add("审查");
|
|
||||||
}
|
|
||||||
if (StringUtils.isBlank(s.getWorkLogUrl())) {
|
|
||||||
lack.add("日志");
|
|
||||||
}
|
|
||||||
missing += lack.size();
|
|
||||||
if (!lack.isEmpty()) {
|
|
||||||
missDesc.append(s.getId()).append("缺[").append(String.join(",", lack)).append("] ");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int perDeduct = rule == null || rule.getIntValue("deductPerStep") == 0 ? 2 : rule.getIntValue("deductPerStep");
|
|
||||||
rawDetail("大型需求 " + scopeIds.size() + " 个,缺失 " + missing + " 份"
|
|
||||||
+ (missing > 0 ? "(" + missDesc.toString().trim() + ")" : ""));
|
|
||||||
return clampScore(FULL_SCORE.subtract(scaleDeduct(rule, BigDecimal.valueOf((long) missing * perDeduct))));
|
|
||||||
}
|
|
||||||
|
|
||||||
private BigDecimal parseIndex(String workloadIndex) {
|
|
||||||
if (workloadIndex == null || workloadIndex.trim().isEmpty()) {
|
|
||||||
return BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
return new BigDecimal(workloadIndex.trim());
|
|
||||||
} catch (NumberFormatException e) {
|
|
||||||
return BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 团队月度达标总工时写死配置:产品名 → 团队月达标总工时。
|
|
||||||
* 命中产品的团队,达标基数用写死值替换公式算出的值(工作天数×人数×5),各公式其余逻辑不变。
|
|
||||||
* 以后别的产品要写死,往 MAP 加一行。
|
|
||||||
*/
|
|
||||||
public final class FixedTeamMonthTime {
|
|
||||||
|
|
||||||
public static final Map<String, BigDecimal> MAP;
|
|
||||||
|
|
||||||
static {
|
|
||||||
Map<String, BigDecimal> m = new HashMap<>();
|
|
||||||
// 飞侠车服(外包团队合同包月工时)
|
|
||||||
m.put("飞侠车服", BigDecimal.valueOf(525));
|
|
||||||
MAP = Collections.unmodifiableMap(m);
|
|
||||||
}
|
|
||||||
|
|
||||||
private FixedTeamMonthTime() {
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,125 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import com.sa.zentao.entity.ZtFile;
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Value;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.InputStream;
|
|
||||||
import java.net.URL;
|
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.nio.file.Files;
|
|
||||||
import java.util.regex.Matcher;
|
|
||||||
import java.util.regex.Pattern;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* MD 附件内容读取器(三期:代码审查报告头部「严重:N/错误:N」解析)。
|
|
||||||
* 取数顺序:url/pathname 为相对路径(/zentao/img/xxx.md)→ 读本地文件目录;
|
|
||||||
* 绝对 http 地址 → URL 拉取(与 CommonsController.downloadByIO 同通道)。
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
@Slf4j
|
|
||||||
public class MdContentResolver {
|
|
||||||
|
|
||||||
/** 严重计数(支持全/半角冒号) */
|
|
||||||
private static final Pattern SEVERE_PATTERN = Pattern.compile("严重\\s*[::]\\s*(\\d+)");
|
|
||||||
/** 错误计数 */
|
|
||||||
private static final Pattern ERROR_PATTERN = Pattern.compile("错误\\s*[::]\\s*(\\d+)");
|
|
||||||
/** 头部扫描行数上限 */
|
|
||||||
private static final int HEADER_LINES = 30;
|
|
||||||
|
|
||||||
@Value("${file.path.windows:}")
|
|
||||||
private String windowsFilePath;
|
|
||||||
|
|
||||||
@Value("${file.path.linux:}")
|
|
||||||
private String linuxFilePath;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 读取附件文本内容,失败返回 null(不抛异常,取数降级由调用方决定)
|
|
||||||
*/
|
|
||||||
public String readContent(ZtFile ztFile) {
|
|
||||||
if (ztFile == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
String path = StringUtils.isNotEmpty(ztFile.getUrl()) ? ztFile.getUrl() : ztFile.getPathname();
|
|
||||||
if (StringUtils.isEmpty(path)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
if (path.startsWith("http://") || path.startsWith("https://")) {
|
|
||||||
return readByUrl(path);
|
|
||||||
}
|
|
||||||
return readLocal(path);
|
|
||||||
} catch (Exception e) {
|
|
||||||
log.warn("读取附件内容失败 id={} path={} err={}", ztFile.getId(), path, e.getMessage());
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private String readLocal(String path) throws Exception {
|
|
||||||
String fileName = path.substring(path.lastIndexOf("/") + 1);
|
|
||||||
String dir;
|
|
||||||
String os = System.getProperty("os.name");
|
|
||||||
if (os != null && os.toLowerCase().startsWith("win")) {
|
|
||||||
dir = windowsFilePath;
|
|
||||||
} else {
|
|
||||||
dir = linuxFilePath;
|
|
||||||
}
|
|
||||||
if (StringUtils.isEmpty(dir)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
File file = new File(dir, fileName);
|
|
||||||
if (!file.exists()) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
byte[] bytes = Files.readAllBytes(file.toPath());
|
|
||||||
return new String(bytes, StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
|
|
||||||
private String readByUrl(String url) throws Exception {
|
|
||||||
try (InputStream is = new URL(url).openStream();
|
|
||||||
ByteArrayOutputStream bos = new ByteArrayOutputStream()) {
|
|
||||||
byte[] buff = new byte[8192];
|
|
||||||
int count;
|
|
||||||
while ((count = is.read(buff)) != -1) {
|
|
||||||
bos.write(buff, 0, count);
|
|
||||||
}
|
|
||||||
return new String(bos.toByteArray(), StandardCharsets.UTF_8);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 解析审查报告 MD 头部「严重:N/错误:N」计数(仅扫描前 {@value #HEADER_LINES} 行)。
|
|
||||||
*
|
|
||||||
* @param content MD 内容(可空)
|
|
||||||
* @return int[]{严重数, 错误数}
|
|
||||||
*/
|
|
||||||
public static int[] parseReviewCounts(String content) {
|
|
||||||
int severe = 0;
|
|
||||||
int error = 0;
|
|
||||||
if (StringUtils.isEmpty(content)) {
|
|
||||||
return new int[]{0, 0};
|
|
||||||
}
|
|
||||||
String[] lines = content.split("\\R");
|
|
||||||
StringBuilder header = new StringBuilder();
|
|
||||||
int limit = Math.min(lines.length, HEADER_LINES);
|
|
||||||
for (int i = 0; i < limit; i++) {
|
|
||||||
header.append(lines[i]).append('\n');
|
|
||||||
}
|
|
||||||
Matcher severeMatcher = SEVERE_PATTERN.matcher(header.toString());
|
|
||||||
if (severeMatcher.find()) {
|
|
||||||
severe = Integer.parseInt(severeMatcher.group(1));
|
|
||||||
}
|
|
||||||
Matcher errorMatcher = ERROR_PATTERN.matcher(header.toString());
|
|
||||||
if (errorMatcher.find()) {
|
|
||||||
error = Integer.parseInt(errorMatcher.group(1));
|
|
||||||
}
|
|
||||||
return new int[]{severe, error};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 线上Bug(项目经理 0.1:普通扣3/重大扣10;测试工程师 0.2:普通扣5/重大该项0分——规则参数在 zt_perf_config)。
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class OnlineBugRateCalculator extends AbstractWeightedBugCalculator {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String key() {
|
|
||||||
return "onlineBugRate";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,44 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.sa.zentao.entity.ZtYwBackups;
|
|
||||||
import com.sa.zentao.service.IZtYwBackupsService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 数据库备份(运维 0.1):每项目每周全量,缺一个扣3分;实做 = zt_yw_backups 当月记录数;
|
|
||||||
* 项目数经 rule_json.projects 配置(缺省 1)。
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class OpsBackupCalculator extends AbstractOpsCountCalculator {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtYwBackupsService backupsService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String key() {
|
|
||||||
return "opsBackup";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected long actualCount(String account, String month) {
|
|
||||||
return backupsService.count(new QueryWrapper<ZtYwBackups>().lambda()
|
|
||||||
.eq(ZtYwBackups::getCreateUser, account)
|
|
||||||
.ge(ZtYwBackups::getBackupsDate, firstDayOfMonth(month))
|
|
||||||
.le(ZtYwBackups::getBackupsDate, lastDayOfMonth(month)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int defaultTimesPerWeek() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int defaultDeductPerMissing() {
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.sa.zentao.entity.ZtYwPatrol;
|
|
||||||
import com.sa.zentao.service.IZtYwPatrolService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 职场巡检(运维 0.1):每周1次,缺一次扣5分;实做 = zt_yw_patrol 当月记录数。
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class OpsInspectCalculator extends AbstractOpsCountCalculator {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtYwPatrolService patrolService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String key() {
|
|
||||||
return "opsInspect";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected long actualCount(String account, String month) {
|
|
||||||
return patrolService.count(new QueryWrapper<ZtYwPatrol>().lambda()
|
|
||||||
.eq(ZtYwPatrol::getPatrolUser, account)
|
|
||||||
.ge(ZtYwPatrol::getPatrolDate, firstDayOfMonth(month))
|
|
||||||
.le(ZtYwPatrol::getPatrolDate, lastDayOfMonth(month)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int defaultTimesPerWeek() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int defaultDeductPerMissing() {
|
|
||||||
return 5;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,63 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import com.alibaba.fastjson.JSONObject;
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.sa.zentao.entity.ZtUser;
|
|
||||||
import com.sa.zentao.entity.ZtYwTask;
|
|
||||||
import com.sa.zentao.service.IZtUserService;
|
|
||||||
import com.sa.zentao.service.IZtYwTaskService;
|
|
||||||
import com.sa.zentao.utils.DateUtils;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
import org.springframework.util.CollectionUtils;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 运维大项任务及时完成(运维 0.2):当月到期运维任务 及时完成率×20(折成百分制得分=及时率×100)。
|
|
||||||
* 及时 = finishDate ≤ taskEndDate(当日末)。
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class OpsMajorTaskCalculator extends AbstractPerfIndicatorCalculator {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtYwTaskService ywTaskService;
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtUserService userService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String key() {
|
|
||||||
return "opsMajorTask";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected BigDecimal doCompute(String account, String month, JSONObject rule) {
|
|
||||||
//CHG-039 同类修复:zt_yw_task.belong_to_user 实际存中文姓名(“岑海峰”),按 account 匹配恒空 → 按昵称匹配,account 兜底
|
|
||||||
String nameKey = account;
|
|
||||||
ZtUser u = userService.getByAccount(account);
|
|
||||||
if (u != null && u.getNickname() != null && !u.getNickname().isEmpty()) {
|
|
||||||
nameKey = u.getNickname();
|
|
||||||
}
|
|
||||||
final String personKey = nameKey;
|
|
||||||
List<ZtYwTask> tasks = ywTaskService.list(new QueryWrapper<ZtYwTask>().lambda()
|
|
||||||
.and(w -> w.eq(ZtYwTask::getBelongToUser, personKey).or().eq(ZtYwTask::getBelongToUser, account))
|
|
||||||
.ge(ZtYwTask::getTaskEndDate, firstDayOfMonth(month))
|
|
||||||
.le(ZtYwTask::getTaskEndDate, lastDayOfMonth(month)));
|
|
||||||
if (CollectionUtils.isEmpty(tasks)) {
|
|
||||||
rawDetail("当月无到期运维任务");
|
|
||||||
return BigDecimal.ZERO;
|
|
||||||
}
|
|
||||||
long onTime = tasks.stream()
|
|
||||||
.filter(o -> o.getFinishDate() != null
|
|
||||||
&& o.getFinishDate().getTime() <= DateUtils.getDayLast(o.getTaskEndDate()).getTime())
|
|
||||||
.count();
|
|
||||||
BigDecimal rate = percent(BigDecimal.valueOf(onTime), BigDecimal.valueOf(tasks.size()));
|
|
||||||
rawDetail("及时 " + onTime + " / 到期 " + tasks.size() + " = " + rate.stripTrailingZeros().toPlainString() + "%");
|
|
||||||
return applyRule(rule, rate);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,43 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
|
||||||
import com.sa.zentao.entity.ZtYwFwqsearch;
|
|
||||||
import com.sa.zentao.service.IZtYwFwqsearchService;
|
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
|
||||||
import org.springframework.stereotype.Component;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 系统运维监控(运维 0.15):每周2次,缺一次扣3分;实做 = zt_yw_fwqsearch 当月记录数。
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Component
|
|
||||||
public class OpsMonitorCalculator extends AbstractOpsCountCalculator {
|
|
||||||
|
|
||||||
@Autowired
|
|
||||||
private IZtYwFwqsearchService fwqsearchService;
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String key() {
|
|
||||||
return "opsMonitor";
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected long actualCount(String account, String month) {
|
|
||||||
return fwqsearchService.count(new QueryWrapper<ZtYwFwqsearch>().lambda()
|
|
||||||
.eq(ZtYwFwqsearch::getCreateUser, account)
|
|
||||||
.ge(ZtYwFwqsearch::getCreateDate, firstDayOfMonth(month))
|
|
||||||
.le(ZtYwFwqsearch::getCreateDate, lastDayOfMonth(month)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int defaultTimesPerWeek() {
|
|
||||||
return 2;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected int defaultDeductPerMissing() {
|
|
||||||
return 3;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,37 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 绩效指标计算器(三期,策略模式)。
|
|
||||||
* 每个实现对应 zt_perf_config.item_key 一个指标;规则参数从 zt_perf_config.rule_json 读取。
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
public interface PerfIndicatorCalculator {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 指标编码,与 zt_perf_config.item_key 对应
|
|
||||||
*/
|
|
||||||
String key();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 计算某账号某月的指标值。
|
|
||||||
* 命中启用配置时返回该项得分(0~100,项内扣分扣完截止);
|
|
||||||
* 无配置/规则类型为 manual 以外的兜底场景返回原始值(如比率%、工时、次数)。
|
|
||||||
*
|
|
||||||
* @param account 账号(zt_user.account)
|
|
||||||
* @param month 月份 yyyy-MM
|
|
||||||
* @return 得分或原始值,永不返回 null
|
|
||||||
*/
|
|
||||||
BigDecimal compute(String account, String month);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CHG-041 消费并清除上一次 compute 的过程值明细(分子/分母/率),无则返回 null。
|
|
||||||
* 默认实现返回 null(非 AbstractPerfIndicatorCalculator 体系不受影响)。
|
|
||||||
*/
|
|
||||||
default String consumeRawDetail() {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,101 +0,0 @@
|
|||||||
package com.sa.zentao.service.perf;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
import java.io.Serializable;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* zt_month_score.scopeJson 统一结构(三期批2 约定,引擎与接口共用):
|
|
||||||
* {"month":"yyyy-MM","role":"backend","account":"zhangsan","total":92.5,
|
|
||||||
* "items":[{"itemKey":"bugDensity","weight":0.3,"raw":0.12,"score":82,"source":"auto"}],
|
|
||||||
* "bonus":[...],"appeals":[...]}
|
|
||||||
*
|
|
||||||
* @author ai
|
|
||||||
* @since 2026-07-28
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class PerfMonthScope implements Serializable {
|
|
||||||
|
|
||||||
private static final long serialVersionUID = 1L;
|
|
||||||
|
|
||||||
private String month;
|
|
||||||
|
|
||||||
private String role;
|
|
||||||
|
|
||||||
private String account;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 总分 = Σ(各项得分×权重) + Σ加分,保留 1 位小数
|
|
||||||
*/
|
|
||||||
private Double total;
|
|
||||||
|
|
||||||
private List<Item> items = new ArrayList<>();
|
|
||||||
|
|
||||||
private List<Bonus> bonus = new ArrayList<>();
|
|
||||||
|
|
||||||
private List<Appeal> appeals = new ArrayList<>();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 单项得分。score 为 null 表示等待人工录入(manual 缺省)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public static class Item implements Serializable {
|
|
||||||
private String itemKey;
|
|
||||||
private String itemName;
|
|
||||||
private Double weight;
|
|
||||||
/**
|
|
||||||
* 指标原始值(如完成率 92.5、Bug 密度 16.0)
|
|
||||||
*/
|
|
||||||
private Double raw;
|
|
||||||
/**
|
|
||||||
* CHG-041 指标过程值明细(分子/分母/率,如 "Σ指数314.03 / 达标105h = 299.0%"),弹窗「绩效数据」列展示用
|
|
||||||
*/
|
|
||||||
private String rawDetail;
|
|
||||||
/**
|
|
||||||
* 项内得分 0~100
|
|
||||||
*/
|
|
||||||
private Double score;
|
|
||||||
/**
|
|
||||||
* 项内扣分(满分 - 得分)
|
|
||||||
*/
|
|
||||||
private Double deduct;
|
|
||||||
/**
|
|
||||||
* 数据来源:auto 系统计算 / manual 人工录入
|
|
||||||
*/
|
|
||||||
private String source;
|
|
||||||
private String remark;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 加分/返还记录
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public static class Bonus implements Serializable {
|
|
||||||
private String itemKey;
|
|
||||||
private String name;
|
|
||||||
private Double points;
|
|
||||||
/**
|
|
||||||
* bonus 加分项 / refund 返还
|
|
||||||
*/
|
|
||||||
private String type;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 申诉记录(随 scopeJson 承载,id 规则:account@month#序号)
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public static class Appeal implements Serializable {
|
|
||||||
private String id;
|
|
||||||
private String itemKey;
|
|
||||||
private String reason;
|
|
||||||
/**
|
|
||||||
* pending 待复核 / upheld 维持原判 / overturned 撤销回滚
|
|
||||||
*/
|
|
||||||
private String status;
|
|
||||||
private String reviewer;
|
|
||||||
private String createDate;
|
|
||||||
private String reviewDate;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user