修复流水异常标签展示与导出
This commit is contained in:
@@ -41,6 +41,10 @@ public class CcdiBankStatementExcel {
|
||||
@Excel(name = "交易类型")
|
||||
private String cashType;
|
||||
|
||||
/** 异常标签 */
|
||||
@Excel(name = "异常标签")
|
||||
private String hitTags;
|
||||
|
||||
/** 交易金额 */
|
||||
@Excel(name = "交易金额")
|
||||
private BigDecimal displayAmount;
|
||||
|
||||
@@ -3,7 +3,9 @@ package com.ruoyi.ccdi.project.domain.vo;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 流水明细详情VO
|
||||
@@ -96,4 +98,7 @@ public class CcdiBankStatementDetailVO {
|
||||
|
||||
/** 原始文件上传时间 */
|
||||
private Date uploadTime;
|
||||
|
||||
/** 命中异常标签 */
|
||||
private List<CcdiBankStatementHitTagVO> hitTags = new ArrayList<>();
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
package com.ruoyi.ccdi.project.domain.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 流水命中异常标签VO
|
||||
*/
|
||||
@Data
|
||||
public class CcdiBankStatementHitTagVO {
|
||||
|
||||
/** 规则编码 */
|
||||
private String ruleCode;
|
||||
|
||||
/** 规则名称 */
|
||||
private String ruleName;
|
||||
|
||||
/** 风险等级 */
|
||||
private String riskLevel;
|
||||
|
||||
/** 命中原因 */
|
||||
private String reasonDetail;
|
||||
|
||||
/** 流水ID */
|
||||
private Long bankStatementId;
|
||||
}
|
||||
@@ -3,6 +3,8 @@ package com.ruoyi.ccdi.project.domain.vo;
|
||||
import lombok.Data;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 流水明细列表VO
|
||||
@@ -38,4 +40,7 @@ public class CcdiBankStatementListVO {
|
||||
|
||||
/** 页面展示金额 */
|
||||
private BigDecimal displayAmount;
|
||||
|
||||
/** 命中异常标签 */
|
||||
private List<CcdiBankStatementHitTagVO> hitTags = new ArrayList<>();
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.ccdi.project.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagResult;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
@@ -20,6 +21,18 @@ public interface CcdiBankTagResultMapper extends BaseMapper<CcdiBankTagResult> {
|
||||
*/
|
||||
int deleteByProjectAndModel(@Param("projectId") Long projectId, @Param("modelCode") String modelCode);
|
||||
|
||||
/**
|
||||
* 按项目和流水ID批量查询命中的异常标签
|
||||
*
|
||||
* @param projectId 项目ID
|
||||
* @param bankStatementIds 流水ID列表
|
||||
* @return 命中的异常标签列表
|
||||
*/
|
||||
List<CcdiBankStatementHitTagVO> selectStatementTagsByProjectAndStatementIds(
|
||||
@Param("projectId") Long projectId,
|
||||
@Param("bankStatementIds") List<Long> bankStatementIds
|
||||
);
|
||||
|
||||
/**
|
||||
* 批量插入结果
|
||||
*
|
||||
|
||||
@@ -5,16 +5,21 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiBankStatementQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
|
||||
import com.ruoyi.ccdi.project.service.ICcdiBankStatementService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
import java.util.Collections;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
@@ -31,6 +36,9 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
|
||||
@Resource
|
||||
private CcdiBankStatementMapper bankStatementMapper;
|
||||
|
||||
@Resource
|
||||
private CcdiBankTagResultMapper bankTagResultMapper;
|
||||
|
||||
@Override
|
||||
public CcdiBankStatementFilterOptionsVO getFilterOptions(Long projectId) {
|
||||
CcdiBankStatementFilterOptionsVO options = bankStatementMapper.selectFilterOptions(projectId);
|
||||
@@ -42,7 +50,9 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
|
||||
CcdiBankStatementQueryDTO queryDTO) {
|
||||
CcdiBankStatementQueryDTO normalizedQuery = queryDTO == null ? new CcdiBankStatementQueryDTO() : queryDTO;
|
||||
normalizeQuery(normalizedQuery);
|
||||
return bankStatementMapper.selectStatementPage(page, normalizedQuery);
|
||||
Page<CcdiBankStatementListVO> result = bankStatementMapper.selectStatementPage(page, normalizedQuery);
|
||||
attachHitTags(result == null ? Collections.emptyList() : result.getRecords(), normalizedQuery.getProjectId());
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -53,12 +63,58 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
|
||||
if (rows == null || rows.isEmpty()) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
attachHitTags(rows, normalizedQuery.getProjectId());
|
||||
return rows.stream().map(this::toExcel).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
public CcdiBankStatementDetailVO getStatementDetail(Long bankStatementId) {
|
||||
return bankStatementMapper.selectStatementDetailById(bankStatementId);
|
||||
CcdiBankStatementDetailVO detail = bankStatementMapper.selectStatementDetailById(bankStatementId);
|
||||
if (detail == null || detail.getProjectId() == null || detail.getBankStatementId() == null) {
|
||||
return detail;
|
||||
}
|
||||
Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = loadHitTagMap(
|
||||
detail.getProjectId(),
|
||||
List.of(detail.getBankStatementId())
|
||||
);
|
||||
detail.setHitTags(new ArrayList<>(hitTagMap.getOrDefault(detail.getBankStatementId(), Collections.emptyList())));
|
||||
return detail;
|
||||
}
|
||||
|
||||
private void attachHitTags(List<CcdiBankStatementListVO> rows, Long projectId) {
|
||||
if (rows == null || rows.isEmpty() || projectId == null) {
|
||||
return;
|
||||
}
|
||||
List<Long> bankStatementIds = rows.stream()
|
||||
.map(CcdiBankStatementListVO::getBankStatementId)
|
||||
.filter(item -> item != null)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
if (bankStatementIds.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
Map<Long, List<CcdiBankStatementHitTagVO>> hitTagMap = loadHitTagMap(projectId, bankStatementIds);
|
||||
rows.forEach(row -> row.setHitTags(new ArrayList<>(
|
||||
hitTagMap.getOrDefault(row.getBankStatementId(), Collections.emptyList())
|
||||
)));
|
||||
}
|
||||
|
||||
private Map<Long, List<CcdiBankStatementHitTagVO>> loadHitTagMap(Long projectId, List<Long> bankStatementIds) {
|
||||
if (projectId == null || bankStatementIds == null || bankStatementIds.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
List<CcdiBankStatementHitTagVO> hitTags =
|
||||
bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(projectId, bankStatementIds);
|
||||
if (hitTags == null || hitTags.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
return hitTags.stream()
|
||||
.filter(item -> item.getBankStatementId() != null)
|
||||
.collect(Collectors.groupingBy(
|
||||
CcdiBankStatementHitTagVO::getBankStatementId,
|
||||
LinkedHashMap::new,
|
||||
Collectors.toList()
|
||||
));
|
||||
}
|
||||
|
||||
private void normalizeQuery(CcdiBankStatementQueryDTO queryDTO) {
|
||||
@@ -138,7 +194,19 @@ public class CcdiBankStatementServiceImpl implements ICcdiBankStatementService {
|
||||
excel.setCustomerAccountNo(row.getCustomerAccountNo());
|
||||
excel.setUserMemo(row.getUserMemo());
|
||||
excel.setCashType(row.getCashType());
|
||||
excel.setHitTags(formatHitTags(row.getHitTags()));
|
||||
excel.setDisplayAmount(row.getDisplayAmount());
|
||||
return excel;
|
||||
}
|
||||
|
||||
private String formatHitTags(List<CcdiBankStatementHitTagVO> hitTags) {
|
||||
if (hitTags == null || hitTags.isEmpty()) {
|
||||
return "";
|
||||
}
|
||||
return hitTags.stream()
|
||||
.map(CcdiBankStatementHitTagVO::getRuleName)
|
||||
.filter(item -> item != null && !item.isBlank())
|
||||
.distinct()
|
||||
.collect(Collectors.joining("、"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -29,6 +29,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
<result property="remark" column="remark"/>
|
||||
</resultMap>
|
||||
|
||||
<resultMap id="CcdiBankStatementHitTagVOResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO">
|
||||
<result property="ruleCode" column="rule_code"/>
|
||||
<result property="ruleName" column="rule_name"/>
|
||||
<result property="riskLevel" column="risk_level"/>
|
||||
<result property="reasonDetail" column="reason_detail"/>
|
||||
<result property="bankStatementId" column="bank_statement_id"/>
|
||||
</resultMap>
|
||||
|
||||
<delete id="deleteByProjectAndModel">
|
||||
delete from ccdi_bank_statement_tag_result
|
||||
where project_id = #{projectId}
|
||||
@@ -37,6 +45,23 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
|
||||
</if>
|
||||
</delete>
|
||||
|
||||
<select id="selectStatementTagsByProjectAndStatementIds" resultMap="CcdiBankStatementHitTagVOResultMap">
|
||||
select
|
||||
rule_code,
|
||||
rule_name,
|
||||
risk_level,
|
||||
reason_detail,
|
||||
bank_statement_id
|
||||
from ccdi_bank_statement_tag_result
|
||||
where project_id = #{projectId}
|
||||
and bank_statement_id is not null
|
||||
and bank_statement_id IN
|
||||
<foreach collection="bankStatementIds" item="item" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
order by bank_statement_id asc, id asc
|
||||
</select>
|
||||
|
||||
<insert id="insertBatch" parameterType="java.util.List">
|
||||
insert into ccdi_bank_statement_tag_result (
|
||||
project_id, model_code, model_name, rule_code, rule_name, indicator_code,
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.ruoyi.ccdi.project.domain.vo;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
class CcdiBankStatementHitTagsContractTest {
|
||||
|
||||
@Test
|
||||
void listVo_shouldExposeHitTagsField() throws Exception {
|
||||
Field field = CcdiBankStatementListVO.class.getDeclaredField("hitTags");
|
||||
|
||||
assertNotNull(field, "流水列表VO应返回命中异常标签");
|
||||
}
|
||||
|
||||
@Test
|
||||
void detailVo_shouldExposeHitTagsField() throws Exception {
|
||||
Field field = CcdiBankStatementDetailVO.class.getDeclaredField("hitTags");
|
||||
|
||||
assertNotNull(field, "流水详情VO应返回命中异常标签");
|
||||
}
|
||||
|
||||
@Test
|
||||
void excelVo_shouldExposeHitTagsField() throws Exception {
|
||||
Field field = com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel.class.getDeclaredField("hitTags");
|
||||
|
||||
assertNotNull(field, "流水导出对象应返回异常标签文本列");
|
||||
}
|
||||
}
|
||||
@@ -2,9 +2,12 @@ package com.ruoyi.ccdi.project.mapper;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CcdiBankTagResultMapperXmlTest {
|
||||
@@ -20,4 +23,25 @@ class CcdiBankTagResultMapperXmlTest {
|
||||
assertTrue(xml.contains("model_code = #{modelCode}"));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
void mapper_shouldExposeStatementTagQueryForBankStatementDetails() {
|
||||
Method method = Arrays.stream(CcdiBankTagResultMapper.class.getDeclaredMethods())
|
||||
.filter(item -> "selectStatementTagsByProjectAndStatementIds".equals(item.getName()))
|
||||
.findFirst()
|
||||
.orElse(null);
|
||||
|
||||
assertNotNull(method, "应提供按项目和流水ID批量查询异常标签的方法");
|
||||
}
|
||||
|
||||
@Test
|
||||
void xml_shouldDefineStatementTagQueryForBankStatementDetails() throws Exception {
|
||||
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(RESOURCE)) {
|
||||
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
|
||||
|
||||
assertTrue(xml.contains("selectStatementTagsByProjectAndStatementIds"), xml);
|
||||
assertTrue(xml.contains("bank_statement_id IN"), xml);
|
||||
assertTrue(xml.contains("project_id = #{projectId}"), xml);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,9 +5,11 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiBankStatementQueryDTO;
|
||||
import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementHitTagVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
|
||||
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementOptionVO;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
|
||||
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
@@ -36,6 +38,9 @@ class CcdiBankStatementServiceImplTest {
|
||||
@Mock
|
||||
private CcdiBankStatementMapper bankStatementMapper;
|
||||
|
||||
@Mock
|
||||
private CcdiBankTagResultMapper bankTagResultMapper;
|
||||
|
||||
@Test
|
||||
void getFilterOptions_shouldReturnProjectWideOptions() {
|
||||
CcdiBankStatementFilterOptionsVO options = new CcdiBankStatementFilterOptionsVO();
|
||||
@@ -54,6 +59,7 @@ class CcdiBankStatementServiceImplTest {
|
||||
queryDTO.setProjectId(100L);
|
||||
queryDTO.setOrderBy("amount");
|
||||
queryDTO.setOrderDirection("desc");
|
||||
page.setRecords(List.of(new CcdiBankStatementListVO()));
|
||||
doReturn(page).when(bankStatementMapper).selectStatementPage(eq(page), same(queryDTO));
|
||||
|
||||
service.selectStatementPage(page, queryDTO);
|
||||
@@ -111,14 +117,66 @@ class CcdiBankStatementServiceImplTest {
|
||||
assertEquals("6222", result.get(0).getLeAccountNo());
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectStatementListForExport_shouldIncludeHitTagNames() {
|
||||
CcdiBankStatementQueryDTO queryDTO = new CcdiBankStatementQueryDTO();
|
||||
queryDTO.setProjectId(43L);
|
||||
CcdiBankStatementListVO row = new CcdiBankStatementListVO();
|
||||
row.setBankStatementId(51274L);
|
||||
row.setLeAccountNo("6222");
|
||||
when(bankStatementMapper.selectStatementListForExport(same(queryDTO))).thenReturn(List.of(row));
|
||||
|
||||
CcdiBankStatementHitTagVO hitTag = new CcdiBankStatementHitTagVO();
|
||||
hitTag.setBankStatementId(51274L);
|
||||
hitTag.setRuleName("大额存现交易");
|
||||
when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(43L, List.of(51274L)))
|
||||
.thenReturn(List.of(hitTag));
|
||||
|
||||
List<CcdiBankStatementExcel> result = service.selectStatementListForExport(queryDTO);
|
||||
|
||||
assertEquals(1, result.size());
|
||||
assertEquals("大额存现交易", result.get(0).getHitTags());
|
||||
}
|
||||
|
||||
@Test
|
||||
void selectStatementPage_shouldAttachHitTags() {
|
||||
Page<CcdiBankStatementListVO> page = new Page<>(1, 10);
|
||||
CcdiBankStatementQueryDTO queryDTO = new CcdiBankStatementQueryDTO();
|
||||
queryDTO.setProjectId(43L);
|
||||
CcdiBankStatementListVO row = new CcdiBankStatementListVO();
|
||||
row.setBankStatementId(51274L);
|
||||
page.setRecords(List.of(row));
|
||||
doReturn(page).when(bankStatementMapper).selectStatementPage(eq(page), same(queryDTO));
|
||||
|
||||
CcdiBankStatementHitTagVO hitTag = new CcdiBankStatementHitTagVO();
|
||||
hitTag.setBankStatementId(51274L);
|
||||
hitTag.setRuleCode("LARGE_CASH_DEPOSIT");
|
||||
hitTag.setRuleName("大额存现交易");
|
||||
when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(43L, List.of(51274L)))
|
||||
.thenReturn(List.of(hitTag));
|
||||
|
||||
Page<CcdiBankStatementListVO> result = service.selectStatementPage(page, queryDTO);
|
||||
|
||||
assertEquals(1, result.getRecords().get(0).getHitTags().size());
|
||||
assertEquals("LARGE_CASH_DEPOSIT", result.getRecords().get(0).getHitTags().get(0).getRuleCode());
|
||||
}
|
||||
|
||||
@Test
|
||||
void getStatementDetail_shouldDelegateToMapper() {
|
||||
CcdiBankStatementDetailVO detailVO = new CcdiBankStatementDetailVO();
|
||||
detailVO.setBankStatementId(200L);
|
||||
detailVO.setProjectId(43L);
|
||||
when(bankStatementMapper.selectStatementDetailById(200L)).thenReturn(detailVO);
|
||||
CcdiBankStatementHitTagVO hitTag = new CcdiBankStatementHitTagVO();
|
||||
hitTag.setBankStatementId(200L);
|
||||
hitTag.setRuleName("大额存现交易");
|
||||
when(bankTagResultMapper.selectStatementTagsByProjectAndStatementIds(43L, List.of(200L)))
|
||||
.thenReturn(List.of(hitTag));
|
||||
|
||||
CcdiBankStatementDetailVO result = service.getStatementDetail(200L);
|
||||
|
||||
assertSame(detailVO, result);
|
||||
assertEquals(1, result.getHitTags().size());
|
||||
assertEquals("大额存现交易", result.getHitTags().get(0).getRuleName());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user