Compare commits
4 Commits
f11d20abc7
...
892-withou
| Author | SHA1 | Date | |
|---|---|---|---|
| 83e1959b94 | |||
| 0164b5eb33 | |||
| 986383b347 | |||
| 21cde7c131 |
@@ -309,7 +309,7 @@ GET /loanPricing/workflow/20250119143025123
|
||||
"midPerFinMan": "false",
|
||||
"midPerEtc": "true",
|
||||
"bpMid": "-15",
|
||||
"totoalBpRelevance": "-50",
|
||||
"totalBpRelevance": "-50",
|
||||
"applyAmt": "500000",
|
||||
"bpLoanAmount": "0",
|
||||
"loanPurpose": "consumer",
|
||||
@@ -325,7 +325,7 @@ GET /loanPricing/workflow/20250119143025123
|
||||
"interestOverdue": "false",
|
||||
"cardOverdue": "false",
|
||||
"bpGreyOverdue": "0",
|
||||
"totoalBpRisk": "0",
|
||||
"totalBpRisk": "0",
|
||||
"totalBp": "-80",
|
||||
"calculateRate": "2.65"
|
||||
},
|
||||
@@ -364,7 +364,7 @@ GET /loanPricing/workflow/20250119143025123
|
||||
"isGreenLoan": "false",
|
||||
"isTechEnt": "true",
|
||||
"bpEntType": "-25",
|
||||
"totoalBpRelevance": "-55",
|
||||
"totalBpRelevance": "-55",
|
||||
"loanTerm": "36",
|
||||
"bpLoanTerm": "0",
|
||||
"applyAmt": "1000000",
|
||||
@@ -377,7 +377,7 @@ GET /loanPricing/workflow/20250119143025123
|
||||
"interestOverdue": "false",
|
||||
"cardOverdue": "false",
|
||||
"bpGreyOverdue": "0",
|
||||
"totoalBpRisk": "0",
|
||||
"totalBpRisk": "0",
|
||||
"totalBp": "-85",
|
||||
"calculateRate": "2.60"
|
||||
}
|
||||
|
||||
@@ -2,6 +2,7 @@ package com.ruoyi.loanpricing.controller;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.common.annotation.Anonymous;
|
||||
import com.ruoyi.common.annotation.Log;
|
||||
import com.ruoyi.common.core.controller.BaseController;
|
||||
import com.ruoyi.common.core.domain.AjaxResult;
|
||||
@@ -139,6 +140,17 @@ public class LoanPricingWorkflowController extends BaseController
|
||||
return success(loanRateHistoryService.query(custIsn));
|
||||
}
|
||||
|
||||
/**
|
||||
* 解密历史流程客户名称和证件号码密文字段
|
||||
*/
|
||||
@Anonymous
|
||||
@Operation(summary = "解密历史流程客户敏感字段")
|
||||
@PostMapping("/anonymous/decrypt-sensitive-fields")
|
||||
public AjaxResult decryptSensitiveFields()
|
||||
{
|
||||
return success(loanPricingWorkflowService.decryptWorkflowSensitiveFields());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询利率定价流程列表
|
||||
*/
|
||||
|
||||
@@ -88,7 +88,8 @@ public class ModelCorpOutputFields {
|
||||
// BP_企业客户类别
|
||||
private String bpEntType;
|
||||
// TOTAL_BP_关联度
|
||||
private String totoalBpRelevance;
|
||||
@TableField("totoal_bp_relevance")
|
||||
private String totalBpRelevance;
|
||||
// 贷款期限
|
||||
private String loanTerm;
|
||||
// BP_贷款期限
|
||||
@@ -112,7 +113,8 @@ public class ModelCorpOutputFields {
|
||||
// BP_灰名单与逾期
|
||||
private String bpGreyOverdue;
|
||||
// TOTAL_BP_风险度
|
||||
private String totoalBpRisk;
|
||||
@TableField("totoal_bp_risk")
|
||||
private String totalBpRisk;
|
||||
// 浮动BP
|
||||
private String totalBp;
|
||||
// 测算利率
|
||||
|
||||
@@ -116,8 +116,9 @@ public class ModelRetailOutputFields {
|
||||
// BP_中间业务
|
||||
private String bpMid;
|
||||
|
||||
// TOTAL_BP_关联度(注意原字段名拼写错误:totoalBpRelevance,已保留原拼写)
|
||||
private String totoalBpRelevance;
|
||||
// TOTAL_BP_关联度
|
||||
@TableField("totoal_bp_relevance")
|
||||
private String totalBpRelevance;
|
||||
|
||||
// 申请金额
|
||||
private String applyAmt;
|
||||
@@ -164,8 +165,9 @@ public class ModelRetailOutputFields {
|
||||
// BP_灰名单与逾期
|
||||
private String bpGreyOverdue;
|
||||
|
||||
// TOTAL_BP_风险度(注意原字段名拼写错误:totoalBpRisk,已保留原拼写)
|
||||
private String totoalBpRisk;
|
||||
// TOTAL_BP_风险度
|
||||
@TableField("totoal_bp_risk")
|
||||
private String totalBpRisk;
|
||||
|
||||
// 浮动BP
|
||||
private String totalBp;
|
||||
|
||||
@@ -17,4 +17,8 @@ public interface LoanPricingWorkflowMapper extends BaseMapper<LoanPricingWorkflo
|
||||
{
|
||||
IPage<LoanPricingWorkflowListVO> selectWorkflowPageWithRates(Page<?> page,
|
||||
@Param("query") LoanPricingWorkflow query);
|
||||
|
||||
int updateSensitiveFieldsById(@Param("id") Long id,
|
||||
@Param("custName") String custName,
|
||||
@Param("idNum") String idNum);
|
||||
}
|
||||
|
||||
@@ -9,6 +9,7 @@ import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowListVO;
|
||||
import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowVO;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* 利率定价流程Service接口
|
||||
@@ -93,4 +94,11 @@ public interface ILoanPricingWorkflowService
|
||||
* @return 是否成功
|
||||
*/
|
||||
public boolean setExecuteRate(String serialNum, String executeRate);
|
||||
|
||||
/**
|
||||
* 解密历史流程客户名称和证件号码密文字段
|
||||
*
|
||||
* @return 迁移统计
|
||||
*/
|
||||
public Map<String, Long> decryptWorkflowSensitiveFields();
|
||||
}
|
||||
|
||||
@@ -36,9 +36,6 @@ public class LoanPricingModelService {
|
||||
@Resource
|
||||
private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper;
|
||||
|
||||
@Resource
|
||||
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
|
||||
|
||||
public void invokeModelAsync(Long workflowId) {
|
||||
invokeModelAndSave(workflowId, false);
|
||||
}
|
||||
@@ -53,16 +50,6 @@ public class LoanPricingModelService {
|
||||
log.error("未找到对应的流程信息,未调用模型服务");
|
||||
return;
|
||||
}
|
||||
try
|
||||
{
|
||||
loanPricingWorkflow.setCustName(sensitiveFieldCryptoService.decrypt(loanPricingWorkflow.getCustName()));
|
||||
loanPricingWorkflow.setIdNum(sensitiveFieldCryptoService.decrypt(loanPricingWorkflow.getIdNum()));
|
||||
}
|
||||
catch (RuntimeException ex)
|
||||
{
|
||||
log.error("贷款定价模型调用前敏感字段解密失败", ex);
|
||||
throw ex;
|
||||
}
|
||||
ModelInvokeDTO modelInvokeDTO = new ModelInvokeDTO();
|
||||
BeanUtils.copyProperties(loanPricingWorkflow, modelInvokeDTO);
|
||||
if ("个人".equals(loanPricingWorkflow.getCustType()))
|
||||
|
||||
@@ -31,7 +31,9 @@ import org.springframework.util.StringUtils;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
@@ -94,8 +96,6 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
}
|
||||
|
||||
loanPricingWorkflow.setDeptId(SecurityUtils.getDeptId());
|
||||
loanPricingWorkflow.setCustName(sensitiveFieldCryptoService.encrypt(loanPricingWorkflow.getCustName()));
|
||||
loanPricingWorkflow.setIdNum(sensitiveFieldCryptoService.encrypt(loanPricingWorkflow.getIdNum()));
|
||||
loanPricingWorkflowMapper.insert(loanPricingWorkflow);
|
||||
loanPricingModelService.invokeModelAsync(loanPricingWorkflow.getId());
|
||||
|
||||
@@ -199,8 +199,6 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
{
|
||||
LoanPricingWorkflow workflow = selectWorkflowBySerialNum(serialNum);
|
||||
assertCurrentUserIsCreator(workflow);
|
||||
workflow.setCustName(sensitiveFieldCryptoService.decrypt(workflow.getCustName()));
|
||||
workflow.setIdNum(sensitiveFieldCryptoService.decrypt(workflow.getIdNum()));
|
||||
return workflow;
|
||||
}
|
||||
|
||||
@@ -236,14 +234,12 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
validateBusinessTypeAndHistoryRate(editingWorkflow);
|
||||
validateCollateralTypeAndCouponRate(editingWorkflow);
|
||||
|
||||
String encryptedCustName = sensitiveFieldCryptoService.encrypt(editingWorkflow.getCustName());
|
||||
String encryptedIdNum = sensitiveFieldCryptoService.encrypt(editingWorkflow.getIdNum());
|
||||
LambdaUpdateWrapper<LoanPricingWorkflow> updateWrapper = new LambdaUpdateWrapper<>();
|
||||
updateWrapper.eq(LoanPricingWorkflow::getId, existingWorkflow.getId())
|
||||
.set(LoanPricingWorkflow::getCustIsn, editingWorkflow.getCustIsn())
|
||||
.set(LoanPricingWorkflow::getCustName, encryptedCustName)
|
||||
.set(LoanPricingWorkflow::getCustName, editingWorkflow.getCustName())
|
||||
.set(LoanPricingWorkflow::getIdType, editingWorkflow.getIdType())
|
||||
.set(LoanPricingWorkflow::getIdNum, encryptedIdNum)
|
||||
.set(LoanPricingWorkflow::getIdNum, editingWorkflow.getIdNum())
|
||||
.set(LoanPricingWorkflow::getGuarType, editingWorkflow.getGuarType())
|
||||
.set(LoanPricingWorkflow::getApplyAmt, editingWorkflow.getApplyAmt())
|
||||
.set(LoanPricingWorkflow::getBusinessType, editingWorkflow.getBusinessType())
|
||||
@@ -312,7 +308,6 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
{
|
||||
LoanPricingWorkflow scopedQuery = applyWorkflowListDataScope(loanPricingWorkflow);
|
||||
IPage<LoanPricingWorkflowListVO> pageResult = loanPricingWorkflowMapper.selectWorkflowPageWithRates(page, scopedQuery);
|
||||
pageResult.getRecords().forEach(row -> row.setCustName(sensitiveFieldCryptoService.decrypt(row.getCustName())));
|
||||
return pageResult;
|
||||
}
|
||||
|
||||
@@ -330,10 +325,6 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
LambdaQueryWrapper<LoanPricingWorkflow> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(LoanPricingWorkflow::getSerialNum, serialNum);
|
||||
LoanPricingWorkflow loanPricingWorkflow = loanPricingWorkflowMapper.selectOne(wrapper);
|
||||
String plainCustName = sensitiveFieldCryptoService.decrypt(loanPricingWorkflow.getCustName());
|
||||
String plainIdNum = sensitiveFieldCryptoService.decrypt(loanPricingWorkflow.getIdNum());
|
||||
loanPricingWorkflow.setCustName(plainCustName);
|
||||
loanPricingWorkflow.setIdNum(plainIdNum);
|
||||
loanPricingWorkflowVO.setLoanPricingWorkflow(loanPricingWorkflow);
|
||||
|
||||
if (Objects.nonNull(loanPricingWorkflow.getModelOutputId())){
|
||||
@@ -381,10 +372,10 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
wrapper.like(LoanPricingWorkflow::getCustIsn, loanPricingWorkflow.getCustIsn());
|
||||
}
|
||||
|
||||
// 按机构号筛选
|
||||
if (StringUtils.hasText(loanPricingWorkflow.getOrgCode()))
|
||||
// 按创建人部门筛选
|
||||
if (loanPricingWorkflow.getDeptId() != null)
|
||||
{
|
||||
wrapper.like(LoanPricingWorkflow::getOrgCode, loanPricingWorkflow.getOrgCode());
|
||||
wrapper.eq(LoanPricingWorkflow::getDeptId, loanPricingWorkflow.getDeptId());
|
||||
}
|
||||
|
||||
return wrapper;
|
||||
@@ -468,4 +459,80 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
int result = loanPricingWorkflowMapper.updateById(workflow);
|
||||
return result > 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Long> decryptWorkflowSensitiveFields()
|
||||
{
|
||||
long processed = 0L;
|
||||
long updated = 0L;
|
||||
long skipped = 0L;
|
||||
long failed = 0L;
|
||||
|
||||
LambdaQueryWrapper<LoanPricingWorkflow> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.and(q -> q.isNotNull(LoanPricingWorkflow::getCustName)
|
||||
.or()
|
||||
.isNotNull(LoanPricingWorkflow::getIdNum));
|
||||
List<LoanPricingWorkflow> workflows = loanPricingWorkflowMapper.selectList(wrapper);
|
||||
|
||||
for (LoanPricingWorkflow workflow : workflows)
|
||||
{
|
||||
processed++;
|
||||
LoanPricingWorkflow update = new LoanPricingWorkflow();
|
||||
update.setId(workflow.getId());
|
||||
boolean changed = false;
|
||||
|
||||
String decryptedCustName = tryDecrypt(workflow.getCustName());
|
||||
if (decryptedCustName != null && !Objects.equals(decryptedCustName, workflow.getCustName()))
|
||||
{
|
||||
update.setCustName(decryptedCustName);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
String decryptedIdNum = tryDecrypt(workflow.getIdNum());
|
||||
if (decryptedIdNum != null && !Objects.equals(decryptedIdNum, workflow.getIdNum()))
|
||||
{
|
||||
update.setIdNum(decryptedIdNum);
|
||||
changed = true;
|
||||
}
|
||||
|
||||
if (!changed)
|
||||
{
|
||||
skipped++;
|
||||
continue;
|
||||
}
|
||||
|
||||
try
|
||||
{
|
||||
loanPricingWorkflowMapper.updateSensitiveFieldsById(update.getId(), update.getCustName(), update.getIdNum());
|
||||
updated++;
|
||||
}
|
||||
catch (RuntimeException ex)
|
||||
{
|
||||
failed++;
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, Long> result = new LinkedHashMap<>();
|
||||
result.put("processed", processed);
|
||||
result.put("updated", updated);
|
||||
result.put("skipped", skipped);
|
||||
result.put("failed", failed);
|
||||
return result;
|
||||
}
|
||||
|
||||
private String tryDecrypt(String value)
|
||||
{
|
||||
if (!StringUtils.hasText(value))
|
||||
{
|
||||
return null;
|
||||
}
|
||||
try
|
||||
{
|
||||
return sensitiveFieldCryptoService.decrypt(value);
|
||||
}
|
||||
catch (RuntimeException ex)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,7 +37,7 @@
|
||||
"isGreenLoan": "1",
|
||||
"isTechEnt": "1",
|
||||
"bpEntType": "7.5",
|
||||
"totoalBpRelevance": "9.2",
|
||||
"totalBpRelevance": "9.2",
|
||||
"bpLoanTerm": "3.3",
|
||||
"applyAmt": "5000000.00",
|
||||
"bpLoanAmount": "5.8",
|
||||
@@ -48,7 +48,7 @@
|
||||
"prinOverdue": "0",
|
||||
"interestOverdue": "0",
|
||||
"bpGreyOverdue": "0",
|
||||
"totoalBpRisk": "1.2",
|
||||
"totalBpRisk": "1.2",
|
||||
"totalBp": "48.2",
|
||||
"baseLoanRate": "3.45",
|
||||
"calculateRate": "3.932",
|
||||
|
||||
@@ -35,7 +35,7 @@
|
||||
"midPerFinMan": "5000.00",
|
||||
"midPerEtc": "180.00",
|
||||
"bpMid": "45",
|
||||
"totoalBpRelevance": "90",
|
||||
"totalBpRelevance": "90",
|
||||
"applyAmt": "200000.00",
|
||||
"bpLoanAmount": "60",
|
||||
"loanPurpose": "个人消费",
|
||||
@@ -51,7 +51,7 @@
|
||||
"interestOverdue": "否",
|
||||
"cardOverdue": "否",
|
||||
"bpGreyOverdue": "98",
|
||||
"totoalBpRisk": "95",
|
||||
"totalBpRisk": "95",
|
||||
"totalBp": "350",
|
||||
"calculateRate": "6.15",
|
||||
"loanRateHistory": "6.40",
|
||||
|
||||
@@ -40,13 +40,30 @@
|
||||
AND SUBSTRING_INDEX(lpw.create_by, '-', -1) LIKE CONCAT('%', #{query.createBy}, '%')
|
||||
</if>
|
||||
<if test="query != null and query.custIsn != null and query.custIsn != ''">
|
||||
AND lpw.cust_isn LIKE CONCAT('%', #{query.custIsn}, '%')
|
||||
AND (
|
||||
lpw.cust_isn LIKE CONCAT('%', #{query.custIsn}, '%')
|
||||
OR lpw.id_num LIKE CONCAT('%', #{query.custIsn}, '%')
|
||||
OR lpw.cust_name LIKE CONCAT('%', #{query.custIsn}, '%')
|
||||
)
|
||||
</if>
|
||||
<if test="query != null and query.orgCode != null and query.orgCode != ''">
|
||||
AND lpw.org_code LIKE CONCAT('%', #{query.orgCode}, '%')
|
||||
<if test="query != null and query.deptId != null">
|
||||
AND lpw.dept_id = #{query.deptId}
|
||||
</if>
|
||||
</where>
|
||||
ORDER BY lpw.update_time DESC
|
||||
</select>
|
||||
|
||||
<update id="updateSensitiveFieldsById">
|
||||
UPDATE loan_pricing_workflow
|
||||
<set>
|
||||
<if test="custName != null">
|
||||
cust_name = #{custName},
|
||||
</if>
|
||||
<if test="idNum != null">
|
||||
id_num = #{idNum},
|
||||
</if>
|
||||
</set>
|
||||
WHERE id = #{id}
|
||||
</update>
|
||||
|
||||
</mapper>
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.ruoyi.loanpricing.mapper;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
@@ -21,6 +22,25 @@ class LoanPricingWorkflowMapperXmlTest
|
||||
assertTrue(xml.contains("lpw.create_by = #{query.dataScopeCreateBy}"));
|
||||
assertTrue(xml.contains("lpw.dept_id IN"));
|
||||
assertTrue(xml.contains("find_in_set(#{query.dataScopeDeptId}, ancestors)"));
|
||||
assertTrue(xml.contains("lpw.dept_id = #{query.deptId}"));
|
||||
assertTrue(xml.contains("SUBSTRING_INDEX(lpw.create_by, '-', -1) LIKE CONCAT('%', #{query.createBy}, '%')"));
|
||||
assertTrue(xml.contains("lpw.cust_isn LIKE CONCAT('%', #{query.custIsn}, '%')"));
|
||||
assertTrue(xml.contains("lpw.id_num LIKE CONCAT('%', #{query.custIsn}, '%')"));
|
||||
assertTrue(xml.contains("lpw.cust_name LIKE CONCAT('%', #{query.custIsn}, '%')"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateOnlySensitiveFieldsForAnonymousMigration() throws IOException
|
||||
{
|
||||
ClassPathResource resource = new ClassPathResource("mapper/loanpricing/LoanPricingWorkflowMapper.xml");
|
||||
String xml = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
|
||||
String updateSql = xml.substring(xml.indexOf("<update id=\"updateSensitiveFieldsById\">"),
|
||||
xml.indexOf("</update>", xml.indexOf("<update id=\"updateSensitiveFieldsById\">")));
|
||||
|
||||
assertTrue(updateSql.contains("cust_name = #{custName}"));
|
||||
assertTrue(updateSql.contains("id_num = #{idNum}"));
|
||||
assertTrue(updateSql.contains("WHERE id = #{id}"));
|
||||
assertFalse(updateSql.contains("update_by"));
|
||||
assertFalse(updateSql.contains("update_time"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,9 +40,6 @@ class LoanPricingModelServicePersonalParamsTest {
|
||||
@Mock
|
||||
private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper;
|
||||
|
||||
@Mock
|
||||
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
|
||||
|
||||
@InjectMocks
|
||||
private LoanPricingModelService loanPricingModelService;
|
||||
|
||||
@@ -87,9 +84,9 @@ class LoanPricingModelServicePersonalParamsTest {
|
||||
workflow.setRunType("1");
|
||||
workflow.setCustIsn("CUST001");
|
||||
workflow.setCustType("个人");
|
||||
workflow.setCustName("cipher-name");
|
||||
workflow.setCustName("张三");
|
||||
workflow.setIdType("身份证");
|
||||
workflow.setIdNum("cipher-id");
|
||||
workflow.setIdNum("110101199001011234");
|
||||
workflow.setGuarType("信用");
|
||||
workflow.setApplyAmt("100000");
|
||||
workflow.setLoanTerm("3");
|
||||
@@ -101,8 +98,6 @@ class LoanPricingModelServicePersonalParamsTest {
|
||||
response.setCalculateRate("6.15");
|
||||
|
||||
when(loanPricingWorkflowMapper.selectById(1L)).thenReturn(workflow);
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
|
||||
when(modelService.invokePersonalModel(any())).thenReturn(response);
|
||||
|
||||
loanPricingModelService.invokeModelAsync(1L);
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
package com.ruoyi.loanpricing.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
||||
|
||||
import com.ruoyi.loanpricing.domain.dto.ModelInvokeDTO;
|
||||
import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
|
||||
@@ -21,7 +20,7 @@ import org.junit.jupiter.api.Test;
|
||||
class LoanPricingModelServiceTest
|
||||
{
|
||||
@Test
|
||||
void shouldDecryptCustNameAndIdNumBeforeInvokePersonalModel() throws Exception
|
||||
void shouldPassPlainCustNameAndIdNumBeforeInvokePersonalModel() throws Exception
|
||||
{
|
||||
TestContext context = createContext(personalWorkflow(1L));
|
||||
|
||||
@@ -37,15 +36,16 @@ class LoanPricingModelServiceTest
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotWritePlainCustNameAndIdNumBackWhenUpdatingWorkflow() throws Exception
|
||||
void shouldOnlyWriteModelOutputIdBackWhenUpdatingWorkflow() throws Exception
|
||||
{
|
||||
TestContext context = createContext(personalWorkflow(2L));
|
||||
|
||||
context.service.invokeModelAsync(2L);
|
||||
|
||||
LoanPricingWorkflow updatedWorkflow = context.updatedWorkflow.get();
|
||||
assertNotEquals("张三", updatedWorkflow.getCustName());
|
||||
assertNotEquals("110101199001011234", updatedWorkflow.getIdNum());
|
||||
assertEquals(2L, updatedWorkflow.getId());
|
||||
assertEquals(null, updatedWorkflow.getCustName());
|
||||
assertEquals(null, updatedWorkflow.getIdNum());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -54,8 +54,8 @@ class LoanPricingModelServiceTest
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setId(3L);
|
||||
workflow.setCustType("企业");
|
||||
workflow.setCustName("cipher-name");
|
||||
workflow.setIdNum("cipher-id");
|
||||
workflow.setCustName("测试科技有限公司");
|
||||
workflow.setIdNum("91110000100000000X");
|
||||
workflow.setRepayMethod("分期");
|
||||
workflow.setResCover("true");
|
||||
workflow.setIsGreenLoan("true");
|
||||
@@ -86,8 +86,8 @@ class LoanPricingModelServiceTest
|
||||
workflow.setId(4L);
|
||||
workflow.setModelOutputId(88L);
|
||||
workflow.setCustType("企业");
|
||||
workflow.setCustName("cipher-name");
|
||||
workflow.setIdNum("cipher-id");
|
||||
workflow.setCustName("测试科技有限公司");
|
||||
workflow.setIdNum("91110000100000000X");
|
||||
TestContext context = createContext(workflow);
|
||||
|
||||
context.service.reinvokeModelAndOverwrite(4L);
|
||||
@@ -102,8 +102,8 @@ class LoanPricingModelServiceTest
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setId(id);
|
||||
workflow.setCustType("个人");
|
||||
workflow.setCustName("cipher-name");
|
||||
workflow.setIdNum("cipher-id");
|
||||
workflow.setCustName("张三");
|
||||
workflow.setIdNum("110101199001011234");
|
||||
workflow.setCouponRate("2.15");
|
||||
return workflow;
|
||||
}
|
||||
@@ -127,7 +127,6 @@ class LoanPricingModelServiceTest
|
||||
savingMapper(ModelRetailOutputFieldsMapper.class, context.retailInsertCount, context.retailUpdateCount));
|
||||
setField(context.service, "modelCorpOutputFieldsMapper",
|
||||
savingMapper(ModelCorpOutputFieldsMapper.class, context.corpInsertCount, context.corpUpdateCount));
|
||||
setField(context.service, "sensitiveFieldCryptoService", new TestSensitiveFieldCryptoService());
|
||||
return context;
|
||||
}
|
||||
|
||||
@@ -228,25 +227,4 @@ class LoanPricingModelServiceTest
|
||||
}
|
||||
}
|
||||
|
||||
private static class TestSensitiveFieldCryptoService extends SensitiveFieldCryptoService
|
||||
{
|
||||
private TestSensitiveFieldCryptoService()
|
||||
{
|
||||
super("1234567890abcdef");
|
||||
}
|
||||
|
||||
@Override
|
||||
public String decrypt(String cipherText)
|
||||
{
|
||||
if ("cipher-name".equals(cipherText))
|
||||
{
|
||||
return "张三";
|
||||
}
|
||||
if ("cipher-id".equals(cipherText))
|
||||
{
|
||||
return "110101199001011234";
|
||||
}
|
||||
return cipherText;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -45,6 +46,8 @@ import org.springframework.security.authentication.UsernamePasswordAuthenticatio
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
@@ -81,7 +84,7 @@ class LoanPricingWorkflowServiceImplTest
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEncryptCustNameAndIdNumBeforeInsert()
|
||||
void shouldKeepCustNameAndIdNumPlainBeforeInsert()
|
||||
{
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setCustName("张三");
|
||||
@@ -89,16 +92,14 @@ class LoanPricingWorkflowServiceImplTest
|
||||
workflow.setCustIsn("CUST001");
|
||||
workflow.setBusinessType("新增");
|
||||
|
||||
when(sensitiveFieldCryptoService.encrypt("张三")).thenReturn("cipher-name");
|
||||
when(sensitiveFieldCryptoService.encrypt("110101199001011234")).thenReturn("cipher-id");
|
||||
|
||||
loanPricingWorkflowService.createLoanPricing(workflow);
|
||||
|
||||
verify(loanPricingWorkflowMapper).insert(argThat((LoanPricingWorkflow entity) ->
|
||||
Objects.equals("cipher-name", entity.getCustName())
|
||||
&& Objects.equals("cipher-id", entity.getIdNum())
|
||||
Objects.equals("张三", entity.getCustName())
|
||||
&& Objects.equals("110101199001011234", entity.getIdNum())
|
||||
&& Objects.equals(100L, entity.getDeptId())
|
||||
&& Objects.equals("CUST001", entity.getCustIsn())));
|
||||
verify(sensitiveFieldCryptoService, never()).encrypt(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -121,17 +122,17 @@ class LoanPricingWorkflowServiceImplTest
|
||||
void shouldReturnPlainCustNameWhenReturningPagedWorkflowList()
|
||||
{
|
||||
LoanPricingWorkflowListVO row = new LoanPricingWorkflowListVO();
|
||||
row.setCustName("cipher-name");
|
||||
row.setCustName("张三");
|
||||
|
||||
Page<LoanPricingWorkflowListVO> pageResult = new Page<>(1, 10);
|
||||
pageResult.setRecords(Collections.singletonList(row));
|
||||
|
||||
when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(pageResult);
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
|
||||
|
||||
IPage<LoanPricingWorkflowListVO> result = loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), new LoanPricingWorkflow());
|
||||
|
||||
assertEquals("张三", result.getRecords().get(0).getCustName());
|
||||
verify(sensitiveFieldCryptoService, never()).decrypt(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -229,7 +230,7 @@ class LoanPricingWorkflowServiceImplTest
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseCustIsnInsteadOfCustNameAsQueryCondition()
|
||||
void shouldUseCustIsnAsQueryConditionInLegacyList()
|
||||
{
|
||||
LoanPricingWorkflow query = new LoanPricingWorkflow();
|
||||
query.setCustIsn("CUST001");
|
||||
@@ -384,17 +385,16 @@ class LoanPricingWorkflowServiceImplTest
|
||||
void shouldReturnEditableWorkflowWithPlainSensitiveFieldsForCreator()
|
||||
{
|
||||
LoanPricingWorkflow workflow = editableWorkflow("个人", "若依-admin");
|
||||
workflow.setCustName("cipher-name");
|
||||
workflow.setIdNum("cipher-id");
|
||||
workflow.setCustName("张三");
|
||||
workflow.setIdNum("110101199001011234");
|
||||
|
||||
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
|
||||
|
||||
LoanPricingWorkflow result = loanPricingWorkflowService.selectEditableLoanPricingBySerialNum("P20260525001");
|
||||
|
||||
assertEquals("张三", result.getCustName());
|
||||
assertEquals("110101199001011234", result.getIdNum());
|
||||
verify(sensitiveFieldCryptoService, never()).decrypt(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -414,10 +414,6 @@ class LoanPricingWorkflowServiceImplTest
|
||||
{
|
||||
LoanPricingWorkflow workflow = editableWorkflow("个人", "若依-admin");
|
||||
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
|
||||
when(sensitiveFieldCryptoService.encrypt("张三")).thenReturn("cipher-name-new");
|
||||
when(sensitiveFieldCryptoService.encrypt("110101199001019999")).thenReturn("cipher-id-new");
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001019999");
|
||||
|
||||
PersonalLoanPricingCreateDTO dto = new PersonalLoanPricingCreateDTO();
|
||||
dto.setCustIsn("CUST001");
|
||||
@@ -434,7 +430,13 @@ class LoanPricingWorkflowServiceImplTest
|
||||
|
||||
ArgumentCaptor<LambdaUpdateWrapper> updateWrapperCaptor = ArgumentCaptor.forClass(LambdaUpdateWrapper.class);
|
||||
verify(loanPricingWorkflowMapper).update(any(), updateWrapperCaptor.capture());
|
||||
assertTrue(updateWrapperCaptor.getValue().getSqlSet().contains("apply_amt"));
|
||||
String sqlSet = updateWrapperCaptor.getValue().getSqlSet();
|
||||
assertTrue(sqlSet.contains("apply_amt"));
|
||||
assertTrue(sqlSet.contains("cust_name"));
|
||||
assertTrue(sqlSet.contains("id_num"));
|
||||
assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("张三"));
|
||||
assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("110101199001019999"));
|
||||
verify(sensitiveFieldCryptoService, never()).encrypt(any());
|
||||
verify(loanPricingModelService).reinvokeModelAndOverwrite(101L);
|
||||
}
|
||||
|
||||
@@ -443,10 +445,6 @@ class LoanPricingWorkflowServiceImplTest
|
||||
{
|
||||
LoanPricingWorkflow workflow = editableWorkflow("企业", "若依-admin");
|
||||
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
|
||||
when(sensitiveFieldCryptoService.encrypt("测试科技有限公司")).thenReturn("cipher-corp-name");
|
||||
when(sensitiveFieldCryptoService.encrypt("91110000100000000X")).thenReturn("cipher-corp-id");
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("测试科技有限公司");
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("91110000100000000X");
|
||||
|
||||
CorporateLoanPricingCreateDTO dto = new CorporateLoanPricingCreateDTO();
|
||||
dto.setCustIsn("CORP001");
|
||||
@@ -473,6 +471,9 @@ class LoanPricingWorkflowServiceImplTest
|
||||
assertTrue(sqlSet.contains("is_green_loan"), sqlSet);
|
||||
assertTrue(sqlSet.contains("is_trade_construction"), sqlSet);
|
||||
assertTrue(sqlSet.contains("coll_third_party"), sqlSet);
|
||||
assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("测试科技有限公司"));
|
||||
assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("91110000100000000X"));
|
||||
verify(sensitiveFieldCryptoService, never()).encrypt(any());
|
||||
verify(loanPricingModelService).reinvokeModelAndOverwrite(101L);
|
||||
}
|
||||
|
||||
@@ -543,17 +544,16 @@ class LoanPricingWorkflowServiceImplTest
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setSerialNum("P20260328001");
|
||||
workflow.setCustType("个人");
|
||||
workflow.setCustName("cipher-name");
|
||||
workflow.setIdNum("cipher-id");
|
||||
workflow.setCustName("张三");
|
||||
workflow.setIdNum("110101199001011234");
|
||||
|
||||
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
|
||||
|
||||
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001");
|
||||
|
||||
assertEquals("张三", result.getLoanPricingWorkflow().getCustName());
|
||||
assertEquals("110101199001011234", result.getLoanPricingWorkflow().getIdNum());
|
||||
verify(sensitiveFieldCryptoService, never()).decrypt(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -562,8 +562,8 @@ class LoanPricingWorkflowServiceImplTest
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setSerialNum("P20260328001");
|
||||
workflow.setCustType("个人");
|
||||
workflow.setCustName("cipher-name");
|
||||
workflow.setIdNum("cipher-id");
|
||||
workflow.setCustName("张三");
|
||||
workflow.setIdNum("110101199001011234");
|
||||
workflow.setModelOutputId(11L);
|
||||
|
||||
ModelRetailOutputFields retailOutputFields = new ModelRetailOutputFields();
|
||||
@@ -573,8 +573,6 @@ class LoanPricingWorkflowServiceImplTest
|
||||
|
||||
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
|
||||
when(modelRetailOutputFieldsMapper.selectById(11L)).thenReturn(retailOutputFields);
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
|
||||
|
||||
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001");
|
||||
|
||||
@@ -611,8 +609,8 @@ class LoanPricingWorkflowServiceImplTest
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setSerialNum("C20260328001");
|
||||
workflow.setCustType("企业");
|
||||
workflow.setCustName("cipher-name");
|
||||
workflow.setIdNum("cipher-id");
|
||||
workflow.setCustName("测试科技有限公司");
|
||||
workflow.setIdNum("91110000100000000X");
|
||||
workflow.setModelOutputId(22L);
|
||||
|
||||
ModelCorpOutputFields corpOutputFields = new ModelCorpOutputFields();
|
||||
@@ -622,8 +620,6 @@ class LoanPricingWorkflowServiceImplTest
|
||||
|
||||
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
|
||||
when(modelCorpOutputFieldsMapper.selectById(22L)).thenReturn(corpOutputFields);
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("测试科技有限公司");
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("91110000100000000X");
|
||||
|
||||
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("C20260328001");
|
||||
|
||||
@@ -631,6 +627,74 @@ class LoanPricingWorkflowServiceImplTest
|
||||
assertEquals("91110000100000000X", result.getModelCorpOutputFields().getIdNum());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldDecryptEncryptedWorkflowFieldsAndSkipPlainFields()
|
||||
{
|
||||
LoanPricingWorkflow encrypted = new LoanPricingWorkflow();
|
||||
encrypted.setId(1L);
|
||||
encrypted.setCustName("cipher-name");
|
||||
encrypted.setIdNum("cipher-id");
|
||||
LoanPricingWorkflow plain = new LoanPricingWorkflow();
|
||||
plain.setId(2L);
|
||||
plain.setCustName("张三");
|
||||
plain.setIdNum("");
|
||||
|
||||
when(loanPricingWorkflowMapper.selectList(any())).thenReturn(List.of(encrypted, plain));
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234");
|
||||
when(sensitiveFieldCryptoService.decrypt("张三")).thenThrow(new ServiceException("贷款定价敏感字段解密失败"));
|
||||
|
||||
Map<String, Long> result = loanPricingWorkflowService.decryptWorkflowSensitiveFields();
|
||||
|
||||
assertEquals(2L, result.get("processed"));
|
||||
assertEquals(1L, result.get("updated"));
|
||||
assertEquals(1L, result.get("skipped"));
|
||||
assertEquals(0L, result.get("failed"));
|
||||
verify(loanPricingWorkflowMapper).updateSensitiveFieldsById(1L, "张三", "110101199001011234");
|
||||
verify(loanPricingWorkflowMapper, never()).updateById(any(LoanPricingWorkflow.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateOnlySuccessfullyDecryptedWorkflowField()
|
||||
{
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setId(3L);
|
||||
workflow.setCustName("cipher-name");
|
||||
workflow.setIdNum("plain-id");
|
||||
|
||||
when(loanPricingWorkflowMapper.selectList(any())).thenReturn(List.of(workflow));
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
|
||||
when(sensitiveFieldCryptoService.decrypt("plain-id")).thenThrow(new ServiceException("贷款定价敏感字段解密失败"));
|
||||
|
||||
Map<String, Long> result = loanPricingWorkflowService.decryptWorkflowSensitiveFields();
|
||||
|
||||
assertEquals(1L, result.get("processed"));
|
||||
assertEquals(1L, result.get("updated"));
|
||||
assertEquals(0L, result.get("skipped"));
|
||||
assertEquals(0L, result.get("failed"));
|
||||
verify(loanPricingWorkflowMapper).updateSensitiveFieldsById(3L, "张三", null);
|
||||
verify(loanPricingWorkflowMapper, never()).updateById(any(LoanPricingWorkflow.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCountDatabaseUpdateExceptionAsMigrationFailure()
|
||||
{
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setId(4L);
|
||||
workflow.setCustName("cipher-name");
|
||||
|
||||
when(loanPricingWorkflowMapper.selectList(any())).thenReturn(List.of(workflow));
|
||||
when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三");
|
||||
when(loanPricingWorkflowMapper.updateSensitiveFieldsById(4L, "张三", null)).thenThrow(new RuntimeException("db error"));
|
||||
|
||||
Map<String, Long> result = loanPricingWorkflowService.decryptWorkflowSensitiveFields();
|
||||
|
||||
assertEquals(1L, result.get("processed"));
|
||||
assertEquals(0L, result.get("updated"));
|
||||
assertEquals(0L, result.get("skipped"));
|
||||
assertEquals(1L, result.get("failed"));
|
||||
}
|
||||
|
||||
private LoanPricingWorkflow validWorkflow()
|
||||
{
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
@@ -650,9 +714,9 @@ class LoanPricingWorkflowServiceImplTest
|
||||
workflow.setModelOutputId(201L);
|
||||
workflow.setCustIsn("CUST001");
|
||||
workflow.setCustType(custType);
|
||||
workflow.setCustName("cipher-name");
|
||||
workflow.setCustName("张三");
|
||||
workflow.setIdType("身份证");
|
||||
workflow.setIdNum("cipher-id");
|
||||
workflow.setIdNum("110101199001011234");
|
||||
workflow.setGuarType("信用");
|
||||
workflow.setApplyAmt("100000");
|
||||
workflow.setBusinessType("新增");
|
||||
|
||||
@@ -70,7 +70,7 @@
|
||||
<el-descriptions-item label="中间业务_个人_理财业务">{{ formatBoolean(retailOutput.midPerFinMan) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中间业务_个人_etc">{{ formatBoolean(retailOutput.midPerEtc) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="BP_中间业务"><span class="bp-value">{{ retailOutput.bpMid || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="TOTAL_BP_关联度"><span class="total-bp-value">{{ retailOutput.totoalBpRelevance || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="TOTAL_BP_关联度"><span class="total-bp-value">{{ retailOutput.totalBpRelevance || '-' }}</span></el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
@@ -98,7 +98,7 @@
|
||||
<el-descriptions-item label="利息逾期">{{ formatBoolean(retailOutput.interestOverdue) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="信用卡逾期">{{ formatBoolean(retailOutput.cardOverdue) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="BP_灰名单与逾期"><span class="bp-value">{{ retailOutput.bpGreyOverdue || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="TOTAL_BP_风险度"><span class="total-bp-value">{{ retailOutput.totoalBpRisk || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="TOTAL_BP_风险度"><span class="total-bp-value">{{ retailOutput.totalBpRisk || '-' }}</span></el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</template>
|
||||
@@ -164,7 +164,7 @@
|
||||
<el-descriptions-item label="代发工资户数">{{ corpOutput.payroll || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="存量贷款余额">{{ corpOutput.invLoanAmount || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="BP_代发工资"><span class="bp-value">{{ corpOutput.bpPayroll || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="TOTAL_BP_关联度"><span class="total-bp-value">{{ corpOutput.totoalBpRelevance || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="TOTAL_BP_关联度"><span class="total-bp-value">{{ corpOutput.totalBpRelevance || '-' }}</span></el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
@@ -200,7 +200,7 @@
|
||||
<el-descriptions-item label="本金逾期">{{ formatBoolean(corpOutput.prinOverdue) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="利息逾期">{{ formatBoolean(corpOutput.interestOverdue) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="BP_灰名单与逾期"><span class="bp-value">{{ corpOutput.bpGreyOverdue || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="TOTAL_BP_风险度"><span class="total-bp-value">{{ corpOutput.totoalBpRisk || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="TOTAL_BP_风险度"><span class="total-bp-value">{{ corpOutput.totalBpRisk || '-' }}</span></el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -3,25 +3,39 @@
|
||||
<!-- 页面头部:标题和返回按钮 -->
|
||||
<div class="page-header">
|
||||
<h2 class="page-title">流程详情</h2>
|
||||
<el-button icon="el-icon-back" size="small" @click="goBack">返回</el-button>
|
||||
<div class="page-actions">
|
||||
<el-button
|
||||
class="workflow-print-button"
|
||||
icon="el-icon-printer"
|
||||
size="small"
|
||||
type="primary"
|
||||
:disabled="loading || !workflowDetail"
|
||||
@click="handlePrint"
|
||||
>
|
||||
打印
|
||||
</el-button>
|
||||
<el-button icon="el-icon-back" size="small" @click="goBack">返回</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- 根据客户类型渲染对应的详情组件 -->
|
||||
<personal-workflow-detail
|
||||
v-if="!loading && workflowDetail && workflowDetail.custType === '个人'"
|
||||
:detail-data="workflowDetail"
|
||||
:retail-output="retailOutput"
|
||||
:bargaining-pool="bargainingPool"
|
||||
@refresh="getDetail"
|
||||
/>
|
||||
<div class="workflow-print-area">
|
||||
<!-- 根据客户类型渲染对应的详情组件 -->
|
||||
<personal-workflow-detail
|
||||
v-if="!loading && workflowDetail && workflowDetail.custType === '个人'"
|
||||
:detail-data="workflowDetail"
|
||||
:retail-output="retailOutput"
|
||||
:bargaining-pool="bargainingPool"
|
||||
@refresh="getDetail"
|
||||
/>
|
||||
|
||||
<corporate-workflow-detail
|
||||
v-if="!loading && workflowDetail && workflowDetail.custType === '企业'"
|
||||
:detail-data="workflowDetail"
|
||||
:corp-output="corpOutput"
|
||||
:bargaining-pool="bargainingPool"
|
||||
@refresh="getDetail"
|
||||
/>
|
||||
<corporate-workflow-detail
|
||||
v-if="!loading && workflowDetail && workflowDetail.custType === '企业'"
|
||||
:detail-data="workflowDetail"
|
||||
:corp-output="corpOutput"
|
||||
:bargaining-pool="bargainingPool"
|
||||
@refresh="getDetail"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -72,6 +86,37 @@ export default {
|
||||
/** 返回上一页 */
|
||||
goBack() {
|
||||
this.$router.go(-1)
|
||||
},
|
||||
/** 打印流程详情 */
|
||||
handlePrint() {
|
||||
this.$nextTick(() => {
|
||||
const originalTitle = document.title
|
||||
document.title = this.buildPrintFileName()
|
||||
window.print()
|
||||
document.title = originalTitle
|
||||
})
|
||||
},
|
||||
/** 生成打印文件名 */
|
||||
buildPrintFileName() {
|
||||
const custName = this.sanitizePrintFileName(this.workflowDetail?.custName || '流程详情')
|
||||
return `${custName}_${this.formatPrintTimestamp(new Date())}`
|
||||
},
|
||||
/** 格式化打印时间戳 */
|
||||
formatPrintTimestamp(date) {
|
||||
const pad = value => String(value).padStart(2, '0')
|
||||
return [
|
||||
date.getFullYear(),
|
||||
pad(date.getMonth() + 1),
|
||||
pad(date.getDate())
|
||||
].join('') + [
|
||||
pad(date.getHours()),
|
||||
pad(date.getMinutes()),
|
||||
pad(date.getSeconds())
|
||||
].join('')
|
||||
},
|
||||
/** 清理文件名中的非法字符 */
|
||||
sanitizePrintFileName(value) {
|
||||
return String(value).replace(/[\\/:*?"<>|]/g, '').trim() || '流程详情'
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -92,6 +137,96 @@ export default {
|
||||
font-weight: 500;
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.page-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="scss">
|
||||
@media print {
|
||||
@page {
|
||||
size: A4;
|
||||
margin: 12mm;
|
||||
}
|
||||
|
||||
body {
|
||||
background: #fff !important;
|
||||
}
|
||||
|
||||
.sidebar-container,
|
||||
.fixed-header,
|
||||
.navbar,
|
||||
.tags-view-container,
|
||||
.page-actions,
|
||||
.el-loading-mask,
|
||||
.execute-rate-input-wrapper .el-button {
|
||||
display: none !important;
|
||||
}
|
||||
|
||||
.main-container,
|
||||
.app-main,
|
||||
.app-container,
|
||||
.workflow-detail-container,
|
||||
.workflow-print-area {
|
||||
margin: 0 !important;
|
||||
padding: 0 !important;
|
||||
width: 100% !important;
|
||||
max-width: none !important;
|
||||
min-height: auto !important;
|
||||
overflow: visible !important;
|
||||
}
|
||||
|
||||
.workflow-detail-container .page-header {
|
||||
display: block !important;
|
||||
margin-bottom: 12px !important;
|
||||
padding: 0 !important;
|
||||
}
|
||||
|
||||
.workflow-detail-container .page-title {
|
||||
font-size: 20px !important;
|
||||
font-weight: 600 !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
|
||||
.personal-workflow-detail .detail-layout,
|
||||
.corporate-workflow-detail .detail-layout {
|
||||
display: block !important;
|
||||
}
|
||||
|
||||
.personal-workflow-detail .left-panel,
|
||||
.corporate-workflow-detail .left-panel,
|
||||
.personal-workflow-detail .right-panel,
|
||||
.corporate-workflow-detail .right-panel {
|
||||
width: 100% !important;
|
||||
max-width: none !important;
|
||||
flex: none !important;
|
||||
}
|
||||
|
||||
.el-card {
|
||||
break-inside: avoid;
|
||||
page-break-inside: avoid;
|
||||
box-shadow: none !important;
|
||||
border: 1px solid #dcdfe6 !important;
|
||||
margin-bottom: 12px !important;
|
||||
}
|
||||
|
||||
.el-card__header {
|
||||
background: #f5f7fa !important;
|
||||
}
|
||||
|
||||
.el-descriptions {
|
||||
page-break-inside: avoid;
|
||||
}
|
||||
|
||||
.el-input__inner,
|
||||
.el-input-group__append {
|
||||
border-color: #dcdfe6 !important;
|
||||
color: #000 !important;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
<template>
|
||||
<div class="app-container">
|
||||
<el-form :model="queryParams" ref="queryForm" size="small" :inline="true" v-show="showSearch" label-width="68px">
|
||||
<el-form-item label="客户内码" prop="custIsn">
|
||||
<el-form-item label="客户信息" prop="custIsn">
|
||||
<el-input
|
||||
v-model="queryParams.custIsn"
|
||||
placeholder="请输入客户内码"
|
||||
placeholder="请输入客户内码/证件号/客户名"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
@@ -17,12 +17,14 @@
|
||||
@keyup.enter.native="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="机构号" prop="orgCode">
|
||||
<el-input
|
||||
v-model="queryParams.orgCode"
|
||||
placeholder="请输入机构号"
|
||||
<el-form-item label="机构号" prop="deptId">
|
||||
<treeselect
|
||||
v-model="queryParams.deptId"
|
||||
:options="deptOptions"
|
||||
:show-count="true"
|
||||
placeholder="请选择机构号"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
style="width: 220px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
@@ -124,16 +126,20 @@
|
||||
|
||||
<script>
|
||||
import {getWorkflowEdit, listWorkflow} from "@/api/loanPricing/workflow"
|
||||
import {deptTreeSelect} from "@/api/system/user"
|
||||
import {mapGetters} from "vuex"
|
||||
import CustomerTypeSelector from "./components/CustomerTypeSelector"
|
||||
import CustomerMapSelector from "./components/CustomerMapSelector"
|
||||
import PersonalCreateDialog from "./components/PersonalCreateDialog"
|
||||
import CorporateCreateDialog from "./components/CorporateCreateDialog"
|
||||
import {formatRate} from "@/utils/rate"
|
||||
import Treeselect from "@riophae/vue-treeselect"
|
||||
import "@riophae/vue-treeselect/dist/vue-treeselect.css"
|
||||
|
||||
export default {
|
||||
name: "LoanPricingWorkflow",
|
||||
components: {
|
||||
Treeselect,
|
||||
CustomerTypeSelector,
|
||||
CustomerMapSelector,
|
||||
PersonalCreateDialog,
|
||||
@@ -149,6 +155,8 @@ export default {
|
||||
total: 0,
|
||||
// 利率定价流程表格数据
|
||||
workflowList: [],
|
||||
// 机构树选项
|
||||
deptOptions: [],
|
||||
// 是否显示客户类型选择弹出层
|
||||
showTypeSelector: false,
|
||||
// 是否显示客户号查询选择弹出层
|
||||
@@ -169,12 +177,13 @@ export default {
|
||||
pageSize: 10,
|
||||
custIsn: undefined,
|
||||
createBy: undefined,
|
||||
orgCode: undefined
|
||||
deptId: undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.getDeptTree()
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
@@ -202,6 +211,12 @@ export default {
|
||||
},
|
||||
methods: {
|
||||
formatRate,
|
||||
/** 查询机构下拉树结构 */
|
||||
getDeptTree() {
|
||||
deptTreeSelect().then(response => {
|
||||
this.deptOptions = response.data
|
||||
})
|
||||
},
|
||||
/** 查询利率定价流程列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
|
||||
Reference in New Issue
Block a user