feat(ai-report): 新增委托单位管理和报告审批功能

- 新增委托单位管理模块,支持增删改查、导入导出功能
- 实现委托单位 Excel 模板下载和数据导入功能
- 添加报告审批抽象处理器,支持审批流程日志记录
- 统一文件存储路径配置,将各模块独立路径改为统一根目录
- 新增 PQDIF 文件名存储字段,完善文件管理功能
- 修复代码中的乱码问题和错误提示信息
- 优化系统常量定义和字典配置管理
This commit is contained in:
2026-07-01 13:15:57 +08:00
parent 0ea686d3cb
commit c1ad7feec2
99 changed files with 6511 additions and 24 deletions

View File

@@ -11,8 +11,21 @@
</parent>
<artifactId>test-report</artifactId>
<packaging>jar</packaging>
<dependencies>
<dependency>
<groupId>com.njcn.gather</groupId>
<artifactId>comservice</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.njcn.gather</groupId>
<artifactId>report-model</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>com.njcn</groupId>
<artifactId>njcn-common</artifactId>
@@ -30,5 +43,27 @@
<artifactId>spingboot2.3.12</artifactId>
<version>2.3.12</version>
</dependency>
<dependency>
<groupId>com.njcn.gather</groupId>
<artifactId>system</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,111 @@
package com.njcn.gather.aireport.testreport.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.common.pojo.annotation.OperateInfo;
import com.njcn.common.pojo.constant.OperateType;
import com.njcn.common.pojo.enums.common.LogEnum;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.response.HttpResult;
import com.njcn.gather.aireport.testreport.pojo.param.TestReportParam;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportVO;
import com.njcn.gather.aireport.testreport.service.TestReportService;
import com.njcn.web.controller.BaseController;
import com.njcn.web.utils.HttpResultUtil;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
@Api(tags = "普测报告管理")
@RestController
@RequestMapping("/api/test-report")
@RequiredArgsConstructor
public class TestReportController extends BaseController {
private final TestReportService testReportService;
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("查询普测报告列表")
@PostMapping("/list")
public HttpResult<Page<TestReportVO>> list(@RequestBody(required = false) TestReportParam.QueryParam param) {
String methodDescribe = getMethodDescribe("list");
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,
testReportService.listTestReports(param), methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.ADD)
@ApiOperation("新增普测报告")
@PostMapping("/add")
public HttpResult<Boolean> add(@RequestBody @Validated TestReportParam.AddParam param) {
String methodDescribe = getMethodDescribe("add");
boolean result = testReportService.addTestReport(param);
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL,
result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
@ApiOperation("编辑普测报告")
@PostMapping("/update")
public HttpResult<Boolean> update(@RequestBody @Validated TestReportParam.UpdateParam param) {
String methodDescribe = getMethodDescribe("update");
boolean result = testReportService.updateTestReport(param);
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL,
result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.DELETE)
@ApiOperation("删除普测报告")
@PostMapping("/delete")
public HttpResult<Boolean> delete(@RequestBody List<String> ids) {
String methodDescribe = getMethodDescribe("delete");
boolean result = testReportService.deleteTestReports(ids);
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL,
result, methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("查询普测报告详情")
@ApiImplicitParam(name = "id", value = "普测报告ID", required = true)
@GetMapping("/{id}")
public HttpResult<TestReportVO> detail(@PathVariable("id") String id) {
String methodDescribe = getMethodDescribe("detail");
return HttpResultUtil.assembleCommonResponseResult(CommonResponseEnum.SUCCESS,
testReportService.getTestReport(id), methodDescribe);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("预览普测报告关联模板文件")
@ApiImplicitParam(name = "id", value = "普测报告ID", required = true)
@GetMapping("/{id}/preview")
public ResponseEntity<byte[]> preview(@PathVariable("id") String id) {
return testReportService.previewTemplate(id);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON)
@ApiOperation("导出普测报告")
@GetMapping("/export")
public void export(@ModelAttribute TestReportParam.QueryParam param) {
testReportService.exportTestReports(param);
}
@OperateInfo(info = LogEnum.BUSINESS_COMMON, operateType = OperateType.UPDATE)
@ApiOperation("提交普测报告审核")
@PostMapping("/submit-audit")
public HttpResult<Boolean> submitAudit(@RequestBody @Validated TestReportParam.SubmitAuditParam param) {
String methodDescribe = getMethodDescribe("submitAudit");
boolean result = testReportService.submitAudit(param);
return HttpResultUtil.assembleCommonResponseResult(result ? CommonResponseEnum.SUCCESS : CommonResponseEnum.FAIL,
result, methodDescribe);
}
}

View File

@@ -0,0 +1,72 @@
package com.njcn.gather.aireport.testreport.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.njcn.gather.aireport.testreport.pojo.param.TestReportParam;
import com.njcn.gather.aireport.testreport.pojo.po.TestReportPO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface TestReportMapper extends BaseMapper<TestReportPO> {
@Select({
"<script>",
"select r.id, r.no, r.client_unit_id as clientUnitId, cu.name as clientUnitName,",
"r.report_model_id as reportModelId, rm.name as reportModelName, r.create_unit as createUnit,",
"r.contract_number as contractNumber, r.standard, r.data, r.phonenumber, r.check_id as checkId,",
"checker.name as checkerName, r.check_time as checkTime, r.check_result as checkResult, r.state,",
"r.status, r.create_by as createBy, creator.name as createByName, r.create_time as createTime,",
"r.update_by as updateBy, updater.name as updateByName, r.update_time as updateTime",
"from ai_testreport r",
"left join ai_clientunit cu on r.client_unit_id = cu.id",
"left join ai_reportmodel rm on r.report_model_id = rm.id",
"left join sys_user creator on r.create_by = creator.id",
"left join sys_user updater on r.update_by = updater.id",
"left join sys_user checker on r.check_id = checker.id",
"where r.status = 1",
"<if test='param.keyword != null and param.keyword != \"\"'>",
"and (r.no like concat('%', #{param.keyword}, '%')",
"or r.contract_number like concat('%', #{param.keyword}, '%')",
"or cu.name like concat('%', #{param.keyword}, '%')",
"or rm.name like concat('%', #{param.keyword}, '%'))",
"</if>",
"<if test='param.searchBeginTime != null and param.searchBeginTime != \"\"'>",
"and r.create_time &gt;= #{param.searchBeginTime}",
"</if>",
"<if test='param.searchEndTime != null and param.searchEndTime != \"\"'>",
"and r.create_time &lt;= #{param.searchEndTime}",
"</if>",
"<if test='param.state != null and param.state != \"\"'>",
"and r.state = #{param.state}",
"</if>",
"order by r.create_time desc",
"</script>"
})
Page<TestReportVO> selectReportPage(Page<TestReportVO> page, @Param("param") TestReportParam.QueryParam param);
@Select({
"select r.id, r.no, r.client_unit_id as clientUnitId, cu.name as clientUnitName,",
"r.report_model_id as reportModelId, rm.name as reportModelName, r.create_unit as createUnit,",
"r.contract_number as contractNumber, r.standard, r.data, r.phonenumber, r.check_id as checkId,",
"checker.name as checkerName, r.check_time as checkTime, r.check_result as checkResult, r.state,",
"r.status, r.create_by as createBy, creator.name as createByName, r.create_time as createTime,",
"r.update_by as updateBy, updater.name as updateByName, r.update_time as updateTime",
"from ai_testreport r",
"left join ai_clientunit cu on r.client_unit_id = cu.id",
"left join ai_reportmodel rm on r.report_model_id = rm.id",
"left join sys_user creator on r.create_by = creator.id",
"left join sys_user updater on r.update_by = updater.id",
"left join sys_user checker on r.check_id = checker.id",
"where r.id = #{id} and r.status = 1"
})
TestReportVO selectReportDetail(@Param("id") String id);
@Select("select count(1) from ai_clientunit where id = #{id} and status = 1")
int countNormalClientUnit(@Param("id") String id);
@Select("select count(1) from ai_reportmodel where id = #{id} and status = 1")
int countNormalReportModel(@Param("id") String id);
}

View File

@@ -0,0 +1,16 @@
package com.njcn.gather.aireport.testreport.pojo.constant;
public final class TestReportConst {
public static final String STATE_EDITING = "01";
public static final String STATE_AUDITING = "02";
public static final String STATE_APPROVED = "03";
public static final String STATE_REJECTED = "04";
public static final String STATE_DELETED = "05";
public static final Integer STATUS_DELETED = 0;
public static final Integer STATUS_NORMAL = 1;
private TestReportConst() {
}
}

View File

@@ -0,0 +1,83 @@
package com.njcn.gather.aireport.testreport.pojo.param;
import com.njcn.web.pojo.param.BaseParam;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.EqualsAndHashCode;
import javax.validation.constraints.NotBlank;
import javax.validation.constraints.Size;
public class TestReportParam {
@Data
@EqualsAndHashCode(callSuper = true)
public static class QueryParam extends BaseParam {
@ApiModelProperty("\u5173\u952e\u5b57\uff0c\u5339\u914d\u62a5\u544a\u7f16\u53f7\u3001\u5408\u540c\u7f16\u53f7\u3001\u59d4\u6258\u5355\u4f4d\u6216\u62a5\u544a\u6a21\u677f")
private String keyword;
@ApiModelProperty("\u521b\u5efa\u5f00\u59cb\u65f6\u95f4\uff0cyyyy-MM-dd HH:mm:ss")
private String searchBeginTime;
@ApiModelProperty("\u521b\u5efa\u7ed3\u675f\u65f6\u95f4\uff0cyyyy-MM-dd HH:mm:ss")
private String searchEndTime;
@ApiModelProperty("\u62a5\u544a\u72b6\u6001")
private String state;
}
@Data
public static class AddParam {
@ApiModelProperty("\u666e\u6d4b\u62a5\u544a\u7f16\u53f7")
@NotBlank(message = "\u666e\u6d4b\u62a5\u544a\u7f16\u53f7\u4e0d\u80fd\u4e3a\u7a7a")
@Size(max = 32, message = "\u666e\u6d4b\u62a5\u544a\u7f16\u53f7\u4e0d\u80fd\u8d85\u8fc732\u4e2a\u5b57\u7b26")
private String no;
@ApiModelProperty("\u59d4\u6258\u5355\u4f4dID")
@NotBlank(message = "\u59d4\u6258\u5355\u4f4d\u4e0d\u80fd\u4e3a\u7a7a")
private String clientUnitId;
@ApiModelProperty("\u62a5\u544a\u6a21\u677fID")
@NotBlank(message = "\u62a5\u544a\u6a21\u677f\u4e0d\u80fd\u4e3a\u7a7a")
private String reportModelId;
@ApiModelProperty("\u68c0\u6d4b\u516c\u53f8ID")
@NotBlank(message = "\u68c0\u6d4b\u516c\u53f8\u4e0d\u80fd\u4e3a\u7a7a")
private String createUnit;
@ApiModelProperty("\u5408\u540c\u7f16\u53f7")
@NotBlank(message = "\u5408\u540c\u7f16\u53f7\u4e0d\u80fd\u4e3a\u7a7a")
@Size(max = 32, message = "\u5408\u540c\u7f16\u53f7\u4e0d\u80fd\u8d85\u8fc732\u4e2a\u5b57\u7b26")
private String contractNumber;
@ApiModelProperty("\u68c0\u6d4b\u4f9d\u636e\uff0cJSON\u5b57\u7b26\u4e32\u6570\u7ec4")
@NotBlank(message = "\u68c0\u6d4b\u4f9d\u636e\u4e0d\u80fd\u4e3a\u7a7a")
private String standard;
@ApiModelProperty("\u53d7\u8d44\u6570\u636e\uff0cJSON\u5bf9\u8c61\u6216JSON\u6570\u7ec4")
@NotBlank(message = "\u53d7\u8d44\u6570\u636e\u4e0d\u80fd\u4e3a\u7a7a")
private String data;
@ApiModelProperty("\u8054\u7cfb\u7535\u8bdd")
@Size(max = 32, message = "\u8054\u7cfb\u7535\u8bdd\u4e0d\u80fd\u8d85\u8fc732\u4e2a\u5b57\u7b26")
private String phonenumber;
}
@Data
public static class UpdateParam extends AddParam {
@ApiModelProperty("\u666e\u6d4b\u62a5\u544aID")
@NotBlank(message = "\u666e\u6d4b\u62a5\u544aID\u4e0d\u80fd\u4e3a\u7a7a")
private String id;
}
@Data
public static class SubmitAuditParam {
@ApiModelProperty("\u666e\u6d4b\u62a5\u544aID")
@NotBlank(message = "\u666e\u6d4b\u62a5\u544aID\u4e0d\u80fd\u4e3a\u7a7a")
private String id;
@ApiModelProperty("\u5ba1\u6838\u610f\u89c1")
@Size(max = 128, message = "\u5ba1\u6838\u610f\u89c1\u4e0d\u80fd\u8d85\u8fc7128\u4e2a\u5b57\u7b26")
private String checkResult;
}
}

View File

@@ -0,0 +1,53 @@
package com.njcn.gather.aireport.testreport.pojo.po;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
@TableName("ai_testreport")
public class TestReportPO implements Serializable {
private static final long serialVersionUID = -3038286126414044160L;
@TableId("id")
private String id;
@TableField("no")
private String no;
@TableField("client_unit_id")
private String clientUnitId;
@TableField("report_model_id")
private String reportModelId;
@TableField("create_unit")
private String createUnit;
@TableField("contract_number")
private String contractNumber;
@TableField("standard")
private String standard;
@TableField("data")
private String data;
@TableField("phonenumber")
private String phonenumber;
@TableField("state")
private String state;
@TableField("check_id")
private String checkId;
@TableField("check_time")
private LocalDate checkTime;
@TableField("check_result")
private String checkResult;
@TableField("status")
private Integer status;
@TableField("create_by")
private String createBy;
@TableField("create_time")
private LocalDateTime createTime;
@TableField("update_by")
private String updateBy;
@TableField("update_time")
private LocalDateTime updateTime;
}

View File

@@ -0,0 +1,53 @@
package com.njcn.gather.aireport.testreport.pojo.vo;
import cn.afterturn.easypoi.excel.annotation.Excel;
import lombok.Data;
import java.io.Serializable;
@Data
public class TestReportExcelVO implements Serializable {
private static final long serialVersionUID = -3010688465572795334L;
@Excel(name = "\u666e\u6d4b\u62a5\u544a\u7f16\u53f7", width = 25)
private String no;
@Excel(name = "\u59d4\u6258\u5355\u4f4d", width = 25)
private String clientUnitName;
@Excel(name = "\u62a5\u544a\u6a21\u677f", width = 25)
private String reportModelName;
@Excel(name = "\u68c0\u6d4b\u516c\u53f8", width = 20)
private String createUnit;
@Excel(name = "\u5408\u540c\u7f16\u53f7", width = 20)
private String contractNumber;
@Excel(name = "\u68c0\u6d4b\u4f9d\u636e", width = 35)
private String standard;
@Excel(name = "\u53d7\u8d44\u6570\u636e", width = 35)
private String data;
@Excel(name = "\u8054\u7cfb\u7535\u8bdd", width = 20)
private String phonenumber;
@Excel(name = "\u5ba1\u6838\u4eba", width = 20)
private String checkerName;
@Excel(name = "\u5ba1\u6838\u65f6\u95f4", width = 20)
private String checkTime;
@Excel(name = "\u5ba1\u6838\u610f\u89c1", width = 30)
private String checkResult;
@Excel(name = "\u62a5\u544a\u72b6\u6001", width = 15)
private String state;
@Excel(name = "\u521b\u5efa\u65f6\u95f4", width = 25)
private String createTime;
@Excel(name = "\u521b\u5efa\u4eba", width = 20)
private String createByName;
}

View File

@@ -0,0 +1,156 @@
package com.njcn.gather.aireport.testreport.pojo.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateDeserializer;
import com.fasterxml.jackson.datatype.jsr310.deser.LocalDateTimeDeserializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateSerializer;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import org.springframework.util.StringUtils;
import java.time.LocalDate;
import java.time.LocalDateTime;
@Data
public class TestReportVO {
private static final String DEFAULT_EMPTY_DISPLAY = "-";
@ApiModelProperty("\u666e\u6d4b\u62a5\u544aID")
private String id;
@ApiModelProperty("\u666e\u6d4b\u62a5\u544a\u7f16\u53f7")
private String no;
@ApiModelProperty("\u59d4\u6258\u5355\u4f4dID")
private String clientUnitId;
@ApiModelProperty("\u59d4\u6258\u5355\u4f4d\u540d\u79f0")
private String clientUnitName;
@ApiModelProperty("\u62a5\u544a\u6a21\u677fID")
private String reportModelId;
@ApiModelProperty("\u62a5\u544a\u6a21\u677f\u540d\u79f0")
private String reportModelName;
@ApiModelProperty("\u68c0\u6d4b\u516c\u53f8ID")
private String createUnit;
@ApiModelProperty("\u5408\u540c\u7f16\u53f7")
private String contractNumber;
@ApiModelProperty("\u68c0\u6d4b\u4f9d\u636e")
private String standard;
@ApiModelProperty("\u53d7\u8d44\u6570\u636e")
private String data;
@ApiModelProperty("\u8054\u7cfb\u7535\u8bdd")
private String phonenumber;
@ApiModelProperty("\u5ba1\u6838\u4ebaID")
private String checkId;
@ApiModelProperty("\u5ba1\u6838\u4eba\u540d\u79f0")
private String checkerName;
@ApiModelProperty("\u5ba1\u6838\u65f6\u95f4")
@JsonFormat(pattern = "yyyy-MM-dd")
@JsonDeserialize(using = LocalDateDeserializer.class)
@JsonSerialize(using = LocalDateSerializer.class)
private LocalDate checkTime;
@ApiModelProperty("\u5ba1\u6838\u610f\u89c1")
private String checkResult;
@ApiModelProperty("\u62a5\u544a\u72b6\u6001")
private String state;
@ApiModelProperty("\u903b\u8f91\u72b6\u6001")
private Integer status;
@ApiModelProperty("\u521b\u5efa\u4eba\u663e\u793a\u540d")
private String createBy;
@ApiModelProperty("\u521b\u5efa\u4eba\u540d\u79f0")
private String createByName;
@ApiModelProperty("\u521b\u5efa\u65f6\u95f4")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime createTime;
@ApiModelProperty("\u66f4\u65b0\u4eba\u663e\u793a\u540d")
private String updateBy;
@ApiModelProperty("\u66f4\u65b0\u4eba\u540d\u79f0")
private String updateByName;
@ApiModelProperty("\u66f4\u65b0\u65f6\u95f4")
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@JsonDeserialize(using = LocalDateTimeDeserializer.class)
@JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime updateTime;
public String getId() {
return defaultDisplay(id);
}
public String getNo() {
return defaultDisplay(no);
}
public String getClientUnitId() {
return defaultDisplay(clientUnitId);
}
public String getClientUnitName() {
return defaultDisplay(clientUnitName);
}
public String getReportModelId() {
return defaultDisplay(reportModelId);
}
public String getReportModelName() {
return defaultDisplay(reportModelName);
}
public String getCreateUnit() {
return defaultDisplay(createUnit);
}
public String getContractNumber() {
return defaultDisplay(contractNumber);
}
public String getStandard() {
return defaultDisplay(standard);
}
public String getData() {
return defaultDisplay(data);
}
public String getPhonenumber() {
return defaultDisplay(phonenumber);
}
public String getCheckId() {
return defaultDisplay(checkId);
}
public String getCheckerName() {
return defaultDisplay(checkerName);
}
public String getCheckResult() {
return defaultDisplay(checkResult);
}
public String getState() {
return defaultDisplay(state);
}
public String getCreateBy() {
return defaultDisplay(createBy);
}
public String getCreateByName() {
return defaultDisplay(createByName);
}
public String getUpdateBy() {
return defaultDisplay(updateBy);
}
public String getUpdateByName() {
return defaultDisplay(updateByName);
}
private String defaultDisplay(String value) {
return StringUtils.hasText(value) ? value : DEFAULT_EMPTY_DISPLAY;
}
}

View File

@@ -0,0 +1,31 @@
package com.njcn.gather.aireport.testreport.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.IService;
import com.njcn.gather.aireport.testreport.pojo.param.TestReportParam;
import com.njcn.gather.aireport.testreport.pojo.po.TestReportPO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportVO;
import org.springframework.http.ResponseEntity;
import java.util.List;
public interface TestReportService extends IService<TestReportPO> {
Page<TestReportVO> listTestReports(TestReportParam.QueryParam param);
boolean addTestReport(TestReportParam.AddParam param);
boolean updateTestReport(TestReportParam.UpdateParam param);
boolean deleteTestReports(List<String> ids);
TestReportVO getTestReport(String id);
TestReportPO requireNormal(String id);
boolean submitAudit(TestReportParam.SubmitAuditParam param);
void exportTestReports(TestReportParam.QueryParam param);
ResponseEntity<byte[]> previewTemplate(String id);
}

View File

@@ -0,0 +1,621 @@
package com.njcn.gather.aireport.testreport.service.impl;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.njcn.common.pojo.enums.response.CommonResponseEnum;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.aireport.reportmodel.component.ReportModelFileStorageService;
import com.njcn.gather.aireport.reportmodel.pojo.po.ReportModelPO;
import com.njcn.gather.aireport.reportmodel.service.ReportModelService;
import com.njcn.gather.aireport.testreport.mapper.TestReportMapper;
import com.njcn.gather.aireport.testreport.pojo.constant.TestReportConst;
import com.njcn.gather.aireport.testreport.pojo.param.TestReportParam;
import com.njcn.gather.aireport.testreport.pojo.po.TestReportPO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportExcelVO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportVO;
import com.njcn.gather.aireport.testreport.service.TestReportService;
import com.njcn.gather.comservice.filepreview.FilePreviewService;
import com.njcn.gather.system.dictionary.pojo.po.DictData;
import com.njcn.gather.system.dictionary.pojo.po.DictType;
import com.njcn.gather.system.dictionary.service.IDictDataService;
import com.njcn.gather.system.dictionary.service.IDictTypeService;
import com.njcn.gather.system.pojo.constant.DictConst;
import com.njcn.gather.system.util.ExportFileNameUtil;
import com.njcn.web.factory.PageFactory;
import com.njcn.web.utils.ExcelUtil;
import com.njcn.web.utils.RequestUtil;
import lombok.Data;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.stream.Collectors;
@Service
public class TestReportServiceImpl extends ServiceImpl<TestReportMapper, TestReportPO> implements TestReportService {
private static final DateTimeFormatter DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
private static final Integer DICT_STATE_NORMAL = 1;
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final String MSG_ID_REQUIRED = "\u666e\u6d4b\u62a5\u544aID\u4e0d\u80fd\u4e3a\u7a7a";
private static final String MSG_REPORT_NOT_FOUND = "\u666e\u6d4b\u62a5\u544a\u4e0d\u5b58\u5728\u6216\u5df2\u5220\u9664";
private static final String MSG_ONLY_EDITABLE_CAN_UPDATE =
"\u4ec5\u7f16\u5236\u6216\u9000\u56de\u72b6\u6001\u7684\u666e\u6d4b\u62a5\u544a\u5141\u8bb8\u7f16\u8f91";
private static final String MSG_ONLY_EDITABLE_CAN_AUDIT =
"\u4ec5\u7f16\u5236\u6216\u9000\u56de\u72b6\u6001\u7684\u666e\u6d4b\u62a5\u544a\u5141\u8bb8\u63d0\u4ea4\u5ba1\u6838";
private static final String MSG_START_TIME_INVALID = "\u5f00\u59cb\u65f6\u95f4\u683c\u5f0f\u4e0d\u6b63\u786e";
private static final String MSG_END_TIME_INVALID = "\u7ed3\u675f\u65f6\u95f4\u683c\u5f0f\u4e0d\u6b63\u786e";
private static final String MSG_PARAM_REQUIRED = "\u666e\u6d4b\u62a5\u544a\u53c2\u6570\u4e0d\u80fd\u4e3a\u7a7a";
private static final String MSG_NO_REQUIRED = "\u666e\u6d4b\u62a5\u544a\u7f16\u53f7\u4e0d\u80fd\u4e3a\u7a7a";
private static final String MSG_NO_TOO_LONG = "\u666e\u6d4b\u62a5\u544a\u7f16\u53f7\u4e0d\u80fd\u8d85\u8fc732\u4e2a\u5b57\u7b26";
private static final String MSG_CLIENT_UNIT_REQUIRED = "\u59d4\u6258\u5355\u4f4d\u4e0d\u80fd\u4e3a\u7a7a";
private static final String MSG_REPORT_MODEL_REQUIRED = "\u62a5\u544a\u6a21\u677f\u4e0d\u80fd\u4e3a\u7a7a";
private static final String MSG_CREATE_UNIT_REQUIRED = "\u68c0\u6d4b\u516c\u53f8\u4e0d\u80fd\u4e3a\u7a7a";
private static final String MSG_CONTRACT_REQUIRED = "\u5408\u540c\u7f16\u53f7\u4e0d\u80fd\u4e3a\u7a7a";
private static final String MSG_CONTRACT_TOO_LONG = "\u5408\u540c\u7f16\u53f7\u4e0d\u80fd\u8d85\u8fc732\u4e2a\u5b57\u7b26";
private static final String MSG_STANDARD_REQUIRED = "\u68c0\u6d4b\u4f9d\u636e\u4e0d\u80fd\u4e3a\u7a7a";
private static final String MSG_DATA_REQUIRED = "\u53d7\u8d44\u6570\u636e\u4e0d\u80fd\u4e3a\u7a7a";
private static final String MSG_DATA_INVALID = "\u53d7\u8d44\u6570\u636e\u683c\u5f0f\u4e0d\u6b63\u786e";
private static final String MSG_PHONE_TOO_LONG = "\u8054\u7cfb\u7535\u8bdd\u4e0d\u80fd\u8d85\u8fc732\u4e2a\u5b57\u7b26";
private static final String MSG_NO_EXISTS = "\u666e\u6d4b\u62a5\u544a\u7f16\u53f7\u5df2\u5b58\u5728";
private static final String MSG_COMPANY_NOT_FOUND = "\u68c0\u6d4b\u516c\u53f8\u4e0d\u5b58\u5728\u6216\u5df2\u5220\u9664";
private static final String MSG_STANDARD_INVALID = "\u6d4b\u8bd5\u4f9d\u636e\u683c\u5f0f\u4e0d\u6b63\u786e";
private static final String MSG_STANDARD_NOT_FOUND = "\u6d4b\u8bd5\u4f9d\u636e\u4e0d\u5b58\u5728\u6216\u5df2\u5220\u9664";
private static final String MSG_CLIENT_UNIT_NOT_FOUND = "\u59d4\u6258\u5355\u4f4d\u4e0d\u5b58\u5728\u6216\u5df2\u5220\u9664";
private static final String MSG_REPORT_MODEL_NOT_FOUND = "\u62a5\u544a\u6a21\u677f\u4e0d\u5b58\u5728\u6216\u5df2\u5220\u9664";
private static final String MSG_PREVIEW_NOT_READY = "\u62a5\u544a\u6a21\u677f\u9884\u89c8\u670d\u52a1\u672a\u521d\u59cb\u5316";
private static final String MSG_TEMPLATE_FILE_NOT_FOUND = "\u62a5\u544a\u6a21\u677f\u6587\u4ef6\u4e0d\u5b58\u5728";
private static final String MSG_ENCODE_FILE_NAME_FAILED = "\u7f16\u7801\u62a5\u544a\u6a21\u677f\u6587\u4ef6\u540d\u5931\u8d25";
private final ReportModelService reportModelService;
private final ReportModelFileStorageService reportModelFileStorageService;
private final FilePreviewService filePreviewService;
private final IDictTypeService dictTypeService;
private final IDictDataService dictDataService;
public TestReportServiceImpl(ReportModelService reportModelService,
ReportModelFileStorageService reportModelFileStorageService,
FilePreviewService filePreviewService,
IDictTypeService dictTypeService,
IDictDataService dictDataService) {
this.reportModelService = reportModelService;
this.reportModelFileStorageService = reportModelFileStorageService;
this.filePreviewService = filePreviewService;
this.dictTypeService = dictTypeService;
this.dictDataService = dictDataService;
}
@Override
public Page<TestReportVO> listTestReports(TestReportParam.QueryParam param) {
TestReportParam.QueryParam query = normalizeQueryParam(param);
Page<TestReportVO> page = this.baseMapper.selectReportPage(
new Page<TestReportVO>(PageFactory.getPageNum(query), PageFactory.getPageSize(query)), query);
fillDisplayUserNames(page.getRecords(), buildUserNameMap(page.getRecords()));
fillDisplayFields(page.getRecords());
return page;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean addTestReport(TestReportParam.AddParam param) {
NormalizedReport data = normalizeParam(param);
checkNoUnique(data.getNo(), null);
checkClientUnit(data.getClientUnitId());
checkReportModel(data.getReportModelId());
checkCreateUnit(data.getCreateUnit());
checkStandard(data.getStandard());
LocalDateTime now = LocalDateTime.now();
String userId = resolveCurrentUserId();
TestReportPO report = new TestReportPO();
report.setId(UUID.randomUUID().toString());
applyBusinessFields(report, data);
report.setState(TestReportConst.STATE_EDITING);
report.setStatus(TestReportConst.STATUS_NORMAL);
report.setCreateBy(userId);
report.setCreateTime(now);
report.setUpdateBy(userId);
report.setUpdateTime(now);
return this.save(report);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateTestReport(TestReportParam.UpdateParam param) {
if (param == null || StrUtil.isBlank(param.getId())) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_ID_REQUIRED);
}
TestReportPO report = requireNormal(param.getId());
if (!allowEdit(report.getState())) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_ONLY_EDITABLE_CAN_UPDATE);
}
NormalizedReport data = normalizeParam(param);
checkNoUnique(data.getNo(), report.getId());
checkClientUnit(data.getClientUnitId());
checkReportModel(data.getReportModelId());
checkCreateUnit(data.getCreateUnit());
checkStandard(data.getStandard());
applyBusinessFields(report, data);
report.setUpdateBy(resolveCurrentUserId());
report.setUpdateTime(LocalDateTime.now());
return this.updateById(report);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteTestReports(List<String> ids) {
List<String> validIds = normalizeIds(ids);
if (validIds.isEmpty()) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_ID_REQUIRED);
}
return this.lambdaUpdate()
.set(TestReportPO::getState, TestReportConst.STATE_DELETED)
.set(TestReportPO::getStatus, TestReportConst.STATUS_DELETED)
.set(TestReportPO::getUpdateBy, resolveCurrentUserId())
.set(TestReportPO::getUpdateTime, LocalDateTime.now())
.in(TestReportPO::getId, validIds)
.eq(TestReportPO::getStatus, TestReportConst.STATUS_NORMAL)
.update();
}
@Override
public TestReportVO getTestReport(String id) {
TestReportVO report = this.baseMapper.selectReportDetail(requireText(id, MSG_ID_REQUIRED));
if (report == null) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_REPORT_NOT_FOUND);
}
fillDisplayUserNames(report, buildUserNameMap(Collections.singletonList(report)));
fillDisplayFields(Collections.singletonList(report));
return report;
}
@Override
public TestReportPO requireNormal(String id) {
String normalizedId = requireText(id, MSG_ID_REQUIRED);
TestReportPO report = this.lambdaQuery()
.eq(TestReportPO::getId, normalizedId)
.eq(TestReportPO::getStatus, TestReportConst.STATUS_NORMAL)
.one();
if (report == null) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_REPORT_NOT_FOUND);
}
return report;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean submitAudit(TestReportParam.SubmitAuditParam param) {
if (param == null || StrUtil.isBlank(param.getId())) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_ID_REQUIRED);
}
TestReportPO report = requireNormal(param.getId());
if (!allowEdit(report.getState())) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_ONLY_EDITABLE_CAN_AUDIT);
}
report.setCheckId(null);
report.setCheckTime(null);
report.setCheckResult(null);
report.setState(TestReportConst.STATE_AUDITING);
report.setUpdateBy(resolveCurrentUserId());
report.setUpdateTime(LocalDateTime.now());
return this.updateById(report);
}
@Override
public void exportTestReports(TestReportParam.QueryParam param) {
TestReportParam.QueryParam query = normalizeQueryParam(param);
Page<TestReportVO> page = this.baseMapper.selectReportPage(new Page<TestReportVO>(1, Integer.MAX_VALUE), query);
fillDisplayUserNames(page.getRecords(), buildUserNameMap(page.getRecords()));
fillDisplayFields(page.getRecords());
List<TestReportExcelVO> exportRows = page.getRecords().stream()
.map(this::toExcelVO)
.collect(Collectors.toList());
ExcelUtil.exportExcel(ExportFileNameUtil.appendToday("\u666e\u6d4b\u62a5\u544a\u5217\u8868.xlsx"),
"\u666e\u6d4b\u62a5\u544a\u5217\u8868", TestReportExcelVO.class, exportRows);
}
@Override
public ResponseEntity<byte[]> previewTemplate(String id) {
TestReportPO report = requireNormal(id);
ReportModelPO model = requireReportModel(report.getReportModelId());
Path filePath = reportModelFileStorageService.resolveDownloadPath(model.getPath());
if (!Files.exists(filePath) || !Files.isRegularFile(filePath)) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_TEMPLATE_FILE_NOT_FOUND);
}
String fileName = resolvePreviewFileName(model);
FilePreviewService.PreviewContent previewContent = filePreviewService.preview(filePath, fileName);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "inline; filename*=UTF-8''" + encodeFileName(fileName))
.header(HttpHeaders.CACHE_CONTROL, "no-cache, no-store, must-revalidate")
.contentType(MediaType.parseMediaType(previewContent.getContentType()))
.body(previewContent.getContent());
}
private TestReportParam.QueryParam normalizeQueryParam(TestReportParam.QueryParam param) {
TestReportParam.QueryParam query = param == null ? new TestReportParam.QueryParam() : param;
query.setKeyword(trimToNull(query.getKeyword()));
if (StrUtil.isNotBlank(query.getSearchBeginTime())) {
query.setSearchBeginTime(formatDateTime(parseDateTime(query.getSearchBeginTime(), MSG_START_TIME_INVALID)));
}
if (StrUtil.isNotBlank(query.getSearchEndTime())) {
query.setSearchEndTime(formatDateTime(parseDateTime(query.getSearchEndTime(), MSG_END_TIME_INVALID)));
}
return query;
}
private NormalizedReport normalizeParam(TestReportParam.AddParam param) {
if (param == null) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_PARAM_REQUIRED);
}
NormalizedReport data = new NormalizedReport();
data.setNo(requireText(param.getNo(), MSG_NO_REQUIRED, 32, MSG_NO_TOO_LONG));
data.setClientUnitId(requireText(param.getClientUnitId(), MSG_CLIENT_UNIT_REQUIRED));
data.setReportModelId(requireText(param.getReportModelId(), MSG_REPORT_MODEL_REQUIRED));
data.setCreateUnit(requireText(param.getCreateUnit(), MSG_CREATE_UNIT_REQUIRED));
data.setContractNumber(requireText(param.getContractNumber(), MSG_CONTRACT_REQUIRED, 32, MSG_CONTRACT_TOO_LONG));
data.setStandard(requireText(param.getStandard(), MSG_STANDARD_REQUIRED));
data.setData(requireJsonObjectOrArrayText(param.getData(), MSG_DATA_REQUIRED, MSG_DATA_INVALID));
data.setPhonenumber(trimToNull(param.getPhonenumber()));
if (data.getPhonenumber() != null && data.getPhonenumber().length() > 32) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_PHONE_TOO_LONG);
}
return data;
}
private void applyBusinessFields(TestReportPO report, NormalizedReport data) {
report.setNo(data.getNo());
report.setClientUnitId(data.getClientUnitId());
report.setReportModelId(data.getReportModelId());
report.setCreateUnit(data.getCreateUnit());
report.setContractNumber(data.getContractNumber());
report.setStandard(data.getStandard());
report.setData(data.getData());
report.setPhonenumber(data.getPhonenumber());
}
private boolean allowEdit(String state) {
return TestReportConst.STATE_EDITING.equals(state) || TestReportConst.STATE_REJECTED.equals(state);
}
private void checkNoUnique(String no, String excludeId) {
LambdaQueryWrapper<TestReportPO> wrapper = new LambdaQueryWrapper<TestReportPO>();
wrapper.eq(TestReportPO::getNo, no)
.eq(TestReportPO::getStatus, TestReportConst.STATUS_NORMAL)
.ne(StrUtil.isNotBlank(excludeId), TestReportPO::getId, excludeId);
if (this.count(wrapper) > 0) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_NO_EXISTS);
}
}
private TestReportExcelVO toExcelVO(TestReportVO vo) {
TestReportExcelVO excelVO = new TestReportExcelVO();
excelVO.setNo(vo.getNo());
excelVO.setClientUnitName(StrUtil.isNotBlank(vo.getClientUnitName()) ? vo.getClientUnitName() : vo.getClientUnitId());
excelVO.setReportModelName(StrUtil.isNotBlank(vo.getReportModelName()) ? vo.getReportModelName() : vo.getReportModelId());
excelVO.setCreateUnit(vo.getCreateUnit());
excelVO.setContractNumber(vo.getContractNumber());
excelVO.setStandard(vo.getStandard());
excelVO.setData(vo.getData());
excelVO.setPhonenumber(vo.getPhonenumber());
excelVO.setCheckerName(StrUtil.isNotBlank(vo.getCheckerName()) ? vo.getCheckerName() : vo.getCheckId());
excelVO.setCheckTime(vo.getCheckTime() == null ? null : vo.getCheckTime().toString());
excelVO.setCheckResult(vo.getCheckResult());
excelVO.setState(vo.getState());
excelVO.setCreateTime(vo.getCreateTime() == null ? null : DATE_TIME_FORMATTER.format(vo.getCreateTime()));
excelVO.setCreateByName(StrUtil.isNotBlank(vo.getCreateByName()) ? vo.getCreateByName() : vo.getCreateBy());
return excelVO;
}
void fillDisplayUserNames(List<TestReportVO> reports, Map<String, String> userNameMap) {
if (reports == null || reports.isEmpty()) {
return;
}
for (TestReportVO report : reports) {
fillDisplayUserNames(report, userNameMap);
}
}
void fillDisplayUserNames(TestReportVO report, Map<String, String> userNameMap) {
if (report == null) {
return;
}
report.setCreateBy(resolveDisplayUserName(report.getCreateBy(), userNameMap));
report.setUpdateBy(resolveDisplayUserName(report.getUpdateBy(), userNameMap));
}
void fillDisplayFields(List<TestReportVO> reports) {
if (reports == null || reports.isEmpty()) {
return;
}
Map<String, String> companyNameMap = buildDictNameMap(DictConst.TEST_REPORT_COMPANY_DICT_CODE);
Map<String, String> standardNameMap = buildDictNameMap(DictConst.TEST_REPORT_STANDARD_DICT_CODE);
for (TestReportVO report : reports) {
if (report == null) {
continue;
}
report.setCreateUnit(resolveDictDisplayValue(report.getCreateUnit(), companyNameMap));
report.setStandard(resolveStandardDisplayValue(report.getStandard(), standardNameMap));
}
}
private Map<String, String> buildUserNameMap(Collection<TestReportVO> reports) {
if (reports == null || reports.isEmpty()) {
return Collections.emptyMap();
}
Map<String, String> userNameMap = new LinkedHashMap<String, String>();
for (TestReportVO report : reports) {
if (report == null) {
continue;
}
putUserName(userNameMap, report.getCreateBy(), report.getCreateByName());
putUserName(userNameMap, report.getUpdateBy(), report.getUpdateByName());
putUserName(userNameMap, report.getCheckId(), report.getCheckerName());
}
return userNameMap;
}
private Map<String, String> buildDictNameMap(String typeCode) {
if (dictTypeService == null || dictDataService == null || StrUtil.isBlank(typeCode)) {
return Collections.emptyMap();
}
DictType dictType = dictTypeService.getByCode(typeCode);
if (dictType == null || StrUtil.isBlank(dictType.getId()) || !DICT_STATE_NORMAL.equals(dictType.getState())) {
return Collections.emptyMap();
}
List<DictData> dictDataList = dictDataService.listDictDataByTypeId(dictType.getId());
if (dictDataList == null || dictDataList.isEmpty()) {
return Collections.emptyMap();
}
Map<String, String> result = new LinkedHashMap<String, String>();
for (DictData dictData : dictDataList) {
if (dictData == null || StrUtil.isBlank(dictData.getId()) || StrUtil.isBlank(dictData.getName())
|| !DICT_STATE_NORMAL.equals(dictData.getState())) {
continue;
}
result.put(dictData.getId(), dictData.getName());
}
return result;
}
private void putUserName(Map<String, String> userNameMap, String userId, String userName) {
String validUserId = trimToNull(userId);
String validUserName = trimToNull(userName);
if (validUserId == null || validUserName == null || "-".equals(validUserName)) {
return;
}
userNameMap.put(validUserId, validUserName);
}
private String resolveDisplayUserName(String userId, Map<String, String> userNameMap) {
String validUserId = trimToNull(userId);
if (validUserId == null) {
return null;
}
if (userNameMap == null || userNameMap.isEmpty()) {
return validUserId;
}
String userName = trimToNull(userNameMap.get(validUserId));
return userName == null ? validUserId : userName;
}
private String resolveDictDisplayValue(String rawValue, Map<String, String> dictNameMap) {
String validValue = trimToNull(rawValue);
if (validValue == null) {
return null;
}
if (dictNameMap == null || dictNameMap.isEmpty()) {
return validValue;
}
String displayValue = trimToNull(dictNameMap.get(validValue));
return displayValue == null ? validValue : displayValue;
}
private String resolveStandardDisplayValue(String rawValue, Map<String, String> dictNameMap) {
String validValue = trimToNull(rawValue);
if (validValue == null) {
return null;
}
List<String> standardIds = parseJsonArray(validValue);
if (standardIds.isEmpty()) {
return validValue;
}
List<String> displayValues = new ArrayList<String>();
for (String standardId : standardIds) {
String displayValue = resolveDictDisplayValue(standardId, dictNameMap);
if (displayValue != null) {
displayValues.add(displayValue);
}
}
if (displayValues.isEmpty()) {
return validValue;
}
return String.join(", ", displayValues);
}
private List<String> parseJsonArray(String jsonArrayText) {
String text = trimToNull(jsonArrayText);
if (text == null) {
return Collections.emptyList();
}
try {
List<String> parsedItems = OBJECT_MAPPER.readValue(text, new TypeReference<List<String>>() {
});
if (parsedItems == null || parsedItems.isEmpty()) {
return Collections.emptyList();
}
List<String> result = new ArrayList<String>();
for (String item : parsedItems) {
String normalizedItem = trimToNull(item);
if (normalizedItem != null) {
result.add(normalizedItem);
}
}
return result;
} catch (Exception exception) {
return Collections.emptyList();
}
}
private String requireJsonObjectOrArrayText(String value, String blankMessage, String invalidMessage) {
String text = requireText(value, blankMessage);
try {
JsonNode jsonNode = OBJECT_MAPPER.readTree(text);
if (jsonNode == null || !(jsonNode.isObject() || jsonNode.isArray())) {
throw new BusinessException(CommonResponseEnum.FAIL, invalidMessage);
}
return text;
} catch (BusinessException exception) {
throw exception;
} catch (Exception exception) {
throw new BusinessException(CommonResponseEnum.FAIL, invalidMessage);
}
}
private void checkCreateUnit(String createUnit) {
Map<String, String> companyNameMap = buildDictNameMap(DictConst.TEST_REPORT_COMPANY_DICT_CODE);
if (!companyNameMap.containsKey(createUnit)) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_COMPANY_NOT_FOUND);
}
}
private void checkStandard(String standard) {
List<String> standardIds = parseJsonArray(standard);
if (standardIds.isEmpty()) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_STANDARD_INVALID);
}
Map<String, String> standardNameMap = buildDictNameMap(DictConst.TEST_REPORT_STANDARD_DICT_CODE);
for (String standardId : standardIds) {
if (!standardNameMap.containsKey(standardId)) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_STANDARD_NOT_FOUND);
}
}
}
private void checkClientUnit(String id) {
if (this.baseMapper.countNormalClientUnit(id) <= 0) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_CLIENT_UNIT_NOT_FOUND);
}
}
private void checkReportModel(String id) {
if (this.baseMapper.countNormalReportModel(id) <= 0) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_REPORT_MODEL_NOT_FOUND);
}
}
private ReportModelPO requireReportModel(String id) {
if (reportModelService == null || reportModelFileStorageService == null || filePreviewService == null) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_PREVIEW_NOT_READY);
}
return reportModelService.requireNormal(id);
}
private String resolvePreviewFileName(ReportModelPO model) {
String fileName = trimToNull(model.getFileName());
if (fileName != null) {
return fileName;
}
return reportModelFileStorageService.resolveDownloadFileName(model.getPath());
}
private List<String> normalizeIds(List<String> ids) {
if (ids == null || ids.isEmpty()) {
return Collections.emptyList();
}
List<String> result = new ArrayList<String>();
for (String id : ids) {
String normalizedId = trimToNull(id);
if (normalizedId != null && !result.contains(normalizedId)) {
result.add(normalizedId);
}
}
return result;
}
private LocalDateTime parseDateTime(String value, String errorMessage) {
try {
return LocalDateTime.parse(value.trim(), DATE_TIME_FORMATTER);
} catch (DateTimeParseException exception) {
throw new BusinessException(CommonResponseEnum.FAIL, errorMessage);
}
}
private String formatDateTime(LocalDateTime value) {
return DATE_TIME_FORMATTER.format(value);
}
private String requireText(String value, String blankMessage) {
String text = trimToNull(value);
if (text == null) {
throw new BusinessException(CommonResponseEnum.FAIL, blankMessage);
}
return text;
}
private String requireText(String value, String blankMessage, int maxLength, String lengthMessage) {
String text = requireText(value, blankMessage);
if (text.length() > maxLength) {
throw new BusinessException(CommonResponseEnum.FAIL, lengthMessage);
}
return text;
}
private String resolveCurrentUserId() {
String userId = RequestUtil.getUserId();
return StrUtil.isBlank(userId) ? null : userId;
}
private String trimToNull(String value) {
if (value == null) {
return null;
}
String text = value.trim();
if (text.isEmpty() || "-".equals(text)) {
return null;
}
return text;
}
private String encodeFileName(String fileName) {
try {
return URLEncoder.encode(fileName, StandardCharsets.UTF_8.name()).replace("+", "%20");
} catch (Exception exception) {
throw new BusinessException(CommonResponseEnum.FAIL, MSG_ENCODE_FILE_NAME_FAILED);
}
}
@Data
private static class NormalizedReport {
private String no;
private String clientUnitId;
private String reportModelId;
private String createUnit;
private String contractNumber;
private String standard;
private String data;
private String phonenumber;
}
}

View File

@@ -0,0 +1,42 @@
package com.njcn.gather.aireport.testreport.pojo.vo;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.time.LocalDate;
import java.time.LocalDateTime;
class TestReportVOTest {
@Test
void getStateShouldUseDefaultDisplayWhenStateIsBlank() {
TestReportVO report = new TestReportVO();
Assertions.assertEquals("-", report.getState());
}
@Test
void getStateShouldReturnOriginalValueWhenStateExists() {
TestReportVO report = new TestReportVO();
report.setState("02");
Assertions.assertEquals("02", report.getState());
}
@Test
void timeJsonShouldUseConfiguredDateFormats() throws Exception {
TestReportVO report = new TestReportVO();
report.setCheckTime(LocalDate.of(2026, 6, 29));
report.setCreateTime(LocalDateTime.of(2026, 6, 29, 9, 8, 7));
report.setUpdateTime(LocalDateTime.of(2026, 6, 29, 18, 16, 14));
String json = new ObjectMapper().writeValueAsString(report);
Assertions.assertTrue(json.contains("\"checkTime\":\"2026-06-29\""));
Assertions.assertTrue(json.contains("\"createTime\":\"2026-06-29 09:08:07\""));
Assertions.assertTrue(json.contains("\"updateTime\":\"2026-06-29 18:16:14\""));
Assertions.assertFalse(json.contains("2026-06-29T09"));
Assertions.assertFalse(json.contains("2026-06-29T18"));
}
}

View File

@@ -0,0 +1,318 @@
package com.njcn.gather.aireport.testreport.service.impl;
import com.njcn.common.pojo.exception.BusinessException;
import com.njcn.gather.aireport.reportmodel.component.ReportModelFileStorageService;
import com.njcn.gather.aireport.reportmodel.service.ReportModelService;
import com.njcn.gather.aireport.testreport.pojo.constant.TestReportConst;
import com.njcn.gather.aireport.testreport.pojo.param.TestReportParam;
import com.njcn.gather.aireport.testreport.pojo.po.TestReportPO;
import com.njcn.gather.aireport.testreport.pojo.vo.TestReportVO;
import com.njcn.gather.comservice.filepreview.FilePreviewService;
import com.njcn.gather.system.dictionary.pojo.po.DictData;
import com.njcn.gather.system.dictionary.pojo.po.DictType;
import com.njcn.gather.system.dictionary.service.IDictDataService;
import com.njcn.gather.system.dictionary.service.IDictTypeService;
import com.njcn.gather.system.pojo.constant.DictConst;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
class TestReportServiceImplTest {
private static final Integer DICT_STATE_NORMAL = 1;
private static final Integer DICT_STATE_DELETED = 0;
@Test
void addTestReportShouldRejectMissingNo() {
TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
TestReportParam.AddParam param = new TestReportParam.AddParam();
param.setClientUnitId("client-1");
param.setReportModelId("model-1");
param.setCreateUnit("unit-1");
param.setContractNumber("HT-001");
param.setStandard("[\"std-1\"]");
param.setData("{}");
BusinessException exception = Assertions.assertThrows(BusinessException.class,
() -> service.addTestReport(param));
Assertions.assertNotNull(exception.getMessage());
}
@Test
void submitAuditShouldRejectMissingId() {
TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
TestReportParam.SubmitAuditParam param = new TestReportParam.SubmitAuditParam();
param.setCheckResult("approved");
BusinessException exception = Assertions.assertThrows(BusinessException.class,
() -> service.submitAudit(param));
Assertions.assertNotNull(exception.getMessage());
}
@Test
void addTestReportShouldRejectInvalidStandardJsonArray() {
TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
TestReportParam.AddParam param = new TestReportParam.AddParam();
param.setNo("PT-001");
param.setClientUnitId("client-1");
param.setReportModelId("model-1");
param.setCreateUnit("company-1");
param.setContractNumber("HT-001");
param.setStandard("not-json-array");
param.setData("{}");
BusinessException exception = Assertions.assertThrows(BusinessException.class,
() -> service.addTestReport(param));
Assertions.assertTrue(exception.getMessage().contains("\u6d4b\u8bd5\u4f9d\u636e"));
}
@Test
void addTestReportShouldRejectInvalidDataJson() {
TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
TestReportParam.AddParam param = buildValidNormalizeParam();
param.setData("not-json");
BusinessException exception = Assertions.assertThrows(BusinessException.class,
() -> invokeNormalizeParam(service, param));
Assertions.assertTrue(exception.getMessage().contains("\u6536\u8d44\u6570\u636e"));
}
@Test
void addTestReportShouldRejectScalarDataJson() {
TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
TestReportParam.AddParam param = buildValidNormalizeParam();
param.setData("\"plain-text\"");
BusinessException exception = Assertions.assertThrows(BusinessException.class,
() -> invokeNormalizeParam(service, param));
Assertions.assertTrue(exception.getMessage().contains("\u6536\u8d44\u6570\u636e"));
}
@Test
void normalizeParamShouldAcceptJsonObjectAndJsonArrayData() {
TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
TestReportParam.AddParam objectParam = buildValidNormalizeParam();
objectParam.setData("{\"fileName\":\"report.json\"}");
TestReportParam.AddParam arrayParam = buildValidNormalizeParam();
arrayParam.setData("[{\"fileName\":\"report-a.json\"}]");
Assertions.assertDoesNotThrow(() -> invokeNormalizeParam(service, objectParam));
Assertions.assertDoesNotThrow(() -> invokeNormalizeParam(service, arrayParam));
}
@Test
void addTestReportShouldRejectDeletedStandardDictData() {
IDictTypeService dictTypeService = mock(IDictTypeService.class);
IDictDataService dictDataService = mock(IDictDataService.class);
TestReportServiceImpl service = createService(dictTypeService, dictDataService);
mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_COMPANY_DICT_CODE, "type-company");
mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_STANDARD_DICT_CODE, "type-standard");
when(dictDataService.listDictDataByTypeId("type-company")).thenReturn(Collections.singletonList(
buildDictData("company-1", "company-A", DICT_STATE_NORMAL)));
when(dictDataService.listDictDataByTypeId("type-standard")).thenReturn(Collections.singletonList(
buildDictData("std-1", "standard-A", DICT_STATE_DELETED)));
TestReportParam.AddParam param = new TestReportParam.AddParam();
param.setNo("PT-001");
param.setClientUnitId("client-1");
param.setReportModelId("model-1");
param.setCreateUnit("company-1");
param.setContractNumber("HT-001");
param.setStandard("[\"std-1\"]");
param.setData("{}");
BusinessException exception = Assertions.assertThrows(BusinessException.class,
() -> service.addTestReport(param));
Assertions.assertTrue(exception.getMessage().contains("\u6d4b\u8bd5\u4f9d\u636e"));
}
@Test
void fillDisplayUserNamesShouldPreferUserNameFallbackToUserIdAndDash() {
TestReportServiceImpl service = createService(mock(IDictTypeService.class), mock(IDictDataService.class));
TestReportVO first = new TestReportVO();
first.setCreateBy("creator-001");
first.setUpdateBy("updater-001");
first.setCreateByName("creator-A");
first.setUpdateByName("updater-A");
TestReportVO second = new TestReportVO();
second.setCreateBy("creator-002");
second.setUpdateBy(null);
Map<String, String> userNameMap = new HashMap<String, String>();
userNameMap.put("creator-001", "creator-A");
userNameMap.put("updater-001", "updater-A");
service.fillDisplayUserNames(first, userNameMap);
service.fillDisplayUserNames(second, userNameMap);
Assertions.assertEquals("creator-A", first.getCreateBy());
Assertions.assertEquals("updater-A", first.getUpdateBy());
Assertions.assertEquals("creator-002", second.getCreateBy());
Assertions.assertEquals("-", second.getUpdateBy());
}
@Test
void fillDisplayFieldsShouldResolveDictNamesAndFallbackToRawValue() {
IDictTypeService dictTypeService = mock(IDictTypeService.class);
IDictDataService dictDataService = mock(IDictDataService.class);
TestReportServiceImpl service = createService(dictTypeService, dictDataService);
mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_COMPANY_DICT_CODE, "type-company");
mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_STANDARD_DICT_CODE, "type-standard");
when(dictDataService.listDictDataByTypeId("type-company")).thenReturn(Collections.singletonList(
buildDictData("company-1", "Nanjing Zhiyou", DICT_STATE_NORMAL)));
when(dictDataService.listDictDataByTypeId("type-standard")).thenReturn(Arrays.asList(
buildDictData("std-1", "GB 50150-2016", DICT_STATE_NORMAL),
buildDictData("std-2", "DL/T 596-2021", DICT_STATE_NORMAL)));
TestReportVO first = new TestReportVO();
first.setCreateUnit("company-1");
first.setStandard("[\"std-1\",\"std-2\"]");
TestReportVO second = new TestReportVO();
second.setCreateUnit("company-unknown");
second.setStandard("[\"std-unknown\"]");
service.fillDisplayFields(Arrays.asList(first, second));
Assertions.assertEquals("Nanjing Zhiyou", first.getCreateUnit());
Assertions.assertEquals("GB 50150-2016, DL/T 596-2021", first.getStandard());
Assertions.assertEquals("company-unknown", second.getCreateUnit());
Assertions.assertEquals("std-unknown", second.getStandard());
}
@Test
void fillDisplayFieldsShouldIgnoreDeletedDictData() {
IDictTypeService dictTypeService = mock(IDictTypeService.class);
IDictDataService dictDataService = mock(IDictDataService.class);
TestReportServiceImpl service = createService(dictTypeService, dictDataService);
mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_COMPANY_DICT_CODE, "type-company");
mockNormalDictType(dictTypeService, DictConst.TEST_REPORT_STANDARD_DICT_CODE, "type-standard");
when(dictDataService.listDictDataByTypeId("type-company")).thenReturn(Collections.singletonList(
buildDictData("company-1", "company-A", DICT_STATE_DELETED)));
when(dictDataService.listDictDataByTypeId("type-standard")).thenReturn(Collections.singletonList(
buildDictData("std-1", "standard-A", DICT_STATE_DELETED)));
TestReportVO report = new TestReportVO();
report.setCreateUnit("company-1");
report.setStandard("[\"std-1\"]");
service.fillDisplayFields(Collections.singletonList(report));
Assertions.assertEquals("company-1", report.getCreateUnit());
Assertions.assertEquals("std-1", report.getStandard());
}
@Test
void submitAuditShouldMoveEditingStateToAuditingAndClearAuditFields() {
TestReportPO report = new TestReportPO();
report.setId("report-001");
report.setState(TestReportConst.STATE_EDITING);
report.setStatus(TestReportConst.STATUS_NORMAL);
report.setCheckId("checker-001");
report.setCheckTime(java.time.LocalDate.of(2026, 6, 26));
report.setCheckResult("done");
TestableTestReportServiceImpl service = new TestableTestReportServiceImpl(mock(IDictTypeService.class),
mock(IDictDataService.class), report);
TestReportParam.SubmitAuditParam param = new TestReportParam.SubmitAuditParam();
param.setId("report-001");
boolean result = service.submitAudit(param);
Assertions.assertTrue(result);
Assertions.assertEquals(TestReportConst.STATE_AUDITING, report.getState());
Assertions.assertNull(report.getCheckId());
Assertions.assertNull(report.getCheckTime());
Assertions.assertNull(report.getCheckResult());
Assertions.assertSame(report, service.updatedReport);
}
private TestReportServiceImpl createService(IDictTypeService dictTypeService, IDictDataService dictDataService) {
return new TestReportServiceImpl(
mock(ReportModelService.class),
mock(ReportModelFileStorageService.class),
mock(FilePreviewService.class),
dictTypeService,
dictDataService);
}
private TestReportParam.AddParam buildValidNormalizeParam() {
TestReportParam.AddParam param = new TestReportParam.AddParam();
param.setNo("PT-001");
param.setClientUnitId("client-1");
param.setReportModelId("model-1");
param.setCreateUnit("company-1");
param.setContractNumber("HT-001");
param.setStandard("[\"std-1\"]");
param.setData("{}");
return param;
}
private Object invokeNormalizeParam(TestReportServiceImpl service, TestReportParam.AddParam param) {
try {
Method method = TestReportServiceImpl.class.getDeclaredMethod("normalizeParam", TestReportParam.AddParam.class);
method.setAccessible(true);
return method.invoke(service, param);
} catch (java.lang.reflect.InvocationTargetException exception) {
Throwable targetException = exception.getTargetException();
if (targetException instanceof RuntimeException) {
throw (RuntimeException) targetException;
}
throw new RuntimeException(targetException);
} catch (Exception exception) {
throw new RuntimeException(exception);
}
}
private static class TestableTestReportServiceImpl extends TestReportServiceImpl {
private final TestReportPO report;
private TestReportPO updatedReport;
private TestableTestReportServiceImpl(IDictTypeService dictTypeService, IDictDataService dictDataService,
TestReportPO report) {
super(mock(ReportModelService.class), mock(ReportModelFileStorageService.class),
mock(FilePreviewService.class), dictTypeService, dictDataService);
this.report = report;
}
@Override
public TestReportPO requireNormal(String id) {
return report;
}
@Override
public boolean updateById(TestReportPO entity) {
this.updatedReport = entity;
return true;
}
}
private void mockNormalDictType(IDictTypeService dictTypeService, String code, String typeId) {
when(dictTypeService.getByCode(code)).thenReturn(buildDictType(typeId, code));
}
private DictType buildDictType(String id, String code) {
DictType dictType = new DictType();
dictType.setId(id);
dictType.setCode(code);
dictType.setState(DICT_STATE_NORMAL);
return dictType;
}
private DictData buildDictData(String id, String name, Integer state) {
DictData dictData = new DictData();
dictData.setId(id);
dictData.setName(name);
dictData.setState(state);
return dictData;
}
}