Compare commits
22 Commits
a4f927fdcb
...
892-withou
| Author | SHA1 | Date | |
|---|---|---|---|
| dd10192c18 | |||
| 3afff5d942 | |||
| b769180bb1 | |||
| cb29c38bf1 | |||
| 9552e2acb8 | |||
| a8ed8472f3 | |||
| 30a1d9771f | |||
| 6b5d31fb92 | |||
| 9d48d4b688 | |||
| 86b50f057e | |||
| 3667a3cf78 | |||
| 739a73c6ee | |||
| 39c7b716cd | |||
| 83e1959b94 | |||
| 0164b5eb33 | |||
| 986383b347 | |||
| 21cde7c131 | |||
| f11d20abc7 | |||
| eef36ff0ec | |||
| 998f0b3c48 | |||
| cc6836804e | |||
| a6e7ef6105 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -59,3 +59,5 @@ audit.log
|
||||
|
||||
.DS_Store
|
||||
*/.DS_Store
|
||||
|
||||
.codegraph/
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,32 @@
|
||||
# 贷款定价单脚本部署改造实施记录
|
||||
|
||||
## 保存路径检查
|
||||
- 参考脚本:`/Users/wkc/Desktop/ccdi/ccdi/deploy/ccdi_function.sh`
|
||||
- 新增脚本保存路径:`bin/prod/loan_pricing_function.sh`
|
||||
- 实施记录保存路径:`doc/implementation-report-2026-05-13-loan-pricing-function-script.md`
|
||||
|
||||
## 修改内容
|
||||
- 新增 `loan_pricing_function.sh`,按 `ccdi_function.sh` 的 `deploy`、`restart`、`stop` 三命令结构改造为贷款定价可用脚本。
|
||||
- 按贷款定价现有生产目录约定调整:
|
||||
- 后端 Jar:`backend/ruoyi-admin.jar`
|
||||
- 前端静态目录:`frontend/dist/`
|
||||
- 日志目录:`logs/backend-console.log`
|
||||
- PID 文件:`run/backend.pid`
|
||||
- 临时目录:`tmp/loan-pricing-function/`
|
||||
- 备份目录:`backup/YYYYMMDDHHMMSS/`
|
||||
- 按贷款定价运行参数调整:
|
||||
- Java 默认目录:`/home/webapp/env/java`
|
||||
- 后端进程标记:`-Dloan.pricing.home=<脚本目录>`
|
||||
- Spring Profile:`uat`
|
||||
- 后端端口:`63310`
|
||||
- 上线包结构固定为根层包含:
|
||||
- `ruoyi-admin.jar`
|
||||
- `dist.zip`
|
||||
- 前端 `dist.zip` 解压后必须包含 `dist/index.html`,部署时写入 `frontend/dist/`。
|
||||
- 默认保持参考脚本的启动后持续输出日志行为,并支持 `FOLLOW_LOGS=0` 供自动化验证跳过持续日志输出。
|
||||
|
||||
## 验证结果
|
||||
- 已执行 `sh -n bin/prod/loan_pricing_function.sh`,语法校验通过。
|
||||
- 已在临时目录构造 `backend/`、`frontend/dist/`、根层发布 zip 和假 Java 进程,验证 `deploy` 可完成备份、替换、启动和日志落盘。
|
||||
- 已验证 `stop` 可停止脚本标记的后端进程并清理 PID 文件。
|
||||
- 验证过程中产生的临时测试目录已删除,未新增仓库内测试文件。
|
||||
@@ -0,0 +1,22 @@
|
||||
# 浏览器页签标题调整实施记录
|
||||
|
||||
## 修改时间
|
||||
|
||||
2026-05-15
|
||||
|
||||
## 修改内容
|
||||
|
||||
- 将前端浏览器页签标题从“上虞利率定价系统”调整为“贷款利率定价系统”。
|
||||
- 同步更新开发、测试、生产环境的 `VUE_APP_TITLE` 配置,确保本地运行和打包产物标题一致。
|
||||
|
||||
## 涉及文件
|
||||
|
||||
- `ruoyi-ui/.env.development`
|
||||
- `ruoyi-ui/.env.staging`
|
||||
- `ruoyi-ui/.env.production`
|
||||
|
||||
## 验证情况
|
||||
|
||||
- 已通过源码检索确认 `ruoyi-ui` 中页面标题配置已统一为“贷款利率定价系统”。
|
||||
- 已使用 browser-use 打开 `http://localhost:9527/login` 进行真实页面验证,浏览器标签页标题与 `document.title` 均为“贷款利率定价系统”。
|
||||
- 验证时仅启动前端服务;因本地后端 `localhost:63310` 未启动,验证码接口代理返回 `ECONNREFUSED`,不影响本次页签标题验证。
|
||||
@@ -0,0 +1,32 @@
|
||||
# 利率前端两位小数展示实施记录
|
||||
|
||||
## 修改时间
|
||||
- 2026-05-15
|
||||
|
||||
## 修改范围
|
||||
- `ruoyi-ui/src/utils/rate.js`
|
||||
- `ruoyi-ui/src/views/loanPricing/workflow/index.vue`
|
||||
- `ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue`
|
||||
- `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue`
|
||||
- `ruoyi-ui/src/views/loanPricing/workflow/components/CorporateWorkflowDetail.vue`
|
||||
- `ruoyi-ui/src/views/loanPricing/workflow/components/HistoryContractSelector.vue`
|
||||
- `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue`
|
||||
- `ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue`
|
||||
|
||||
## 修改内容
|
||||
- 新增 `formatRate` 前端格式化方法,统一将可解析的利率数值展示为小数点后两位。
|
||||
- 利率定价流程列表的测算利率、执行利率改为通过 `formatRate` 展示。
|
||||
- 个人/企业流程详情的基准利率、最终测算利率、执行利率初始展示值、历史贷款利率改为两位小数展示。
|
||||
- 模型输出中的基准利率、测算利率、历史利率、产品最低利率下限、平滑幅度、参考利率、最终测算利率、派生率改为两位小数展示。
|
||||
- 历史贷款合同选择弹窗和新增流程弹窗中的历史贷款利率展示改为两位小数。
|
||||
|
||||
## 影响说明
|
||||
- 本次仅调整前端展示格式,不改后端接口、数据库字段和模型调用逻辑。
|
||||
- 历史贷款利率在新增弹窗中仅格式化展示,表单内部仍保留接口返回的原始值。
|
||||
|
||||
## 验证
|
||||
- 已执行 `source ~/.nvm/nvm.sh && nvm use 14.21.3 && npm run build:prod`,构建通过,仅存在资源体积 warning。
|
||||
- 已启动后端 `http://localhost:63310` 和前端 `http://localhost:9527/`,通过 browser-use 打开真实页面验证。
|
||||
- 流程列表接口原始返回中存在 `calculateRate = 3.932` 的数据,流程列表页面 `测算利率(%)` 展示为 `3.93`。
|
||||
- 流程列表页面 `执行利率(%)` 展示为 `3.88`、`6.18` 或 `-`,已确认非空利率均为小数点后两位。
|
||||
- 流程详情页模型输出中,`finalCalculateRate = 3.732` 对应页面展示为 `3.73`,其他利率字段也按两位小数展示。
|
||||
@@ -0,0 +1,74 @@
|
||||
# 流程列表支行管理员数据权限实施记录
|
||||
|
||||
## 修改日期
|
||||
|
||||
2026-05-18
|
||||
|
||||
## 需求范围
|
||||
|
||||
- 在 `loan_pricing_workflow` 新增 `dept_id`,保存新创建流程的创建人机构号。
|
||||
- `GET /loanPricing/workflow/list` 增加支行管理员数据权限:
|
||||
- 超级管理员、角色名“管理员”或角色标识 `headAdmin` 查看全部流程。
|
||||
- 角色名“支行管理员”或角色标识 `branchAdmin` 查看本人机构及下级机构创建的流程。
|
||||
- 其他用户继续只查看本人 `create_by` 精确匹配的流程。
|
||||
- 不回填历史流程数据,历史 `dept_id` 为空的数据不纳入支行管理员机构权限。
|
||||
- 本次无前端代码改动。
|
||||
|
||||
## 修改内容
|
||||
|
||||
- 新增 `sql/add_workflow_dept_id_20260518.sql`,为流程表增加 `dept_id` 字段和 `idx_dept_id` 索引。
|
||||
- 同步更新流程表建表脚本中的 `dept_id` 字段和索引定义。
|
||||
- `LoanPricingWorkflow` 增加表字段 `deptId` 和列表内部权限字段 `dataScopeDeptId`。
|
||||
- `LoanPricingWorkflowServiceImpl.createLoanPricing` 在插入前写入当前登录人的 `deptId`。
|
||||
- `LoanPricingWorkflowServiceImpl.selectLoanPricingPage` 增加支行管理员分支:
|
||||
- `headAdmin` 不加权限过滤。
|
||||
- `branchAdmin` 写入当前登录人的 `dataScopeDeptId`。
|
||||
- 客户经理继续写入 `dataScopeCreateBy`。
|
||||
- `LoanPricingWorkflowMapper.xml` 增加基于 `lpw.dept_id` 与 `sys_dept.ancestors` 的本机构及下级机构过滤。
|
||||
- 创建者查询参数仍按 `SUBSTRING_INDEX(lpw.create_by, '-', -1)` 只模糊匹配柜员号,并与数据权限条件取交集。
|
||||
|
||||
## 验证记录
|
||||
|
||||
- 单元测试通过:
|
||||
|
||||
```bash
|
||||
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowServiceImplTest,LoanPricingWorkflowMapperXmlTest -Dsurefire.failIfNoSpecifiedTests=false test
|
||||
```
|
||||
|
||||
- 后端打包通过:
|
||||
|
||||
```bash
|
||||
mvn -pl ruoyi-admin -am clean package -DskipTests
|
||||
```
|
||||
|
||||
- 已在开发库执行:
|
||||
|
||||
```bash
|
||||
mysql ... loan-pricing < sql/add_workflow_dept_id_20260518.sql
|
||||
```
|
||||
|
||||
- 数据库回查确认 `loan_pricing_workflow.dept_id` 和 `idx_dept_id` 已存在。
|
||||
- API 验证通过:
|
||||
- `8929999/headAdmin` 查询临时流程返回 6 条,包含全部测试数据。
|
||||
- `8920100/branchAdmin` 查询临时流程返回 4 条,仅包含本机构、本人和下级机构数据。
|
||||
- `8920001/客户经理` 查询临时流程返回 1 条,仅包含本人创建数据。
|
||||
- `8920100/branchAdmin` 使用其他支行创建者 `8920201` 作为查询条件时返回 0 条,创建者查询参数不能扩大数据权限。
|
||||
- 真实创建接口验证通过:
|
||||
- `8920100/branchAdmin` 调用个人流程创建接口后,新流程落库 `dept_id=101`,`create_by=测试支行管理员-8920100`。
|
||||
- browser-use 真实页面验证通过:
|
||||
- 支行管理员登录真实流程列表页,页面显示 `共 4 条`。
|
||||
- 页面只显示 `BRANCH_SCOPE_20260518_CREATE`、`BRANCH_SCOPE_20260518_SELF`、`BRANCH_SCOPE_20260518_SAME`、`BRANCH_SCOPE_20260518_CHILD`。
|
||||
- 页面未显示其他支行 `BRANCH_SCOPE_20260518_OTHER` 和普通客户经理本人 `BRANCH_SCOPE_20260518_MANAGER`。
|
||||
|
||||
## 测试数据保留
|
||||
|
||||
- 按复测要求,本次验证保留测试用户和测试流程数据,不做清理。
|
||||
- 保留临时用户:
|
||||
- `8920100`,昵称 `测试支行管理员`,角色 `branchAdmin`,机构 `101`。
|
||||
- 保留流程数据:
|
||||
- `BRANCH_SCOPE_20260518_SELF`,机构 `101`,创建者 `测试支行管理员-8920100`。
|
||||
- `BRANCH_SCOPE_20260518_SAME`,机构 `101`,创建者 `同支行客户经理-8920101`。
|
||||
- `BRANCH_SCOPE_20260518_CHILD`,机构 `103`,创建者 `下级客户经理-8920103`。
|
||||
- `BRANCH_SCOPE_20260518_OTHER`,机构 `102`,创建者 `其他支行客户经理-8920201`。
|
||||
- `BRANCH_SCOPE_20260518_MANAGER`,机构 `100`,创建者 `测试客户经理-8920001`。
|
||||
- `BRANCH_SCOPE_20260518_CREATE`,机构 `101`,创建者 `测试支行管理员-8920100`,由真实新增接口创建。
|
||||
@@ -0,0 +1,19 @@
|
||||
# 流程列表客户内码字段实施记录
|
||||
|
||||
## 修改内容
|
||||
|
||||
- 后端流程列表查询 `selectWorkflowPageWithRates` 增加 `lpw.cust_isn AS custIsn`,确保列表接口返回客户内码。
|
||||
- 流程列表返回对象 `LoanPricingWorkflowListVO` 增加 `custIsn` 字段,承接接口返回值。
|
||||
- 前端流程列表页新增“客户内码”表格列,字段绑定 `custIsn`,支持超长内容 tooltip 展示。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 仅影响利率定价流程列表 `/loanPricing/workflow/list` 的返回字段和页面展示。
|
||||
- 不修改新增流程、详情页、筛选条件和数据库结构。
|
||||
|
||||
## 验证记录
|
||||
|
||||
- `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowListVOTest -Dsurefire.failIfNoSpecifiedTests=false test` 通过。
|
||||
- `source ~/.nvm/nvm.sh && nvm use 14.21.3 && node tests/customer-map-selection.test.js` 通过。
|
||||
- 使用真实后端接口 `/loanPricing/workflow/list?pageNum=1&pageSize=3` 验证返回 `custIsn`,前三条返回值为 `81000529053`、`81000791269`、`81000769824`。
|
||||
- 使用 browser-use 打开真实流程列表页 `http://localhost:1024/index`,确认表头包含“客户内码”,前三条列表行客户内码展示为 `81000529053`、`81000791269`、`81000769824`。
|
||||
@@ -0,0 +1,22 @@
|
||||
# 流程列表最终测算利率展示实施记录
|
||||
|
||||
## 修改内容
|
||||
|
||||
- 流程列表联表查询中,企业客户分支由 `model_corp_output_fields.calculate_rate` 改为 `model_corp_output_fields.final_calculate_rate`。
|
||||
- 个人客户分支保持读取 `model_retail_output_fields.final_calculate_rate`。
|
||||
- 前端流程列表列名由“测算利率(%)”调整为“最终测算利率(%)”,继续复用列表接口字段 `calculateRate` 展示,避免扩大接口字段变更范围。
|
||||
|
||||
## 影响范围
|
||||
|
||||
- 仅影响利率定价流程列表 `/loanPricing/workflow/list` 的利率来源和列名展示。
|
||||
- 不修改详情页、新增流程、执行利率和数据库结构。
|
||||
|
||||
## 验证记录
|
||||
|
||||
- 已更新 `LoanPricingWorkflowMapperXmlTest`,约束个人、企业流程列表均取 `final_calculate_rate`。
|
||||
- `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowMapperXmlTest,LoanPricingWorkflowListVOTest -Dsurefire.failIfNoSpecifiedTests=false test` 通过。
|
||||
- `source ~/.nvm/nvm.sh && nvm use 14.21.3 && node tests/customer-map-selection.test.js` 通过。
|
||||
- 使用 Node 静态断言确认流程列表列名为“最终测算利率(%)”,并且不再展示旧列名“测算利率(%)”。
|
||||
- `mvn -pl ruoyi-admin -am clean package -DskipTests` 打包通过,并重启本地 63310 后端。
|
||||
- 使用真实后端接口 `/loanPricing/workflow/list?pageNum=1&pageSize=3` 验证返回值,前三条 `calculateRate` 分别为 `3.732`、`6.05`、`3.732`。
|
||||
- 使用 browser-use 打开真实流程列表页 `http://localhost:1024/index`,确认列名为“最终测算利率(%)”,前三条页面展示值为 `3.73`、`6.05`、`3.73`,旧列名未出现。
|
||||
@@ -0,0 +1,28 @@
|
||||
# 流程列表列宽连续展示实施记录
|
||||
|
||||
## 修改时间
|
||||
- 2026-05-18
|
||||
|
||||
## 修改范围
|
||||
- `ruoyi-ui/src/views/loanPricing/workflow/index.vue`
|
||||
|
||||
## 修改内容
|
||||
- 将流程列表表格字段改为最小列宽布局,宽屏下自动铺满容器,窄屏下通过表格横向滚动查看。
|
||||
- 加宽“业务方流水号”“客户内码”“客户名称”“创建者”等关键字段最小列宽,避免当前业务数据被省略或换行截断。
|
||||
- 将关键字段列的 `show-overflow-tooltip` 省略展示方式改为最小列宽完整展示,并覆盖 Element UI 默认省略号样式。
|
||||
- 将“操作”列固定在右侧,并通过最小列宽布局让主体列自动铺满,避免固定列与主体列之间出现中间空白断开。
|
||||
|
||||
## 影响说明
|
||||
- 本次仅调整流程列表前端表格展示,不修改接口、后端查询逻辑、数据库结构和权限逻辑。
|
||||
- 字段内容较长时优先按最小列宽完整展示,页面可通过横向滚动查看完整列表。
|
||||
|
||||
## 验证
|
||||
- 已执行 `source ~/.nvm/nvm.sh && nvm use 14.21.3 && npm run build:prod`,前端生产构建通过,仅存在既有资源体积 warning。
|
||||
- 已复用本机前端 `http://localhost:1024/` 与后端 `http://localhost:63310` 进行真实页面验证。
|
||||
- 已通过 browser-use 打开真实流程列表页 `http://localhost:1024/loanPricing/workflow` 验证页面标题为“贷款利率定价系统”,页面非空且无浏览器 error/warn 日志。
|
||||
- 在真实页面验证流程列表:
|
||||
- 在 `1707x517` 视口下,表格主体 `scrollWidth=2020`、`clientWidth=1607`,确认窄屏仍可横向滚动。
|
||||
- 在 `2167x542` 视口下,表格主体 `scrollWidth=2067`、`clientWidth=2067`,确认宽屏下表格自动铺满容器。
|
||||
- “操作”列固定在表格右侧,固定列宽度约 `112px`。
|
||||
- 固定“操作”列左侧间距 `gapBeforeFixed=0`,右侧尾部间距 `trailingGapAfterFixed=0`,未出现中间或尾部大块空白。
|
||||
- “业务方流水号”“客户内码”“客户名称”“创建者”单元格样式为 `text-overflow: clip`、`overflow: hidden`、`white-space: nowrap`,当前列表关键字段 `BRANCH_SCOPE_20260518_MANAGER`、`测试支行管理员-8920100` 等完整展示且未使用省略号截断。
|
||||
50
doc/implementation-report-2026-05-19-sys-user-role-sql.md
Normal file
50
doc/implementation-report-2026-05-19-sys-user-role-sql.md
Normal file
@@ -0,0 +1,50 @@
|
||||
# sys_user_role 角色关系 SQL 生成记录
|
||||
|
||||
## 修改日期
|
||||
|
||||
2026-05-20
|
||||
|
||||
## 需求范围
|
||||
|
||||
- 根据 `/Users/wkc/Downloads/892.xlsx` 的 `Sheet1` 生成 `sys_user_role` 插入语句。
|
||||
- Excel 字段使用规则:`柜员号` 匹配 `sys_user.user_name` 后取 `sys_user.user_id`,`类型` 匹配 `sys_role.role_name` 后取 `sys_role.role_id`。
|
||||
- 未将 Excel 柜员号直接写入 `sys_user_role.user_id`。
|
||||
- 本次只生成 SQL 文件,未执行写入数据库。
|
||||
|
||||
## 输出文件
|
||||
|
||||
- `sql/insert_sys_user_role_892_20260519.sql`
|
||||
|
||||
## 数据统计
|
||||
|
||||
- Excel 有效数据行:257 行。
|
||||
- 管理员:4 行。
|
||||
- 支行管理员:37 行。
|
||||
- 客户经理:213 行。
|
||||
- Excel 中 3 个重复柜员号同时出现客户经理和支行管理员,本次按支行管理员优先处理,不插入客户经理角色。
|
||||
|
||||
## 重复柜员号
|
||||
|
||||
- `8922557`: 裘朝山 / 892220 / 客户经理 / Excel 第 26 行、裘朝山 / 892220 / 支行管理员 / Excel 第 117 行
|
||||
- `8922667`: 徐华源 / 892080 / 客户经理 / Excel 第 66 行、徐华源 / 892080 / 支行管理员 / Excel 第 121 行
|
||||
- `8923504`: 陈俊杰 / 892170 / 客户经理 / Excel 第 28 行、陈俊杰 / 892170 / 支行管理员 / Excel 第 118 行
|
||||
- 处理规则:以上重复柜员号只保留在支行管理员插入语句中。
|
||||
|
||||
## 数据库映射确认
|
||||
|
||||
- 已查询开发库 `sys_role`,当前有效角色为:
|
||||
- `管理员` -> `role_id=100`, `role_key=headAdmin`
|
||||
- `客户经理` -> `role_id=101`, `role_key=common`
|
||||
- `支行管理员` -> `role_id=102`, `role_key=branchAdmin`
|
||||
- SQL 文件不硬编码 `role_id`,执行时按 `sys_role.role_name` 动态匹配,避免不同环境角色自增 ID 不一致。
|
||||
- SQL 使用 `INSERT IGNORE`,重复执行不会重复写入相同 `(user_id, role_id)`。
|
||||
- SQL 不创建临时表,已按 `管理员`、`支行管理员`、`客户经理` 拆成三条独立插入语句,便于分角色执行和核对影响范围。
|
||||
|
||||
## 验证记录
|
||||
|
||||
- 已读取 Excel 并校验 `类型` 只包含 `管理员`、`支行管理员`、`客户经理`。
|
||||
- 已确认 `sys_user_role` 表结构为 `user_id`、`role_id` 联合主键。
|
||||
- 已对开发库做不写入目标表的干跑校验:257 行角色均可匹配,当前库 Excel 柜员号匹配到的有效用户数为 0。
|
||||
- 批量执行该 SQL 前需先导入 Excel 对应用户,否则三条插入语句因匹配不到 `sys_user.user_name` 不会写入关系。
|
||||
- 已按要求移除临时表建表、临时表插入和临时表删除语句。
|
||||
- 已在事务内执行 SQL 脚本并 `ROLLBACK`,确认三条插入语句语法通过且未落库。
|
||||
38
doc/implementation-report-2026-05-22-corporate-res-cover.md
Normal file
38
doc/implementation-report-2026-05-22-corporate-res-cover.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# 对公余值覆盖字段实施记录
|
||||
|
||||
## 背景
|
||||
|
||||
对公新增流程需要在“贷款信息”分组补充“余值覆盖”开关,字段名为 `resCover`,提交到后端和模型调用时使用 `0/1` 值。
|
||||
|
||||
## 修改内容
|
||||
|
||||
1. 前端企业新增弹窗 `CorporateCreateDialog.vue`
|
||||
- 在“贷款信息”分组新增“余值覆盖”开关。
|
||||
- 表单默认值为 `false`。
|
||||
- 提交时转换为 `resCover: '1'` 或 `resCover: '0'`。
|
||||
|
||||
2. 后端字段链路
|
||||
- `CorporateLoanPricingCreateDTO` 增加 `resCover`。
|
||||
- `LoanPricingWorkflow` 增加 `resCover`,对应数据库字段 `res_cover`。
|
||||
- `LoanPricingConverter` 将 DTO 字段写入流程实体。
|
||||
- `ModelInvokeDTO` 增加 `resCover`。
|
||||
- `LoanPricingModelService` 在企业模型调用前将 `resCover` 归一化为 `0/1`。
|
||||
|
||||
3. 页面展示
|
||||
- 企业流程详情页展示“余值覆盖”。
|
||||
|
||||
4. SQL
|
||||
- 新增 `sql/add_res_cover_20260522.sql`。
|
||||
- 同步更新 `sql/loan_pricing_workflow.sql`、`sql/loan_pricing_schema_20260328.sql`、`sql/loan_pricing_prod_init_20260331.sql`。
|
||||
|
||||
## 验证结果
|
||||
|
||||
- 已执行前端字段静态断言:`npm run test:corporate-create-input-params`,通过。
|
||||
- 已执行前端详情字段静态断言:`npm run test:corporate-display-fields`,通过。
|
||||
- 已执行后端单测:`mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingModelServiceTest,LoanPricingWorkflowServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test`,通过。
|
||||
- 已执行后端打包:`mvn -pl ruoyi-admin -am -DskipTests package`,通过。
|
||||
- 已执行开发库 SQL:`sql/add_res_cover_20260522.sql`,回查 `loan_pricing_workflow.res_cover` 为 `varchar(10)`。
|
||||
- 已启动真实前端页面,通过浏览器打开企业新增弹窗,确认“贷款信息”分组中展示“余值覆盖”开关,且位置在“企业标识”分组之前。
|
||||
- 已用临时后端端口发起企业创建接口,响应中 `resCover` 为 `1`,后端外部模型调用日志确认请求参数包含 `"resCover":"1"`。
|
||||
- 接口验证产生的测试流程数据已清理,回查 `cust_isn = 'RES_COVER_TEST_20260522'` 的流程记录数为 `0`。
|
||||
- 测试时启动的前端和临时后端进程已关闭。
|
||||
14
doc/implementation-report-2026-05-25-user-dialog-width.md
Normal file
14
doc/implementation-report-2026-05-25-user-dialog-width.md
Normal file
@@ -0,0 +1,14 @@
|
||||
# 用户管理新增弹窗宽度前端实施记录
|
||||
|
||||
## 修改内容
|
||||
- 将用户管理“添加或修改用户配置对话框”的宽度从 `600px` 调整为页面宽度的 `80%`。
|
||||
- 修改范围仅限前端页面 `ruoyi-ui/src/views/system/user/index.vue`,不涉及后端接口、数据结构或权限逻辑。
|
||||
|
||||
## 验证记录
|
||||
- `source ~/.nvm/nvm.sh && nvm use 14.21.3 >/dev/null && npm run build:prod`
|
||||
- 结果:通过;仅保留项目既有的 webpack 体积提示。
|
||||
- 真实页面验证:
|
||||
- 前端地址:`http://localhost:1024/system/user`。
|
||||
- 登录用户:`admin`。
|
||||
- 点击用户管理页面“新增”后打开 `添加用户` 弹窗。
|
||||
- 浏览器视口宽度为 `1067px`,弹窗实际宽度为 `853.33px`,宽度比例为 `0.7998`,符合页面宽度 `80%` 的要求。
|
||||
@@ -0,0 +1,26 @@
|
||||
# 流程列表编辑功能后端实施记录
|
||||
|
||||
## 修改内容
|
||||
- 在利率定价流程接口新增编辑查询接口:`GET /loanPricing/workflow/{serialNum}/edit`。
|
||||
- 新增个人流程编辑接口:`PUT /loanPricing/workflow/{serialNum}/personal`。
|
||||
- 新增企业流程编辑接口:`PUT /loanPricing/workflow/{serialNum}/corporate`。
|
||||
- 编辑接口按当前登录用户的 `昵称-柜员号` 校验创建者,只允许流程创建者编辑。
|
||||
- 编辑时保持原业务方流水号、客户类型、创建者、创建时间和创建人部门,只覆盖表单字段。
|
||||
- 编辑保存后重新调用模型服务;已有模型输出记录时覆盖原模型输出,并保持流程关联。
|
||||
|
||||
## 验证记录
|
||||
- `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowServiceImplTest,LoanPricingModelServiceTest -Dsurefire.failIfNoSpecifiedTests=false test`
|
||||
- 结果:通过。
|
||||
- 覆盖:创建者编辑、非创建者拒绝、客户类型不匹配拒绝、编辑数据解密返回、重新测算覆盖模型结果。
|
||||
- `mvn -pl ruoyi-loan-pricing -am test`
|
||||
- 结果:通过。
|
||||
- 覆盖:利率定价模块现有单测和本次新增单测。
|
||||
- `mvn -pl ruoyi-admin -am -DskipTests package`
|
||||
- 结果:通过,重新打包 `ruoyi-admin/target/ruoyi-admin.jar` 用于真实接口验证。
|
||||
- 真实接口验证:
|
||||
- 创建临时个人流程 `20260525110739953`,创建者为 `若依-admin`。
|
||||
- 创建者调用 `GET /loanPricing/workflow/20260525110739953/edit` 成功返回原始客户名称 `编辑测试客户` 和原始证件号 `330103199901019999`。
|
||||
- 创建者通过页面编辑提交后,编辑详情接口返回 `applyAmt=120000`,并保持原 `serialNum`、`custType`、`createBy`、`createTime`、`deptId`。
|
||||
- 非创建者 `8929999` 调用编辑详情接口返回 `只有创建者可以编辑该流程`。
|
||||
- 非创建者 `8929999` 调用个人更新接口返回 `只有创建者可以编辑该流程`。
|
||||
- 验证完成后已按精确流水号删除临时流程和关联 `model_retail_output_fields` 记录,清理后计数均为 0。
|
||||
@@ -0,0 +1,24 @@
|
||||
# 流程列表编辑功能前端实施记录
|
||||
|
||||
## 修改内容
|
||||
- 在流程列表操作列新增“编辑”按钮。
|
||||
- 编辑按钮只在 `row.createBy` 等于当前登录用户 `nickName-name` 时展示。
|
||||
- 点击编辑后直接查询流程编辑数据,并按客户类型打开个人或企业弹窗,不再进入客户类型选择和客户号选择流程。
|
||||
- 个人和企业新增弹窗复用为新增/编辑双模式:
|
||||
- 新增模式继续调用原新增接口。
|
||||
- 编辑模式显示编辑标题、回显原始数据,并调用对应更新接口。
|
||||
- 编辑回显时跳过担保方式和抵质押类型监听中的清空逻辑,避免抵质押字段被初始化过程误清除。
|
||||
|
||||
## 验证记录
|
||||
- `source ~/.nvm/nvm.sh && nvm use 14.21.3 >/dev/null && node tests/workflow-index-refresh.test.js && node tests/personal-create-input-params.test.js && node tests/corporate-create-input-params.test.js`
|
||||
- 结果:通过。
|
||||
- 覆盖:操作列编辑按钮、创建者展示控制、编辑数据查询、个人/企业弹窗编辑模式和更新接口调用。
|
||||
- `source ~/.nvm/nvm.sh && nvm use 14.21.3 >/dev/null && npm run build:prod`
|
||||
- 结果:通过;仅保留项目既有的 webpack 体积提示。
|
||||
- 真实页面验证:
|
||||
- 前端地址:`http://localhost:1024/loanPricing/workflow`。
|
||||
- 创建者 `admin` 登录后,临时流水 `20260525110739953` 操作列显示“查看”和“编辑”。
|
||||
- 点击“编辑”直接打开 `编辑个人利率定价流程` 弹窗,回显客户内码 `EDITTEST20260525`、客户名称 `编辑测试客户`、证件号 `330103199901019999`、担保方式 `信用`、申请金额 `100000`、借款期限 `3`、业务种类 `新增`。
|
||||
- 将申请金额改为 `120000` 后提交,页面提示“编辑成功”,列表刷新后该流水申请金额变为 `120000`。
|
||||
- 非创建者 `8929999` 登录后,同一流水仍可查看,但操作列只显示“查看”,不显示“编辑”。
|
||||
- 浏览器控制台无相关 error;仅出现登录和表单过程中的 `async-validator` 校验 warning。
|
||||
@@ -0,0 +1,28 @@
|
||||
# 取消贷款定价页面脱敏展示后端实施记录
|
||||
|
||||
## 需求范围
|
||||
|
||||
- 取消贷款定价流程页面展示层的客户名称、证件号码脱敏效果。
|
||||
- 列表、详情、模型输出基本信息返回完整展示值。
|
||||
- 保留流程表客户名称、证件号码存储加密,以及创建、编辑、模型调用链路中的加解密逻辑。
|
||||
|
||||
## 实施内容
|
||||
|
||||
- 调整 `LoanPricingWorkflowServiceImpl` 分页列表返回逻辑,客户名称解密后直接返回。
|
||||
- 调整 `LoanPricingWorkflowServiceImpl` 详情返回逻辑,流程主信息中的客户名称、证件号码解密后直接返回。
|
||||
- 取消个人、企业模型输出基本信息中的客户名称、证件号码返回前脱敏处理。
|
||||
- 删除不再使用的贷款定价专用展示脱敏服务及对应单元测试。
|
||||
- 更新工作流服务单元测试,验证列表、详情、个人模型输出、企业模型输出均返回完整展示值。
|
||||
|
||||
## 未改动内容
|
||||
|
||||
- 未修改数据库表结构和数据迁移脚本。
|
||||
- 未修改 `SensitiveFieldCryptoService` 存储加解密逻辑。
|
||||
- 未修改登录、密码传输加密逻辑。
|
||||
- 未修改通用 `@Sensitive` 脱敏机制。
|
||||
- 未修改前端业务代码。
|
||||
|
||||
## 验证计划
|
||||
|
||||
- 执行 `LoanPricingWorkflowServiceImplTest`,确认返回展示值与原加解密边界符合预期。
|
||||
- 启动真实前后端页面,进入贷款定价流程列表与详情页,确认页面展示完整客户名称和证件号码。
|
||||
BIN
ruoyi-admin/.DS_Store
vendored
BIN
ruoyi-admin/.DS_Store
vendored
Binary file not shown.
@@ -25,16 +25,16 @@ spring:
|
||||
druid:
|
||||
# 主库数据源
|
||||
master:
|
||||
url: jdbc:mysql://116.62.17.81:3307/loan-pricing?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
username: root
|
||||
password: Kfcx@1234
|
||||
url: jdbc:mysql://116.62.17.81:53306/loan_pricing?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8
|
||||
username: znsj
|
||||
password: Znsj@123456
|
||||
# 从库数据源
|
||||
slave:
|
||||
# 从数据源开关/默认关闭
|
||||
enabled: false
|
||||
url:
|
||||
username:
|
||||
password:
|
||||
password:
|
||||
# 初始连接数
|
||||
initialSize: 5
|
||||
# 最小连接池数量
|
||||
|
||||
@@ -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;
|
||||
@@ -69,6 +70,46 @@ public class LoanPricingWorkflowController extends BaseController
|
||||
return success(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询利率定价流程编辑数据
|
||||
*/
|
||||
@Operation(summary = "查询利率定价流程编辑数据")
|
||||
@GetMapping("/{serialNum}/edit")
|
||||
public AjaxResult getEditInfo(
|
||||
@Parameter(description = "业务方流水号")
|
||||
@PathVariable("serialNum") String serialNum)
|
||||
{
|
||||
return success(loanPricingWorkflowService.selectEditableLoanPricingBySerialNum(serialNum));
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑个人客户利率定价流程
|
||||
*/
|
||||
@Operation(summary = "编辑个人客户利率定价流程")
|
||||
@Log(title = "个人客户利率定价流程", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/{serialNum}/personal")
|
||||
public AjaxResult updatePersonal(
|
||||
@Parameter(description = "业务方流水号")
|
||||
@PathVariable("serialNum") String serialNum,
|
||||
@Validated @RequestBody PersonalLoanPricingCreateDTO dto)
|
||||
{
|
||||
return success(loanPricingWorkflowService.updatePersonalLoanPricing(serialNum, dto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 编辑企业客户利率定价流程
|
||||
*/
|
||||
@Operation(summary = "编辑企业客户利率定价流程")
|
||||
@Log(title = "企业客户利率定价流程", businessType = BusinessType.UPDATE)
|
||||
@PutMapping("/{serialNum}/corporate")
|
||||
public AjaxResult updateCorporate(
|
||||
@Parameter(description = "业务方流水号")
|
||||
@PathVariable("serialNum") String serialNum,
|
||||
@Validated @RequestBody CorporateLoanPricingCreateDTO dto)
|
||||
{
|
||||
return success(loanPricingWorkflowService.updateCorporateLoanPricing(serialNum, dto));
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询个人客户号映射
|
||||
*/
|
||||
@@ -99,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());
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询利率定价流程列表
|
||||
*/
|
||||
|
||||
@@ -56,14 +56,25 @@ public class CorporateLoanPricingCreateDTO implements Serializable {
|
||||
@NotBlank(message = "贷款期限不能为空")
|
||||
private String loanTerm;
|
||||
|
||||
@Schema(description = "余值覆盖", example = "0")
|
||||
private String resCover;
|
||||
|
||||
@Schema(description = "绿色贷款", example = "0")
|
||||
private String isGreenLoan;
|
||||
|
||||
@Schema(description = "贸易和建筑业企业标识", example = "0")
|
||||
private String isTradeBuildEnt;
|
||||
|
||||
@Schema(description = "抵质押类型", example = "一类", allowableValues = {"一类", "二类", "三类", "四类", "排污权抵押", "设备等其他不动产抵押", "存单质押", "股权质押", "其他质押"})
|
||||
@Pattern(regexp = "^(一类|二类|三类|四类|排污权抵押|设备等其他不动产抵押|存单质押|股权质押|其他质押)$", message = "抵质押类型必须是:一类、二类、三类、四类、排污权抵押、设备等其他不动产抵押、存单质押、股权质押、其他质押之一")
|
||||
@Schema(description = "抵押物性质", example = "普通住宅、土地、厂房", allowableValues = {"普通住宅、土地、厂房", "商业性质不动产", "排污权", "其他不动产"})
|
||||
@Pattern(regexp = "^(普通住宅、土地、厂房|商业性质不动产|排污权|其他不动产)$", message = "抵押物性质必须是:普通住宅、土地、厂房、商业性质不动产、排污权、其他不动产之一")
|
||||
private String collateralNature;
|
||||
|
||||
@Schema(description = "是否属于九地", example = "是", allowableValues = {"是", "否"})
|
||||
@Pattern(regexp = "^(是|否)$", message = "是否属于九地必须是:是、否之一")
|
||||
private String isNineArea;
|
||||
|
||||
@Schema(description = "抵质押类型", example = "一类", allowableValues = {"一类", "二类", "三类", "排污权抵押", "其他不动产抵押", "存单质押", "股权质押", "其他质押"})
|
||||
@Pattern(regexp = "^(一类|二类|三类|排污权抵押|其他不动产抵押|存单质押|股权质押|其他质押)$", message = "抵质押类型必须是:一类、二类、三类、排污权抵押、其他不动产抵押、存单质押、股权质押、其他质押之一")
|
||||
private String collType;
|
||||
|
||||
@Schema(description = "抵质押物是否三方所有", example = "0")
|
||||
|
||||
@@ -125,12 +125,12 @@ public class ModelInvokeDTO {
|
||||
|
||||
/**
|
||||
* 贷款用途(非必填)
|
||||
* 可选值:consumer/business
|
||||
* 可选值:经营/消费
|
||||
*/
|
||||
private String loanPurpose;
|
||||
|
||||
/**
|
||||
* 是否有经营佐证(非必填)
|
||||
* 是否有营业执照(非必填)
|
||||
* 可选值:0/1
|
||||
*/
|
||||
private String bizProof;
|
||||
@@ -163,6 +163,12 @@ public class ModelInvokeDTO {
|
||||
*/
|
||||
private String couponRate;
|
||||
|
||||
/**
|
||||
* 余值覆盖(非必填)
|
||||
* 可选值:0/1
|
||||
*/
|
||||
private String resCover;
|
||||
|
||||
// /**
|
||||
// * 贷款利率(必填)
|
||||
// */
|
||||
|
||||
@@ -41,6 +41,11 @@ public class PersonalLoanPricingCreateDTO implements Serializable {
|
||||
@NotBlank(message = "申请金额不能为空")
|
||||
private String applyAmt;
|
||||
|
||||
@Schema(description = "贷款用途", requiredMode = Schema.RequiredMode.REQUIRED, example = "business", allowableValues = {"business", "consumer"})
|
||||
@NotBlank(message = "贷款用途不能为空")
|
||||
@Pattern(regexp = "^(business|consumer)$", message = "贷款用途必须是:business、consumer之一")
|
||||
private String loanPurpose;
|
||||
|
||||
@Schema(description = "业务种类", requiredMode = Schema.RequiredMode.REQUIRED, example = "存量转贷", allowableValues = {"新增", "存量新增", "存量转贷"})
|
||||
@NotBlank(message = "业务种类不能为空")
|
||||
@Pattern(regexp = "^(新增|存量新增|存量转贷)$", message = "业务种类必须是:新增、存量新增、存量转贷之一")
|
||||
@@ -53,10 +58,22 @@ public class PersonalLoanPricingCreateDTO implements Serializable {
|
||||
@NotBlank(message = "借款期限不能为空")
|
||||
private String loanTerm;
|
||||
|
||||
@Schema(description = "是否有营业执照", example = "0", allowableValues = {"0", "1"})
|
||||
@Pattern(regexp = "^[01]$", message = "是否有营业执照必须是:0或1")
|
||||
private String bizProof;
|
||||
|
||||
@Schema(description = "循环功能", example = "0")
|
||||
private String loanLoop;
|
||||
|
||||
@Schema(description = "抵质押类型", example = "一类", allowableValues = {"一线", "一类", "二类", "三类", "存单质押", "其他质押"})
|
||||
@Schema(description = "抵押物性质", example = "普通住宅、土地、厂房", allowableValues = {"普通住宅、土地、厂房", "商业性质不动产", "其他抵押财产"})
|
||||
@Pattern(regexp = "^(普通住宅、土地、厂房|商业性质不动产|其他抵押财产)$", message = "抵押物性质必须是:普通住宅、土地、厂房、商业性质不动产、其他抵押财产之一")
|
||||
private String collateralNature;
|
||||
|
||||
@Schema(description = "是否属于九地", example = "是", allowableValues = {"是", "否"})
|
||||
@Pattern(regexp = "^(是|否)$", message = "是否属于九地必须是:是、否之一")
|
||||
private String isNineArea;
|
||||
|
||||
@Schema(description = "抵质押类型", example = "一类", allowableValues = {"一类", "二类", "三类", "四类", "存单质押", "其他质押"})
|
||||
private String collType;
|
||||
|
||||
@Schema(description = "抵质押物是否三方所有", example = "0")
|
||||
|
||||
@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.annotation.*;
|
||||
import com.fasterxml.jackson.annotation.JsonFormat;
|
||||
import jakarta.validation.constraints.NotBlank;
|
||||
import lombok.Data;
|
||||
import org.springframework.format.annotation.DateTimeFormat;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.Date;
|
||||
@@ -112,7 +113,10 @@ public class LoanPricingWorkflow implements Serializable
|
||||
/** 存单票面利率 */
|
||||
private String couponRate;
|
||||
|
||||
/** 是否有经营佐证: true/false */
|
||||
/** 余值覆盖: 0/1 */
|
||||
private String resCover;
|
||||
|
||||
/** 是否有营业执照: true/false */
|
||||
private String bizProof;
|
||||
|
||||
/** 循环功能: true/false */
|
||||
@@ -121,6 +125,12 @@ public class LoanPricingWorkflow implements Serializable
|
||||
/** 抵质押类型: 一类/二类/三类/四类 */
|
||||
private String collType;
|
||||
|
||||
/** 抵押物性质 */
|
||||
private String collateralNature;
|
||||
|
||||
/** 是否属于九地: 是/否 */
|
||||
private String isNineArea;
|
||||
|
||||
/** 抵质押物是否三方所有: true/false */
|
||||
private String collThirdParty;
|
||||
|
||||
@@ -146,6 +156,9 @@ public class LoanPricingWorkflow implements Serializable
|
||||
/** 是否普惠小微借款人: true/false */
|
||||
private String isInclusiveFinance;
|
||||
|
||||
/** 创建人部门ID */
|
||||
private Long deptId;
|
||||
|
||||
/** 创建者 */
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private String createBy;
|
||||
@@ -154,6 +167,20 @@ public class LoanPricingWorkflow implements Serializable
|
||||
@TableField(exist = false)
|
||||
private String dataScopeCreateBy;
|
||||
|
||||
/** 列表数据权限部门过滤条件 */
|
||||
@TableField(exist = false)
|
||||
private Long dataScopeDeptId;
|
||||
|
||||
/** 创建时间查询起始值 */
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@TableField(exist = false)
|
||||
private Date beginCreateTime;
|
||||
|
||||
/** 创建时间查询结束值 */
|
||||
@DateTimeFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@TableField(exist = false)
|
||||
private Date endCreateTime;
|
||||
|
||||
/** 创建时间 */
|
||||
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
|
||||
@@ -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_贷款期限
|
||||
@@ -105,14 +106,25 @@ public class ModelCorpOutputFields {
|
||||
private String bpCollateral;
|
||||
// 灰名单客户
|
||||
private String greyCust;
|
||||
// 黑名单客户
|
||||
private String blackCust;
|
||||
// 本金逾期
|
||||
private String prinOverdue;
|
||||
// BP_本金逾期
|
||||
private String bpPrinOverdue;
|
||||
// 利息逾期
|
||||
private String interestOverdue;
|
||||
// BP_利息逾期
|
||||
private String bpIinterestOverdue;
|
||||
// 信用卡逾期
|
||||
private String cardOverdue;
|
||||
// BP_信用卡逾期
|
||||
private String bpCardOverdue;
|
||||
// BP_灰名单与逾期
|
||||
private String bpGreyOverdue;
|
||||
// TOTAL_BP_风险度
|
||||
private String totoalBpRisk;
|
||||
@TableField("totoal_bp_risk")
|
||||
private String totalBpRisk;
|
||||
// 浮动BP
|
||||
private String totalBp;
|
||||
// 测算利率
|
||||
@@ -127,6 +139,8 @@ public class ModelCorpOutputFields {
|
||||
private String finalCalculateRate;
|
||||
// 参考利率
|
||||
private String referenceRate;
|
||||
// 行社下调BP
|
||||
private String branchBp;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
|
||||
@@ -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;
|
||||
@@ -128,7 +129,7 @@ public class ModelRetailOutputFields {
|
||||
// 贷款用途
|
||||
private String loanPurpose;
|
||||
|
||||
// 是否有经营佐证
|
||||
// 是否有营业执照
|
||||
private String bizProof;
|
||||
|
||||
// BP_贷款用途
|
||||
@@ -152,20 +153,33 @@ public class ModelRetailOutputFields {
|
||||
// 灰名单客户
|
||||
private String greyCust;
|
||||
|
||||
// 黑名单客户
|
||||
private String blackCust;
|
||||
|
||||
// 本金逾期
|
||||
private String prinOverdue;
|
||||
|
||||
// BP_本金逾期
|
||||
private String bpPrinOverdue;
|
||||
|
||||
// 利息逾期
|
||||
private String interestOverdue;
|
||||
|
||||
// BP_利息逾期
|
||||
private String bpIinterestOverdue;
|
||||
|
||||
// 信用卡逾期
|
||||
private String cardOverdue;
|
||||
|
||||
// BP_信用卡逾期
|
||||
private String bpCardOverdue;
|
||||
|
||||
// BP_灰名单与逾期
|
||||
private String bpGreyOverdue;
|
||||
|
||||
// TOTAL_BP_风险度(注意原字段名拼写错误:totoalBpRisk,已保留原拼写)
|
||||
private String totoalBpRisk;
|
||||
// TOTAL_BP_风险度
|
||||
@TableField("totoal_bp_risk")
|
||||
private String totalBpRisk;
|
||||
|
||||
// 浮动BP
|
||||
private String totalBp;
|
||||
@@ -188,6 +202,9 @@ public class ModelRetailOutputFields {
|
||||
// 参考利率
|
||||
private String referenceRate;
|
||||
|
||||
// 行社下调BP
|
||||
private String branchBp;
|
||||
|
||||
@TableField(fill = FieldFill.INSERT)
|
||||
private Date createTime;
|
||||
}
|
||||
|
||||
@@ -9,6 +9,10 @@ public class LoanPricingWorkflowListVO
|
||||
{
|
||||
private String serialNum;
|
||||
|
||||
private Long deptId;
|
||||
|
||||
private String custIsn;
|
||||
|
||||
private String custName;
|
||||
|
||||
private String custType;
|
||||
|
||||
@@ -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接口
|
||||
@@ -34,6 +35,32 @@ public interface ILoanPricingWorkflowService
|
||||
*/
|
||||
public LoanPricingWorkflow createCorporateLoanPricing(CorporateLoanPricingCreateDTO dto);
|
||||
|
||||
/**
|
||||
* 查询利率定价流程编辑数据
|
||||
*
|
||||
* @param serialNum 业务方流水号
|
||||
* @return 编辑用流程数据
|
||||
*/
|
||||
public LoanPricingWorkflow selectEditableLoanPricingBySerialNum(String serialNum);
|
||||
|
||||
/**
|
||||
* 编辑个人客户利率定价流程
|
||||
*
|
||||
* @param serialNum 业务方流水号
|
||||
* @param dto 个人客户编辑DTO
|
||||
* @return 更新后的流程数据
|
||||
*/
|
||||
public LoanPricingWorkflow updatePersonalLoanPricing(String serialNum, PersonalLoanPricingCreateDTO dto);
|
||||
|
||||
/**
|
||||
* 编辑企业客户利率定价流程
|
||||
*
|
||||
* @param serialNum 业务方流水号
|
||||
* @param dto 企业客户编辑DTO
|
||||
* @return 更新后的流程数据
|
||||
*/
|
||||
public LoanPricingWorkflow updateCorporateLoanPricing(String serialNum, CorporateLoanPricingCreateDTO dto);
|
||||
|
||||
/**
|
||||
* 查询利率定价流程列表
|
||||
*
|
||||
@@ -67,4 +94,11 @@ public interface ILoanPricingWorkflowService
|
||||
* @return 是否成功
|
||||
*/
|
||||
public boolean setExecuteRate(String serialNum, String executeRate);
|
||||
|
||||
/**
|
||||
* 解密历史流程客户名称和证件号码密文字段
|
||||
*
|
||||
* @return 迁移统计
|
||||
*/
|
||||
public Map<String, Long> decryptWorkflowSensitiveFields();
|
||||
}
|
||||
|
||||
@@ -36,25 +36,20 @@ public class LoanPricingModelService {
|
||||
@Resource
|
||||
private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper;
|
||||
|
||||
@Resource
|
||||
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
|
||||
|
||||
public void invokeModelAsync(Long workflowId) {
|
||||
invokeModelAndSave(workflowId, false);
|
||||
}
|
||||
|
||||
public void reinvokeModelAndOverwrite(Long workflowId) {
|
||||
invokeModelAndSave(workflowId, true);
|
||||
}
|
||||
|
||||
private void invokeModelAndSave(Long workflowId, boolean overwriteModelOutput) {
|
||||
LoanPricingWorkflow loanPricingWorkflow = loanPricingWorkflowMapper.selectById(workflowId);
|
||||
if (Objects.isNull(loanPricingWorkflow)){
|
||||
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()))
|
||||
@@ -68,36 +63,68 @@ public class LoanPricingModelService {
|
||||
if (loanPricingWorkflow.getCustType().equals("个人")){
|
||||
// 个人模型
|
||||
ModelRetailOutputFields modelRetailOutputFields = modelService.invokePersonalModel(modelInvokeDTO);
|
||||
modelRetailOutputFieldsMapper.insert(modelRetailOutputFields);
|
||||
if (overwriteModelOutput && Objects.nonNull(loanPricingWorkflow.getModelOutputId()))
|
||||
{
|
||||
modelRetailOutputFields.setId(loanPricingWorkflow.getModelOutputId());
|
||||
modelRetailOutputFieldsMapper.updateById(modelRetailOutputFields);
|
||||
}
|
||||
else
|
||||
{
|
||||
modelRetailOutputFieldsMapper.insert(modelRetailOutputFields);
|
||||
}
|
||||
log.info("个人模型调用成功");
|
||||
LoanPricingWorkflow workflowToUpdate = new LoanPricingWorkflow();
|
||||
workflowToUpdate.setId(loanPricingWorkflow.getId());
|
||||
workflowToUpdate.setModelOutputId(modelRetailOutputFields.getId());
|
||||
loanPricingWorkflowMapper.updateById(workflowToUpdate);
|
||||
log.info("更新流程信息成功");
|
||||
updateWorkflowModelOutputId(loanPricingWorkflow, modelRetailOutputFields.getId());
|
||||
}else if (loanPricingWorkflow.getCustType().equals("企业")){
|
||||
// 企业模型
|
||||
ModelCorpOutputFields modelCorpOutputFields = modelService.invokeCorporateModel(modelInvokeDTO);
|
||||
modelCorpOutputFieldsMapper.insert(modelCorpOutputFields);
|
||||
if (overwriteModelOutput && Objects.nonNull(loanPricingWorkflow.getModelOutputId()))
|
||||
{
|
||||
modelCorpOutputFields.setId(loanPricingWorkflow.getModelOutputId());
|
||||
modelCorpOutputFieldsMapper.updateById(modelCorpOutputFields);
|
||||
}
|
||||
else
|
||||
{
|
||||
modelCorpOutputFieldsMapper.insert(modelCorpOutputFields);
|
||||
}
|
||||
log.info("企业模型调用成功");
|
||||
LoanPricingWorkflow workflowToUpdate = new LoanPricingWorkflow();
|
||||
workflowToUpdate.setId(loanPricingWorkflow.getId());
|
||||
workflowToUpdate.setModelOutputId(modelCorpOutputFields.getId());
|
||||
loanPricingWorkflowMapper.updateById(workflowToUpdate);
|
||||
log.info("更新流程信息成功");
|
||||
updateWorkflowModelOutputId(loanPricingWorkflow, modelCorpOutputFields.getId());
|
||||
}
|
||||
}
|
||||
|
||||
private void updateWorkflowModelOutputId(LoanPricingWorkflow loanPricingWorkflow, Long modelOutputId)
|
||||
{
|
||||
LoanPricingWorkflow workflowToUpdate = new LoanPricingWorkflow();
|
||||
workflowToUpdate.setId(loanPricingWorkflow.getId());
|
||||
workflowToUpdate.setModelOutputId(modelOutputId);
|
||||
loanPricingWorkflowMapper.updateById(workflowToUpdate);
|
||||
log.info("更新流程信息成功");
|
||||
}
|
||||
|
||||
private void normalizePersonalModelInvokeDTO(ModelInvokeDTO modelInvokeDTO)
|
||||
{
|
||||
modelInvokeDTO.setLoanPurpose(toPersonalModelLoanPurpose(modelInvokeDTO.getLoanPurpose()));
|
||||
modelInvokeDTO.setBizProof(toZeroOne(modelInvokeDTO.getBizProof()));
|
||||
modelInvokeDTO.setLoanLoop(toZeroOne(modelInvokeDTO.getLoanLoop()));
|
||||
modelInvokeDTO.setCollThirdParty(toZeroOne(modelInvokeDTO.getCollThirdParty()));
|
||||
}
|
||||
|
||||
private String toPersonalModelLoanPurpose(String value)
|
||||
{
|
||||
if ("business".equals(value))
|
||||
{
|
||||
return "经营";
|
||||
}
|
||||
if ("consumer".equals(value))
|
||||
{
|
||||
return "消费";
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
private void normalizeCorporateModelInvokeDTO(ModelInvokeDTO modelInvokeDTO)
|
||||
{
|
||||
modelInvokeDTO.setCollThirdParty(toZeroOne(modelInvokeDTO.getCollThirdParty()));
|
||||
modelInvokeDTO.setResCover(toZeroOne(modelInvokeDTO.getResCover()));
|
||||
modelInvokeDTO.setIsGreenLoan(toZeroOne(modelInvokeDTO.getIsGreenLoan()));
|
||||
modelInvokeDTO.setIsTradeBuildEnt(toZeroOne(modelInvokeDTO.getIsTradeBuildEnt()));
|
||||
}
|
||||
|
||||
@@ -1,46 +0,0 @@
|
||||
package com.ruoyi.loanpricing.service;
|
||||
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
@Service
|
||||
public class LoanPricingSensitiveDisplayService
|
||||
{
|
||||
public String maskCustName(String custName)
|
||||
{
|
||||
if (!StringUtils.hasText(custName))
|
||||
{
|
||||
return custName;
|
||||
}
|
||||
if (custName.contains("公司") && custName.length() > 4)
|
||||
{
|
||||
return custName.substring(0, 2) + "*".repeat(custName.length() - 4) + custName.substring(custName.length() - 2);
|
||||
}
|
||||
if (custName.length() == 1)
|
||||
{
|
||||
return custName;
|
||||
}
|
||||
return custName.substring(0, 1) + "*".repeat(custName.length() - 1);
|
||||
}
|
||||
|
||||
public String maskIdNum(String idNum)
|
||||
{
|
||||
if (!StringUtils.hasText(idNum))
|
||||
{
|
||||
return idNum;
|
||||
}
|
||||
if (idNum.startsWith("91") && idNum.length() == 18)
|
||||
{
|
||||
return idNum.substring(0, 2) + "*".repeat(13) + idNum.substring(idNum.length() - 3);
|
||||
}
|
||||
if (idNum.matches("\\d{17}[\\dXx]"))
|
||||
{
|
||||
return idNum.substring(0, 4) + "*".repeat(8) + idNum.substring(idNum.length() - 4);
|
||||
}
|
||||
if (idNum.length() > 5)
|
||||
{
|
||||
return idNum.substring(0, 2) + "*".repeat(idNum.length() - 5) + idNum.substring(idNum.length() - 3);
|
||||
}
|
||||
return "*".repeat(idNum.length());
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package com.ruoyi.loanpricing.service.impl;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import com.ruoyi.common.constant.UserConstants;
|
||||
@@ -20,7 +21,6 @@ import com.ruoyi.loanpricing.mapper.LoanPricingWorkflowMapper;
|
||||
import com.ruoyi.loanpricing.mapper.ModelCorpOutputFieldsMapper;
|
||||
import com.ruoyi.loanpricing.mapper.ModelRetailOutputFieldsMapper;
|
||||
import com.ruoyi.loanpricing.service.ILoanPricingWorkflowService;
|
||||
import com.ruoyi.loanpricing.service.LoanPricingSensitiveDisplayService;
|
||||
import com.ruoyi.loanpricing.service.LoanPricingModelService;
|
||||
import com.ruoyi.loanpricing.service.SensitiveFieldCryptoService;
|
||||
import com.ruoyi.loanpricing.util.LoanPricingConverter;
|
||||
@@ -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;
|
||||
|
||||
/**
|
||||
@@ -47,6 +49,10 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
|
||||
private static final String WORKFLOW_ADMIN_ROLE_KEY = "headAdmin";
|
||||
|
||||
private static final String WORKFLOW_BRANCH_ADMIN_ROLE_NAME = "支行管理员";
|
||||
|
||||
private static final String WORKFLOW_BRANCH_ADMIN_ROLE_KEY = "branchAdmin";
|
||||
|
||||
@Resource
|
||||
private LoanPricingWorkflowMapper loanPricingWorkflowMapper;
|
||||
|
||||
@@ -62,10 +68,6 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
@Resource
|
||||
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
|
||||
|
||||
@Resource
|
||||
private LoanPricingSensitiveDisplayService loanPricingSensitiveDisplayService;
|
||||
|
||||
|
||||
/**
|
||||
* 发起利率定价流程
|
||||
*
|
||||
@@ -93,8 +95,7 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
loanPricingWorkflow.setRunType("1");
|
||||
}
|
||||
|
||||
loanPricingWorkflow.setCustName(sensitiveFieldCryptoService.encrypt(loanPricingWorkflow.getCustName()));
|
||||
loanPricingWorkflow.setIdNum(sensitiveFieldCryptoService.encrypt(loanPricingWorkflow.getIdNum()));
|
||||
loanPricingWorkflow.setDeptId(SecurityUtils.getDeptId());
|
||||
loanPricingWorkflowMapper.insert(loanPricingWorkflow);
|
||||
loanPricingModelService.invokeModelAsync(loanPricingWorkflow.getId());
|
||||
|
||||
@@ -138,8 +139,8 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
|
||||
private void validatePersonalCollateralType(LoanPricingWorkflow workflow) {
|
||||
if ("抵押".equals(workflow.getGuarType())
|
||||
&& !isOneOf(workflow.getCollType(), "一线", "一类", "二类", "三类")) {
|
||||
throw new ServiceException("个人抵押抵质押类型必须是:一线、一类、二类、三类之一");
|
||||
&& !isOneOf(workflow.getCollType(), "一类", "二类", "三类", "四类")) {
|
||||
throw new ServiceException("个人抵押抵质押类型必须是:一类、二类、三类、四类之一");
|
||||
}
|
||||
if ("质押".equals(workflow.getGuarType())
|
||||
&& !isOneOf(workflow.getCollType(), "存单质押", "其他质押")) {
|
||||
@@ -149,8 +150,8 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
|
||||
private void validateCorporateCollateralType(LoanPricingWorkflow workflow) {
|
||||
if ("抵押".equals(workflow.getGuarType())
|
||||
&& !isOneOf(workflow.getCollType(), "一类", "二类", "三类", "四类", "排污权抵押", "设备等其他不动产抵押")) {
|
||||
throw new ServiceException("企业抵押抵质押类型必须是:一类、二类、三类、四类、排污权抵押、设备等其他不动产抵押之一");
|
||||
&& !isOneOf(workflow.getCollType(), "一类", "二类", "三类", "排污权抵押", "其他不动产抵押")) {
|
||||
throw new ServiceException("企业抵押抵质押类型必须是:一类、二类、三类、排污权抵押、其他不动产抵押之一");
|
||||
}
|
||||
if ("质押".equals(workflow.getGuarType())
|
||||
&& !isOneOf(workflow.getCollType(), "存单质押", "股权质押", "其他质押")) {
|
||||
@@ -193,6 +194,97 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
return createLoanPricing(entity);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LoanPricingWorkflow selectEditableLoanPricingBySerialNum(String serialNum)
|
||||
{
|
||||
LoanPricingWorkflow workflow = selectWorkflowBySerialNum(serialNum);
|
||||
assertCurrentUserIsCreator(workflow);
|
||||
return workflow;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public LoanPricingWorkflow updatePersonalLoanPricing(String serialNum, PersonalLoanPricingCreateDTO dto)
|
||||
{
|
||||
LoanPricingWorkflow editingWorkflow = LoanPricingConverter.toEntity(dto);
|
||||
return updateLoanPricing(serialNum, editingWorkflow, "个人");
|
||||
}
|
||||
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public LoanPricingWorkflow updateCorporateLoanPricing(String serialNum, CorporateLoanPricingCreateDTO dto)
|
||||
{
|
||||
LoanPricingWorkflow editingWorkflow = LoanPricingConverter.toEntity(dto);
|
||||
return updateLoanPricing(serialNum, editingWorkflow, "企业");
|
||||
}
|
||||
|
||||
private LoanPricingWorkflow updateLoanPricing(String serialNum, LoanPricingWorkflow editingWorkflow, String expectedCustType)
|
||||
{
|
||||
LoanPricingWorkflow existingWorkflow = selectWorkflowBySerialNum(serialNum);
|
||||
assertCurrentUserIsCreator(existingWorkflow);
|
||||
if (!expectedCustType.equals(existingWorkflow.getCustType()))
|
||||
{
|
||||
throw new ServiceException("客户类型不匹配,不能编辑该流程");
|
||||
}
|
||||
|
||||
editingWorkflow.setId(existingWorkflow.getId());
|
||||
editingWorkflow.setSerialNum(existingWorkflow.getSerialNum());
|
||||
editingWorkflow.setCustType(existingWorkflow.getCustType());
|
||||
editingWorkflow.setModelOutputId(existingWorkflow.getModelOutputId());
|
||||
validateBusinessTypeAndHistoryRate(editingWorkflow);
|
||||
validateCollateralTypeAndCouponRate(editingWorkflow);
|
||||
|
||||
LambdaUpdateWrapper<LoanPricingWorkflow> updateWrapper = new LambdaUpdateWrapper<>();
|
||||
updateWrapper.eq(LoanPricingWorkflow::getId, existingWorkflow.getId())
|
||||
.set(LoanPricingWorkflow::getCustIsn, editingWorkflow.getCustIsn())
|
||||
.set(LoanPricingWorkflow::getCustName, editingWorkflow.getCustName())
|
||||
.set(LoanPricingWorkflow::getIdType, editingWorkflow.getIdType())
|
||||
.set(LoanPricingWorkflow::getIdNum, editingWorkflow.getIdNum())
|
||||
.set(LoanPricingWorkflow::getGuarType, editingWorkflow.getGuarType())
|
||||
.set(LoanPricingWorkflow::getApplyAmt, editingWorkflow.getApplyAmt())
|
||||
.set(LoanPricingWorkflow::getLoanPurpose, editingWorkflow.getLoanPurpose())
|
||||
.set(LoanPricingWorkflow::getBusinessType, editingWorkflow.getBusinessType())
|
||||
.set(LoanPricingWorkflow::getLoanRateHistory, editingWorkflow.getLoanRateHistory())
|
||||
.set(LoanPricingWorkflow::getCouponRate, editingWorkflow.getCouponRate())
|
||||
.set(LoanPricingWorkflow::getLoanTerm, editingWorkflow.getLoanTerm())
|
||||
.set(LoanPricingWorkflow::getCollateralNature, editingWorkflow.getCollateralNature())
|
||||
.set(LoanPricingWorkflow::getIsNineArea, editingWorkflow.getIsNineArea())
|
||||
.set(LoanPricingWorkflow::getCollType, editingWorkflow.getCollType())
|
||||
.set(LoanPricingWorkflow::getCollThirdParty, editingWorkflow.getCollThirdParty())
|
||||
.set(LoanPricingWorkflow::getBizProof, editingWorkflow.getBizProof())
|
||||
.set(LoanPricingWorkflow::getLoanLoop, editingWorkflow.getLoanLoop())
|
||||
.set(LoanPricingWorkflow::getResCover, editingWorkflow.getResCover())
|
||||
.set(LoanPricingWorkflow::getRepayMethod, editingWorkflow.getRepayMethod())
|
||||
.set(LoanPricingWorkflow::getIsGreenLoan, editingWorkflow.getIsGreenLoan())
|
||||
.set(LoanPricingWorkflow::getIsTradeBuildEnt, editingWorkflow.getIsTradeBuildEnt())
|
||||
.set(LoanPricingWorkflow::getUpdateBy, buildCurrentCreateBy(SecurityUtils.getLoginUser()))
|
||||
.set(LoanPricingWorkflow::getUpdateTime, new Date());
|
||||
loanPricingWorkflowMapper.update(null, updateWrapper);
|
||||
loanPricingModelService.reinvokeModelAndOverwrite(existingWorkflow.getId());
|
||||
return selectEditableLoanPricingBySerialNum(serialNum);
|
||||
}
|
||||
|
||||
private LoanPricingWorkflow selectWorkflowBySerialNum(String serialNum)
|
||||
{
|
||||
LambdaQueryWrapper<LoanPricingWorkflow> wrapper = new LambdaQueryWrapper<>();
|
||||
wrapper.eq(LoanPricingWorkflow::getSerialNum, serialNum);
|
||||
LoanPricingWorkflow workflow = loanPricingWorkflowMapper.selectOne(wrapper);
|
||||
if (workflow == null)
|
||||
{
|
||||
throw new ServiceException("记录不存在");
|
||||
}
|
||||
return workflow;
|
||||
}
|
||||
|
||||
private void assertCurrentUserIsCreator(LoanPricingWorkflow workflow)
|
||||
{
|
||||
String currentCreateBy = buildCurrentCreateBy(SecurityUtils.getLoginUser());
|
||||
if (!currentCreateBy.equals(workflow.getCreateBy()))
|
||||
{
|
||||
throw new ServiceException("只有创建者可以编辑该流程");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询利率定价流程列表
|
||||
*
|
||||
@@ -220,9 +312,6 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
{
|
||||
LoanPricingWorkflow scopedQuery = applyWorkflowListDataScope(loanPricingWorkflow);
|
||||
IPage<LoanPricingWorkflowListVO> pageResult = loanPricingWorkflowMapper.selectWorkflowPageWithRates(page, scopedQuery);
|
||||
pageResult.getRecords().forEach(row -> row.setCustName(
|
||||
loanPricingSensitiveDisplayService.maskCustName(
|
||||
sensitiveFieldCryptoService.decrypt(row.getCustName()))));
|
||||
return pageResult;
|
||||
}
|
||||
|
||||
@@ -240,10 +329,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(loanPricingSensitiveDisplayService.maskCustName(plainCustName));
|
||||
loanPricingWorkflow.setIdNum(loanPricingSensitiveDisplayService.maskIdNum(plainIdNum));
|
||||
loanPricingWorkflowVO.setLoanPricingWorkflow(loanPricingWorkflow);
|
||||
|
||||
if (Objects.nonNull(loanPricingWorkflow.getModelOutputId())){
|
||||
@@ -251,7 +336,6 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
ModelRetailOutputFields modelRetailOutputFields = modelRetailOutputFieldsMapper.selectById(loanPricingWorkflow.getModelOutputId());
|
||||
if (Objects.nonNull(modelRetailOutputFields))
|
||||
{
|
||||
maskModelRetailOutputBasicInfo(modelRetailOutputFields);
|
||||
loanPricingWorkflow.setLoanRate(modelRetailOutputFields.getFinalCalculateRate());
|
||||
}
|
||||
loanPricingWorkflowVO.setModelRetailOutputFields(modelRetailOutputFields);
|
||||
@@ -260,7 +344,6 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
ModelCorpOutputFields modelCorpOutputFields = modelCorpOutputFieldsMapper.selectById(loanPricingWorkflow.getModelOutputId());
|
||||
if (Objects.nonNull(modelCorpOutputFields))
|
||||
{
|
||||
maskModelCorpOutputBasicInfo(modelCorpOutputFields);
|
||||
loanPricingWorkflow.setLoanRate(modelCorpOutputFields.getFinalCalculateRate());
|
||||
}
|
||||
loanPricingWorkflowVO.setModelCorpOutputFields(modelCorpOutputFields);
|
||||
@@ -293,10 +376,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;
|
||||
@@ -306,7 +389,15 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
{
|
||||
LoanPricingWorkflow scopedQuery = query == null ? new LoanPricingWorkflow() : query;
|
||||
LoginUser loginUser = SecurityUtils.getLoginUser();
|
||||
if (!canViewAllWorkflows(loginUser))
|
||||
if (canViewAllWorkflows(loginUser))
|
||||
{
|
||||
return scopedQuery;
|
||||
}
|
||||
if (canViewBranchWorkflows(loginUser))
|
||||
{
|
||||
scopedQuery.setDataScopeDeptId(loginUser.getDeptId() == null ? -1L : loginUser.getDeptId());
|
||||
}
|
||||
else
|
||||
{
|
||||
scopedQuery.setDataScopeCreateBy(buildCurrentCreateBy(loginUser));
|
||||
}
|
||||
@@ -331,28 +422,25 @@ public class LoanPricingWorkflowServiceImpl implements ILoanPricingWorkflowServi
|
||||
|| WORKFLOW_ADMIN_ROLE_KEY.equals(role.getRoleKey())));
|
||||
}
|
||||
|
||||
private boolean canViewBranchWorkflows(LoginUser loginUser)
|
||||
{
|
||||
List<SysRole> roles = loginUser.getUser().getRoles();
|
||||
if (roles == null)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
return roles.stream().anyMatch(role -> role != null
|
||||
&& UserConstants.ROLE_NORMAL.equals(role.getStatus())
|
||||
&& (WORKFLOW_BRANCH_ADMIN_ROLE_NAME.equals(role.getRoleName())
|
||||
|| WORKFLOW_BRANCH_ADMIN_ROLE_KEY.equals(role.getRoleKey())));
|
||||
}
|
||||
|
||||
private String buildCurrentCreateBy(LoginUser loginUser)
|
||||
{
|
||||
SysUser user = loginUser.getUser();
|
||||
return user.getNickName() + "-" + loginUser.getUsername();
|
||||
}
|
||||
|
||||
private void maskModelRetailOutputBasicInfo(ModelRetailOutputFields modelRetailOutputFields)
|
||||
{
|
||||
modelRetailOutputFields.setCustName(
|
||||
loanPricingSensitiveDisplayService.maskCustName(modelRetailOutputFields.getCustName()));
|
||||
modelRetailOutputFields.setIdNum(
|
||||
loanPricingSensitiveDisplayService.maskIdNum(modelRetailOutputFields.getIdNum()));
|
||||
}
|
||||
|
||||
private void maskModelCorpOutputBasicInfo(ModelCorpOutputFields modelCorpOutputFields)
|
||||
{
|
||||
modelCorpOutputFields.setCustName(
|
||||
loanPricingSensitiveDisplayService.maskCustName(modelCorpOutputFields.getCustName()));
|
||||
modelCorpOutputFields.setIdNum(
|
||||
loanPricingSensitiveDisplayService.maskIdNum(modelCorpOutputFields.getIdNum()));
|
||||
}
|
||||
|
||||
/**
|
||||
* 设定执行利率
|
||||
*
|
||||
@@ -375,4 +463,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -28,13 +28,17 @@ public class LoanPricingConverter {
|
||||
entity.setIdNum(dto.getIdNum());
|
||||
entity.setGuarType(dto.getGuarType());
|
||||
entity.setApplyAmt(dto.getApplyAmt());
|
||||
entity.setLoanPurpose(dto.getLoanPurpose());
|
||||
entity.setBusinessType(dto.getBusinessType());
|
||||
entity.setLoanRateHistory(dto.getLoanRateHistory());
|
||||
entity.setCouponRate(dto.getCouponRate());
|
||||
entity.setLoanTerm(dto.getLoanTerm());
|
||||
entity.setCollateralNature(dto.getCollateralNature());
|
||||
entity.setIsNineArea(dto.getIsNineArea());
|
||||
entity.setCollType(dto.getCollType());
|
||||
entity.setCollThirdParty(dto.getCollThirdParty());
|
||||
// 映射个人特有字段
|
||||
entity.setBizProof(dto.getBizProof());
|
||||
entity.setLoanLoop(dto.getLoanLoop());
|
||||
return entity;
|
||||
}
|
||||
@@ -58,7 +62,10 @@ public class LoanPricingConverter {
|
||||
entity.setBusinessType(dto.getBusinessType());
|
||||
entity.setLoanRateHistory(dto.getLoanRateHistory());
|
||||
entity.setCouponRate(dto.getCouponRate());
|
||||
entity.setResCover(dto.getResCover());
|
||||
entity.setRepayMethod(dto.getRepayMethod());
|
||||
entity.setCollateralNature(dto.getCollateralNature());
|
||||
entity.setIsNineArea(dto.getIsNineArea());
|
||||
entity.setCollType(dto.getCollType());
|
||||
entity.setCollThirdParty(dto.getCollThirdParty());
|
||||
entity.setIsGreenLoan(dto.getIsGreenLoan());
|
||||
|
||||
@@ -20,13 +20,13 @@
|
||||
"loanAvg": "3000000.00",
|
||||
"derivationRate": "1.8",
|
||||
"totalBpContribution": "12.3",
|
||||
"midEntConnect": "1",
|
||||
"midEntEffect": "1",
|
||||
"midEntInter": "1",
|
||||
"midEntAccept": "1",
|
||||
"midEntDiscount": "1",
|
||||
"midEntEleDdc": "1",
|
||||
"midEntWaterDdc": "1",
|
||||
"midEntConnect": "-2",
|
||||
"midEntEffect": "0",
|
||||
"midEntInter": "-3",
|
||||
"midEntAccept": "0",
|
||||
"midEntDiscount": "-1",
|
||||
"midEntEleDdc": "0",
|
||||
"midEntWaterDdc": "-1",
|
||||
"bpMid": "6.8",
|
||||
"payroll": "200",
|
||||
"invLoanAmount": "2500000.00",
|
||||
@@ -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",
|
||||
@@ -45,10 +45,15 @@
|
||||
"collThirdParty": "0",
|
||||
"bpCollateral": "4.5",
|
||||
"greyCust": "0",
|
||||
"blackCust": "1",
|
||||
"prinOverdue": "0",
|
||||
"interestOverdue": "0",
|
||||
"bpPrinOverdue": "0",
|
||||
"interestOverdue": "3",
|
||||
"bpIinterestOverdue": "0",
|
||||
"cardOverdue": "0",
|
||||
"bpCardOverdue": "0",
|
||||
"bpGreyOverdue": "0",
|
||||
"totoalBpRisk": "1.2",
|
||||
"totalBpRisk": "1.2",
|
||||
"totalBp": "48.2",
|
||||
"baseLoanRate": "3.45",
|
||||
"calculateRate": "3.932",
|
||||
@@ -56,7 +61,8 @@
|
||||
"minRateProduct": "3.10",
|
||||
"smoothRange": "-0.20",
|
||||
"finalCalculateRate": "3.732",
|
||||
"referenceRate": "3.432"
|
||||
"referenceRate": "3.432",
|
||||
"branchBp": "-5"
|
||||
},
|
||||
"extensionMap": {},
|
||||
"reasonMessage": "Running successfully",
|
||||
|
||||
@@ -22,20 +22,20 @@
|
||||
"loanAvg": "100000.00",
|
||||
"derivationRate": "1.2",
|
||||
"totalBpContribution": "88",
|
||||
"midPerCard": "1000.50",
|
||||
"midPerPass": "500.00",
|
||||
"midPerHarvest": "800.20",
|
||||
"midPerEffect": "是",
|
||||
"midPerQuickPay": "300.00",
|
||||
"midPerEleDdc": "150.00",
|
||||
"midPerWaterDdc": "80.00",
|
||||
"midPerHuashuDdc": "120.00",
|
||||
"MidPerGasDdc": "90.00",
|
||||
"midPerCitizencard": "200.00",
|
||||
"midPerFinMan": "5000.00",
|
||||
"midPerEtc": "180.00",
|
||||
"midPerCard": "-2",
|
||||
"midPerPass": "0",
|
||||
"midPerHarvest": "-3",
|
||||
"midPerEffect": "0",
|
||||
"midPerQuickPay": "-1",
|
||||
"midPerEleDdc": "0",
|
||||
"midPerWaterDdc": "-1",
|
||||
"midPerHuashuDdc": "0",
|
||||
"MidPerGasDdc": "-1",
|
||||
"midPerCitizencard": "0",
|
||||
"midPerFinMan": "-4",
|
||||
"midPerEtc": "0",
|
||||
"bpMid": "45",
|
||||
"totoalBpRelevance": "90",
|
||||
"totalBpRelevance": "90",
|
||||
"applyAmt": "200000.00",
|
||||
"bpLoanAmount": "60",
|
||||
"loanPurpose": "个人消费",
|
||||
@@ -47,18 +47,23 @@
|
||||
"collThirdParty": "否",
|
||||
"bpCollateral": "0",
|
||||
"greyCust": "否",
|
||||
"blackCust": "否",
|
||||
"prinOverdue": "否",
|
||||
"bpPrinOverdue": "0",
|
||||
"interestOverdue": "否",
|
||||
"bpIinterestOverdue": "0",
|
||||
"cardOverdue": "否",
|
||||
"bpCardOverdue": "0",
|
||||
"bpGreyOverdue": "98",
|
||||
"totoalBpRisk": "95",
|
||||
"totalBpRisk": "95",
|
||||
"totalBp": "350",
|
||||
"calculateRate": "6.15",
|
||||
"loanRateHistory": "6.40",
|
||||
"minRateProduct": "5.50",
|
||||
"smoothRange": "-0.10",
|
||||
"finalCalculateRate": "6.05",
|
||||
"referenceRate": "5.95"
|
||||
"referenceRate": "5.95",
|
||||
"branchBp": "-8"
|
||||
},
|
||||
"extensionMap": {},
|
||||
"reasonMessage": "Running successfully",
|
||||
|
||||
@@ -7,13 +7,15 @@
|
||||
<select id="selectWorkflowPageWithRates" resultType="com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowListVO">
|
||||
SELECT
|
||||
lpw.serial_num AS serialNum,
|
||||
lpw.dept_id AS deptId,
|
||||
lpw.cust_isn AS custIsn,
|
||||
lpw.cust_name AS custName,
|
||||
lpw.cust_type AS custType,
|
||||
lpw.guar_type AS guarType,
|
||||
lpw.apply_amt AS applyAmt,
|
||||
CASE
|
||||
WHEN lpw.cust_type = '个人' THEN mr.final_calculate_rate
|
||||
WHEN lpw.cust_type = '企业' THEN mc.calculate_rate
|
||||
WHEN lpw.cust_type = '企业' THEN mc.final_calculate_rate
|
||||
ELSE NULL
|
||||
END AS calculateRate,
|
||||
lpw.execute_rate AS executeRate,
|
||||
@@ -26,17 +28,55 @@
|
||||
<if test="query != null and query.dataScopeCreateBy != null and query.dataScopeCreateBy != ''">
|
||||
AND lpw.create_by = #{query.dataScopeCreateBy}
|
||||
</if>
|
||||
<if test="query != null and query.dataScopeDeptId != null">
|
||||
AND lpw.dept_id IN (
|
||||
SELECT dept_id
|
||||
FROM sys_dept
|
||||
WHERE del_flag = '0'
|
||||
AND (dept_id = #{query.dataScopeDeptId}
|
||||
OR find_in_set(#{query.dataScopeDeptId}, ancestors))
|
||||
)
|
||||
</if>
|
||||
<if test="query != null and query.createBy != null and query.createBy != ''">
|
||||
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>
|
||||
<if test="query != null and query.custType != null and query.custType != ''">
|
||||
AND lpw.cust_type = #{query.custType}
|
||||
</if>
|
||||
<if test="query != null and query.guarType != null and query.guarType != ''">
|
||||
AND lpw.guar_type = #{query.guarType}
|
||||
</if>
|
||||
<if test="query != null and query.beginCreateTime != null">
|
||||
AND lpw.create_time >= #{query.beginCreateTime}
|
||||
</if>
|
||||
<if test="query != null and query.endCreateTime != null">
|
||||
AND lpw.create_time <= #{query.endCreateTime}
|
||||
</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>
|
||||
|
||||
@@ -23,5 +23,10 @@ class ModelRetailOutputFieldsTest
|
||||
assertTrue(fieldNames.contains("finalCalculateRate"), "缺少字段 finalCalculateRate");
|
||||
assertTrue(fieldNames.contains("referenceRate"), "缺少字段 referenceRate");
|
||||
assertTrue(fieldNames.contains("greyBlackCust"), "缺少字段 greyBlackCust");
|
||||
assertTrue(fieldNames.contains("blackCust"), "缺少字段 blackCust");
|
||||
assertTrue(fieldNames.contains("branchBp"), "缺少字段 branchBp");
|
||||
assertTrue(fieldNames.contains("bpPrinOverdue"), "缺少字段 bpPrinOverdue");
|
||||
assertTrue(fieldNames.contains("bpIinterestOverdue"), "缺少字段 bpIinterestOverdue");
|
||||
assertTrue(fieldNames.contains("bpCardOverdue"), "缺少字段 bpCardOverdue");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,9 +10,13 @@ class LoanPricingWorkflowListVOTest
|
||||
void shouldExposeCalculateRateAndExecuteRateFields()
|
||||
{
|
||||
LoanPricingWorkflowListVO vo = new LoanPricingWorkflowListVO();
|
||||
vo.setDeptId(100L);
|
||||
vo.setCustIsn("CUST001");
|
||||
vo.setCalculateRate("6.15");
|
||||
vo.setExecuteRate("5.80");
|
||||
|
||||
assertEquals(100L, vo.getDeptId());
|
||||
assertEquals("CUST001", vo.getCustIsn());
|
||||
assertEquals("6.15", vo.getCalculateRate());
|
||||
assertEquals("5.80", vo.getExecuteRate());
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -17,8 +18,34 @@ class LoanPricingWorkflowMapperXmlTest
|
||||
String xml = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
|
||||
|
||||
assertTrue(xml.contains("WHEN lpw.cust_type = '个人' THEN mr.final_calculate_rate"));
|
||||
assertTrue(xml.contains("WHEN lpw.cust_type = '企业' THEN mc.calculate_rate"));
|
||||
assertTrue(xml.contains("WHEN lpw.cust_type = '企业' THEN mc.final_calculate_rate"));
|
||||
assertTrue(xml.contains("lpw.dept_id AS deptId"));
|
||||
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}, '%')"));
|
||||
assertTrue(xml.contains("lpw.cust_type = #{query.custType}"));
|
||||
assertTrue(xml.contains("lpw.guar_type = #{query.guarType}"));
|
||||
assertTrue(xml.contains("lpw.create_time >= #{query.beginCreateTime}"));
|
||||
assertTrue(xml.contains("lpw.create_time <= #{query.endCreateTime}"));
|
||||
}
|
||||
|
||||
@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"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,8 +18,6 @@ import java.util.Objects;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.argThat;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -40,35 +38,32 @@ class LoanPricingModelServicePersonalParamsTest {
|
||||
@Mock
|
||||
private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper;
|
||||
|
||||
@Mock
|
||||
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
|
||||
|
||||
@InjectMocks
|
||||
private LoanPricingModelService loanPricingModelService;
|
||||
|
||||
@Test
|
||||
void shouldRemoveLoanPurposeAndKeepLoanTermInPersonalCreateDto() throws NoSuchFieldException {
|
||||
assertThrows(NoSuchFieldException.class,
|
||||
() -> PersonalLoanPricingCreateDTO.class.getDeclaredField("loanPurpose"));
|
||||
assertThrows(NoSuchFieldException.class,
|
||||
() -> PersonalLoanPricingCreateDTO.class.getDeclaredField("bizProof"));
|
||||
void shouldContainLoanPurposeAndPersonalModelFieldsInCreateDto() throws NoSuchFieldException {
|
||||
assertNotNull(PersonalLoanPricingCreateDTO.class.getDeclaredField("loanPurpose"));
|
||||
assertNotNull(PersonalLoanPricingCreateDTO.class.getDeclaredField("bizProof"));
|
||||
assertNotNull(PersonalLoanPricingCreateDTO.class.getDeclaredField("loanTerm"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldNotMapLoanPurposeAndBizProofFromPersonalDto() {
|
||||
void shouldMapLoanPurposeAndBizProofFromPersonalDto() {
|
||||
PersonalLoanPricingCreateDTO dto = new PersonalLoanPricingCreateDTO();
|
||||
dto.setCustIsn("CUST001");
|
||||
dto.setCustName("张三");
|
||||
dto.setGuarType("信用");
|
||||
dto.setApplyAmt("100000");
|
||||
dto.setLoanPurpose("business");
|
||||
dto.setBusinessType("新增");
|
||||
dto.setLoanTerm("3");
|
||||
dto.setBizProof("1");
|
||||
|
||||
LoanPricingWorkflow workflow = LoanPricingConverter.toEntity(dto);
|
||||
|
||||
assertNull(workflow.getLoanPurpose());
|
||||
assertNull(workflow.getBizProof());
|
||||
assertEquals("business", workflow.getLoanPurpose());
|
||||
assertEquals("1", workflow.getBizProof());
|
||||
assertEquals("3", workflow.getLoanTerm());
|
||||
}
|
||||
|
||||
@@ -87,12 +82,14 @@ 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.setLoanPurpose("consumer");
|
||||
workflow.setLoanTerm("3");
|
||||
workflow.setBizProof("true");
|
||||
workflow.setLoanLoop("false");
|
||||
workflow.setCollThirdParty("true");
|
||||
workflow.setCollType("一类");
|
||||
@@ -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);
|
||||
@@ -118,9 +113,27 @@ class LoanPricingModelServicePersonalParamsTest {
|
||||
&& Objects.equals("110101199001011234", dto.getIdNum())
|
||||
&& Objects.equals("信用", dto.getGuarType())
|
||||
&& Objects.equals("100000", dto.getApplyAmt())
|
||||
&& Objects.equals("消费", dto.getLoanPurpose())
|
||||
&& Objects.equals("3", dto.getLoanTerm())
|
||||
&& Objects.equals("1", dto.getBizProof())
|
||||
&& Objects.equals("0", dto.getLoanLoop())
|
||||
&& Objects.equals("1", dto.getCollThirdParty())
|
||||
&& Objects.equals("一类", dto.getCollType())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldConvertBusinessLoanPurposeForPersonalModel() {
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setId(2L);
|
||||
workflow.setCustType("个人");
|
||||
workflow.setLoanPurpose("business");
|
||||
|
||||
when(loanPricingWorkflowMapper.selectById(2L)).thenReturn(workflow);
|
||||
when(modelService.invokePersonalModel(any())).thenReturn(new ModelRetailOutputFields());
|
||||
|
||||
loanPricingModelService.invokeModelAsync(2L);
|
||||
|
||||
verify(modelService).invokePersonalModel(argThat((ModelInvokeDTO dto) ->
|
||||
Objects.equals("经营", dto.getLoanPurpose())));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
|
||||
@@ -30,6 +29,7 @@ class LoanPricingModelServiceTest
|
||||
assertEquals("张三", context.modelService.personalRequest.getCustName());
|
||||
assertEquals("110101199001011234", context.modelService.personalRequest.getIdNum());
|
||||
assertEquals("2.15", context.modelService.personalRequest.getCouponRate());
|
||||
assertEquals("1", context.modelService.personalRequest.getBizProof());
|
||||
assertEquals(1, context.modelService.personalCalls.get());
|
||||
assertEquals(0, context.modelService.corporateCalls.get());
|
||||
assertEquals(1, context.retailInsertCount.get());
|
||||
@@ -37,15 +37,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,9 +55,10 @@ 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");
|
||||
workflow.setIsTradeBuildEnt("false");
|
||||
workflow.setCollThirdParty("true");
|
||||
@@ -67,6 +69,7 @@ class LoanPricingModelServiceTest
|
||||
|
||||
ModelInvokeDTO request = context.modelService.corporateRequest;
|
||||
assertEquals("分期", request.getRepayMethod());
|
||||
assertEquals("1", request.getResCover());
|
||||
assertEquals("1", request.getIsGreenLoan());
|
||||
assertEquals("0", request.getIsTradeBuildEnt());
|
||||
assertEquals("1", request.getCollThirdParty());
|
||||
@@ -77,14 +80,33 @@ class LoanPricingModelServiceTest
|
||||
assertEquals(1, context.corpInsertCount.get());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldOverwriteExistingCorporateModelOutputWhenReinvokingModel() throws Exception
|
||||
{
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setId(4L);
|
||||
workflow.setModelOutputId(88L);
|
||||
workflow.setCustType("企业");
|
||||
workflow.setCustName("测试科技有限公司");
|
||||
workflow.setIdNum("91110000100000000X");
|
||||
TestContext context = createContext(workflow);
|
||||
|
||||
context.service.reinvokeModelAndOverwrite(4L);
|
||||
|
||||
assertEquals(1, context.modelService.corporateCalls.get());
|
||||
assertEquals(0, context.corpInsertCount.get());
|
||||
assertEquals(1, context.corpUpdateCount.get());
|
||||
}
|
||||
|
||||
private static LoanPricingWorkflow personalWorkflow(Long id)
|
||||
{
|
||||
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");
|
||||
workflow.setBizProof("true");
|
||||
return workflow;
|
||||
}
|
||||
|
||||
@@ -96,16 +118,17 @@ class LoanPricingModelServiceTest
|
||||
context.workflow = workflow;
|
||||
context.updatedWorkflow = new AtomicReference<>();
|
||||
context.retailInsertCount = new AtomicInteger();
|
||||
context.retailUpdateCount = new AtomicInteger();
|
||||
context.corpInsertCount = new AtomicInteger();
|
||||
context.corpUpdateCount = new AtomicInteger();
|
||||
|
||||
setField(context.service, "modelService", context.modelService);
|
||||
setField(context.service, "loanPricingWorkflowMapper",
|
||||
workflowMapper(context.workflow, context.updatedWorkflow));
|
||||
setField(context.service, "modelRetailOutputFieldsMapper",
|
||||
insertCountingMapper(ModelRetailOutputFieldsMapper.class, context.retailInsertCount));
|
||||
savingMapper(ModelRetailOutputFieldsMapper.class, context.retailInsertCount, context.retailUpdateCount));
|
||||
setField(context.service, "modelCorpOutputFieldsMapper",
|
||||
insertCountingMapper(ModelCorpOutputFieldsMapper.class, context.corpInsertCount));
|
||||
setField(context.service, "sensitiveFieldCryptoService", new TestSensitiveFieldCryptoService());
|
||||
savingMapper(ModelCorpOutputFieldsMapper.class, context.corpInsertCount, context.corpUpdateCount));
|
||||
return context;
|
||||
}
|
||||
|
||||
@@ -126,7 +149,7 @@ class LoanPricingModelServiceTest
|
||||
});
|
||||
}
|
||||
|
||||
private static <T> T insertCountingMapper(Class<T> mapperClass, AtomicInteger insertCount)
|
||||
private static <T> T savingMapper(Class<T> mapperClass, AtomicInteger insertCount, AtomicInteger updateCount)
|
||||
{
|
||||
return proxy(mapperClass, (proxy, method, args) -> {
|
||||
if ("insert".equals(method.getName()))
|
||||
@@ -134,6 +157,11 @@ class LoanPricingModelServiceTest
|
||||
insertCount.incrementAndGet();
|
||||
return 1;
|
||||
}
|
||||
if ("updateById".equals(method.getName()))
|
||||
{
|
||||
updateCount.incrementAndGet();
|
||||
return 1;
|
||||
}
|
||||
return defaultValue(method);
|
||||
});
|
||||
}
|
||||
@@ -172,7 +200,9 @@ class LoanPricingModelServiceTest
|
||||
private LoanPricingWorkflow workflow;
|
||||
private AtomicReference<LoanPricingWorkflow> updatedWorkflow;
|
||||
private AtomicInteger retailInsertCount;
|
||||
private AtomicInteger retailUpdateCount;
|
||||
private AtomicInteger corpInsertCount;
|
||||
private AtomicInteger corpUpdateCount;
|
||||
}
|
||||
|
||||
private static class CapturingModelService extends ModelService
|
||||
@@ -199,25 +229,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,24 +0,0 @@
|
||||
package com.ruoyi.loanpricing.service;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class LoanPricingSensitiveDisplayServiceTest
|
||||
{
|
||||
private final LoanPricingSensitiveDisplayService displayService = new LoanPricingSensitiveDisplayService();
|
||||
|
||||
@Test
|
||||
void shouldMaskPersonalNameAndIdNum()
|
||||
{
|
||||
assertEquals("张*", displayService.maskCustName("张三"));
|
||||
assertEquals("1101********1234", displayService.maskIdNum("110101199001011234"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMaskCorporateNameAndCreditCode()
|
||||
{
|
||||
assertEquals("测试****公司", displayService.maskCustName("测试科技有限公司"));
|
||||
assertEquals("91*************00X", displayService.maskIdNum("91110000100000000X"));
|
||||
}
|
||||
}
|
||||
@@ -6,11 +6,13 @@ 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;
|
||||
|
||||
import com.baomidou.mybatisplus.core.MybatisConfiguration;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
@@ -29,7 +31,6 @@ import com.ruoyi.loanpricing.domain.vo.LoanPricingWorkflowVO;
|
||||
import com.ruoyi.loanpricing.mapper.LoanPricingWorkflowMapper;
|
||||
import com.ruoyi.loanpricing.mapper.ModelCorpOutputFieldsMapper;
|
||||
import com.ruoyi.loanpricing.mapper.ModelRetailOutputFieldsMapper;
|
||||
import com.ruoyi.loanpricing.service.LoanPricingSensitiveDisplayService;
|
||||
import com.ruoyi.loanpricing.service.LoanPricingModelService;
|
||||
import com.ruoyi.loanpricing.service.SensitiveFieldCryptoService;
|
||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||
@@ -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)
|
||||
@@ -65,9 +68,6 @@ class LoanPricingWorkflowServiceImplTest
|
||||
@Mock
|
||||
private SensitiveFieldCryptoService sensitiveFieldCryptoService;
|
||||
|
||||
@Mock
|
||||
private LoanPricingSensitiveDisplayService loanPricingSensitiveDisplayService;
|
||||
|
||||
@InjectMocks
|
||||
private LoanPricingWorkflowServiceImpl loanPricingWorkflowService;
|
||||
|
||||
@@ -84,7 +84,7 @@ class LoanPricingWorkflowServiceImplTest
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldEncryptCustNameAndIdNumBeforeInsert()
|
||||
void shouldKeepCustNameAndIdNumPlainBeforeInsert()
|
||||
{
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setCustName("张三");
|
||||
@@ -92,15 +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
|
||||
@@ -120,21 +119,20 @@ class LoanPricingWorkflowServiceImplTest
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMaskCustNameWhenReturningPagedWorkflowList()
|
||||
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("张三");
|
||||
when(loanPricingSensitiveDisplayService.maskCustName("张三")).thenReturn("张*");
|
||||
|
||||
IPage<LoanPricingWorkflowListVO> result = loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), new LoanPricingWorkflow());
|
||||
|
||||
assertEquals("张*", result.getRecords().get(0).getCustName());
|
||||
assertEquals("张三", result.getRecords().get(0).getCustName());
|
||||
verify(sensitiveFieldCryptoService, never()).decrypt(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -148,6 +146,7 @@ class LoanPricingWorkflowServiceImplTest
|
||||
verify(loanPricingWorkflowMapper).selectWorkflowPageWithRates(any(), queryCaptor.capture());
|
||||
|
||||
assertNull(queryCaptor.getValue().getDataScopeCreateBy());
|
||||
assertNull(queryCaptor.getValue().getDataScopeDeptId());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -162,6 +161,40 @@ class LoanPricingWorkflowServiceImplTest
|
||||
verify(loanPricingWorkflowMapper).selectWorkflowPageWithRates(any(), queryCaptor.capture());
|
||||
|
||||
assertNull(queryCaptor.getValue().getDataScopeCreateBy());
|
||||
assertNull(queryCaptor.getValue().getDataScopeDeptId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldSetCurrentDeptForBranchAdminWhenReturningPagedWorkflowList()
|
||||
{
|
||||
setLoginUser(102L, 101L, "8920100", "测试支行管理员", role(102L, "支行管理员", "branchAdmin"));
|
||||
when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(emptyPageResult());
|
||||
|
||||
loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), new LoanPricingWorkflow());
|
||||
|
||||
ArgumentCaptor<LoanPricingWorkflow> queryCaptor = ArgumentCaptor.forClass(LoanPricingWorkflow.class);
|
||||
verify(loanPricingWorkflowMapper).selectWorkflowPageWithRates(any(), queryCaptor.capture());
|
||||
|
||||
assertEquals(101L, queryCaptor.getValue().getDataScopeDeptId());
|
||||
assertNull(queryCaptor.getValue().getDataScopeCreateBy());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepBranchAdminWithinDeptDataScopeWhenCreateByQueryIsProvided()
|
||||
{
|
||||
setLoginUser(102L, 101L, "8920100", "测试支行管理员", role(102L, "支行管理员", "branchAdmin"));
|
||||
LoanPricingWorkflow query = new LoanPricingWorkflow();
|
||||
query.setCreateBy("8920001");
|
||||
when(loanPricingWorkflowMapper.selectWorkflowPageWithRates(any(), any())).thenReturn(emptyPageResult());
|
||||
|
||||
loanPricingWorkflowService.selectLoanPricingPage(new Page<>(1, 10), query);
|
||||
|
||||
ArgumentCaptor<LoanPricingWorkflow> queryCaptor = ArgumentCaptor.forClass(LoanPricingWorkflow.class);
|
||||
verify(loanPricingWorkflowMapper).selectWorkflowPageWithRates(any(), queryCaptor.capture());
|
||||
|
||||
assertEquals("8920001", queryCaptor.getValue().getCreateBy());
|
||||
assertEquals(101L, queryCaptor.getValue().getDataScopeDeptId());
|
||||
assertNull(queryCaptor.getValue().getDataScopeCreateBy());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -193,10 +226,11 @@ class LoanPricingWorkflowServiceImplTest
|
||||
|
||||
assertEquals("若依-admin", queryCaptor.getValue().getCreateBy());
|
||||
assertEquals("测试客户经理-8920001", queryCaptor.getValue().getDataScopeCreateBy());
|
||||
assertNull(queryCaptor.getValue().getDataScopeDeptId());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseCustIsnInsteadOfCustNameAsQueryCondition()
|
||||
void shouldUseCustIsnAsQueryConditionInLegacyList()
|
||||
{
|
||||
LoanPricingWorkflow query = new LoanPricingWorkflow();
|
||||
query.setCustIsn("CUST001");
|
||||
@@ -256,36 +290,67 @@ class LoanPricingWorkflowServiceImplTest
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAllowPersonalMortgageOneLineAndRejectOldOther()
|
||||
void shouldAllowPersonalMortgageFourthCategoryAndRejectOldFirstLine()
|
||||
{
|
||||
LoanPricingWorkflow allowedWorkflow = validWorkflow();
|
||||
allowedWorkflow.setCustType("个人");
|
||||
allowedWorkflow.setGuarType("抵押");
|
||||
allowedWorkflow.setCollType("一线");
|
||||
allowedWorkflow.setCollType("四类");
|
||||
|
||||
loanPricingWorkflowService.createLoanPricing(allowedWorkflow);
|
||||
|
||||
LoanPricingWorkflow rejectedWorkflow = validWorkflow();
|
||||
rejectedWorkflow.setCustType("个人");
|
||||
rejectedWorkflow.setGuarType("抵押");
|
||||
rejectedWorkflow.setCollType("其他");
|
||||
rejectedWorkflow.setCollType("一线");
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> loanPricingWorkflowService.createLoanPricing(rejectedWorkflow));
|
||||
|
||||
assertEquals("个人抵押抵质押类型必须是:一线、一类、二类、三类之一", exception.getMessage());
|
||||
assertEquals("个人抵押抵质押类型必须是:一类、二类、三类、四类之一", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldAllowCorporateMortgagePollutionRightAndPledgeEquity()
|
||||
void shouldAllowCorporateNewMortgageTypesAndRejectOldMortgageTypes()
|
||||
{
|
||||
LoanPricingWorkflow mortgageWorkflow = validWorkflow();
|
||||
mortgageWorkflow.setCustType("企业");
|
||||
mortgageWorkflow.setGuarType("抵押");
|
||||
mortgageWorkflow.setCollType("排污权抵押");
|
||||
LoanPricingWorkflow pollutionRightWorkflow = validWorkflow();
|
||||
pollutionRightWorkflow.setCustType("企业");
|
||||
pollutionRightWorkflow.setGuarType("抵押");
|
||||
pollutionRightWorkflow.setCollType("排污权抵押");
|
||||
|
||||
loanPricingWorkflowService.createLoanPricing(mortgageWorkflow);
|
||||
loanPricingWorkflowService.createLoanPricing(pollutionRightWorkflow);
|
||||
|
||||
LoanPricingWorkflow otherRealEstateWorkflow = validWorkflow();
|
||||
otherRealEstateWorkflow.setCustType("企业");
|
||||
otherRealEstateWorkflow.setGuarType("抵押");
|
||||
otherRealEstateWorkflow.setCollType("其他不动产抵押");
|
||||
|
||||
loanPricingWorkflowService.createLoanPricing(otherRealEstateWorkflow);
|
||||
|
||||
LoanPricingWorkflow oldFourthCategoryWorkflow = validWorkflow();
|
||||
oldFourthCategoryWorkflow.setCustType("企业");
|
||||
oldFourthCategoryWorkflow.setGuarType("抵押");
|
||||
oldFourthCategoryWorkflow.setCollType("四类");
|
||||
|
||||
ServiceException fourthCategoryException = assertThrows(ServiceException.class,
|
||||
() -> loanPricingWorkflowService.createLoanPricing(oldFourthCategoryWorkflow));
|
||||
|
||||
LoanPricingWorkflow oldOtherRealEstateWorkflow = validWorkflow();
|
||||
oldOtherRealEstateWorkflow.setCustType("企业");
|
||||
oldOtherRealEstateWorkflow.setGuarType("抵押");
|
||||
oldOtherRealEstateWorkflow.setCollType("设备等其他不动产抵押");
|
||||
|
||||
ServiceException otherRealEstateException = assertThrows(ServiceException.class,
|
||||
() -> loanPricingWorkflowService.createLoanPricing(oldOtherRealEstateWorkflow));
|
||||
|
||||
String expectedMessage = "企业抵押抵质押类型必须是:一类、二类、三类、排污权抵押、其他不动产抵押之一";
|
||||
assertEquals(expectedMessage, fourthCategoryException.getMessage());
|
||||
assertEquals(expectedMessage, otherRealEstateException.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldKeepCorporatePledgeTypesUnchanged()
|
||||
{
|
||||
LoanPricingWorkflow pledgeWorkflow = validWorkflow();
|
||||
pledgeWorkflow.setCustType("企业");
|
||||
pledgeWorkflow.setGuarType("质押");
|
||||
@@ -305,7 +370,7 @@ class LoanPricingWorkflowServiceImplTest
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreatePersonalWorkflowWithCouponRateAndWithoutRemovedFields()
|
||||
void shouldCreatePersonalWorkflowWithCouponRateAndBizProof()
|
||||
{
|
||||
PersonalLoanPricingCreateDTO dto = new PersonalLoanPricingCreateDTO();
|
||||
dto.setCustIsn("CUST001");
|
||||
@@ -315,6 +380,7 @@ class LoanPricingWorkflowServiceImplTest
|
||||
dto.setLoanTerm("3");
|
||||
dto.setCollType("存单质押");
|
||||
dto.setCouponRate("2.15");
|
||||
dto.setBizProof("1");
|
||||
|
||||
loanPricingWorkflowService.createPersonalLoanPricing(dto);
|
||||
|
||||
@@ -322,7 +388,7 @@ class LoanPricingWorkflowServiceImplTest
|
||||
Objects.equals("个人", entity.getCustType())
|
||||
&& Objects.equals("2.15", entity.getCouponRate())
|
||||
&& Objects.isNull(entity.getLoanPurpose())
|
||||
&& Objects.isNull(entity.getBizProof())));
|
||||
&& Objects.equals("1", entity.getBizProof())));
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -336,15 +402,206 @@ class LoanPricingWorkflowServiceImplTest
|
||||
dto.setLoanTerm("3");
|
||||
dto.setCollType("存单质押");
|
||||
dto.setCouponRate("2.35");
|
||||
dto.setResCover("1");
|
||||
|
||||
loanPricingWorkflowService.createCorporateLoanPricing(dto);
|
||||
|
||||
verify(loanPricingWorkflowMapper).insert(argThat((LoanPricingWorkflow entity) ->
|
||||
Objects.equals("企业", entity.getCustType())
|
||||
&& Objects.equals("新增", entity.getBusinessType())
|
||||
&& Objects.equals("1", entity.getResCover())
|
||||
&& Objects.equals("2.35", entity.getCouponRate())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreatePersonalWorkflowWithMortgageSourceFields()
|
||||
{
|
||||
PersonalLoanPricingCreateDTO dto = new PersonalLoanPricingCreateDTO();
|
||||
dto.setCustIsn("CUST001");
|
||||
dto.setGuarType("抵押");
|
||||
dto.setApplyAmt("100000");
|
||||
dto.setBusinessType("新增");
|
||||
dto.setLoanTerm("3");
|
||||
dto.setCollateralNature("普通住宅、土地、厂房");
|
||||
dto.setIsNineArea("是");
|
||||
dto.setCollType("一类");
|
||||
|
||||
loanPricingWorkflowService.createPersonalLoanPricing(dto);
|
||||
|
||||
verify(loanPricingWorkflowMapper).insert(argThat((LoanPricingWorkflow entity) ->
|
||||
Objects.equals("普通住宅、土地、厂房", entity.getCollateralNature())
|
||||
&& Objects.equals("是", entity.getIsNineArea())
|
||||
&& Objects.equals("一类", entity.getCollType())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldCreateCorporateWorkflowWithMortgageSourceFields()
|
||||
{
|
||||
CorporateLoanPricingCreateDTO dto = new CorporateLoanPricingCreateDTO();
|
||||
dto.setCustIsn("CORP001");
|
||||
dto.setGuarType("抵押");
|
||||
dto.setApplyAmt("1000000");
|
||||
dto.setBusinessType("新增");
|
||||
dto.setLoanTerm("5");
|
||||
dto.setCollateralNature("商业性质不动产");
|
||||
dto.setIsNineArea("否");
|
||||
dto.setCollType("三类");
|
||||
|
||||
loanPricingWorkflowService.createCorporateLoanPricing(dto);
|
||||
|
||||
verify(loanPricingWorkflowMapper).insert(argThat((LoanPricingWorkflow entity) ->
|
||||
Objects.equals("商业性质不动产", entity.getCollateralNature())
|
||||
&& Objects.equals("否", entity.getIsNineArea())
|
||||
&& Objects.equals("三类", entity.getCollType())));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldReturnEditableWorkflowWithPlainSensitiveFieldsForCreator()
|
||||
{
|
||||
LoanPricingWorkflow workflow = editableWorkflow("个人", "若依-admin");
|
||||
workflow.setCustName("张三");
|
||||
workflow.setIdNum("110101199001011234");
|
||||
|
||||
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
|
||||
|
||||
LoanPricingWorkflow result = loanPricingWorkflowService.selectEditableLoanPricingBySerialNum("P20260525001");
|
||||
|
||||
assertEquals("张三", result.getCustName());
|
||||
assertEquals("110101199001011234", result.getIdNum());
|
||||
verify(sensitiveFieldCryptoService, never()).decrypt(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectEditableWorkflowWhenCurrentUserIsNotCreator()
|
||||
{
|
||||
LoanPricingWorkflow workflow = editableWorkflow("个人", "其他用户-8920001");
|
||||
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> loanPricingWorkflowService.selectEditableLoanPricingBySerialNum("P20260525001"));
|
||||
|
||||
assertEquals("只有创建者可以编辑该流程", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdatePersonalWorkflowAndReinvokeModelForCreator()
|
||||
{
|
||||
LoanPricingWorkflow workflow = editableWorkflow("个人", "若依-admin");
|
||||
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
|
||||
|
||||
PersonalLoanPricingCreateDTO dto = new PersonalLoanPricingCreateDTO();
|
||||
dto.setCustIsn("CUST001");
|
||||
dto.setCustName("张三");
|
||||
dto.setIdType("身份证");
|
||||
dto.setIdNum("110101199001019999");
|
||||
dto.setGuarType("抵押");
|
||||
dto.setApplyAmt("200000");
|
||||
dto.setLoanPurpose("business");
|
||||
dto.setBusinessType("新增");
|
||||
dto.setLoanTerm("3");
|
||||
dto.setLoanLoop("1");
|
||||
dto.setBizProof("1");
|
||||
dto.setCollateralNature("普通住宅、土地、厂房");
|
||||
dto.setIsNineArea("是");
|
||||
dto.setCollType("一类");
|
||||
|
||||
loanPricingWorkflowService.updatePersonalLoanPricing("P20260525001", dto);
|
||||
|
||||
ArgumentCaptor<LambdaUpdateWrapper> updateWrapperCaptor = ArgumentCaptor.forClass(LambdaUpdateWrapper.class);
|
||||
verify(loanPricingWorkflowMapper).update(any(), updateWrapperCaptor.capture());
|
||||
String sqlSet = updateWrapperCaptor.getValue().getSqlSet();
|
||||
assertTrue(sqlSet.contains("apply_amt"));
|
||||
assertTrue(sqlSet.contains("loan_purpose"));
|
||||
assertTrue(sqlSet.contains("cust_name"));
|
||||
assertTrue(sqlSet.contains("id_num"));
|
||||
assertTrue(sqlSet.contains("biz_proof"));
|
||||
assertTrue(sqlSet.contains("collateral_nature"));
|
||||
assertTrue(sqlSet.contains("is_nine_area"));
|
||||
assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("张三"));
|
||||
assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("110101199001019999"));
|
||||
assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("普通住宅、土地、厂房"));
|
||||
assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("business"));
|
||||
assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("是"));
|
||||
assertTrue(updateWrapperCaptor.getValue().getParamNameValuePairs().containsValue("1"));
|
||||
verify(sensitiveFieldCryptoService, never()).encrypt(any());
|
||||
verify(loanPricingModelService).reinvokeModelAndOverwrite(101L);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUpdateCorporateWorkflowAndReinvokeModelForCreator()
|
||||
{
|
||||
LoanPricingWorkflow workflow = editableWorkflow("企业", "若依-admin");
|
||||
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
|
||||
|
||||
CorporateLoanPricingCreateDTO dto = new CorporateLoanPricingCreateDTO();
|
||||
dto.setCustIsn("CORP001");
|
||||
dto.setCustName("测试科技有限公司");
|
||||
dto.setIdType("统一社会信用代码");
|
||||
dto.setIdNum("91110000100000000X");
|
||||
dto.setGuarType("质押");
|
||||
dto.setApplyAmt("300000");
|
||||
dto.setBusinessType("新增");
|
||||
dto.setLoanTerm("5");
|
||||
dto.setCollType("存单质押");
|
||||
dto.setCollThirdParty("1");
|
||||
dto.setCouponRate("2.35");
|
||||
dto.setResCover("1");
|
||||
dto.setIsGreenLoan("1");
|
||||
dto.setIsTradeBuildEnt("0");
|
||||
|
||||
loanPricingWorkflowService.updateCorporateLoanPricing("C20260525001", dto);
|
||||
|
||||
ArgumentCaptor<LambdaUpdateWrapper> updateWrapperCaptor = ArgumentCaptor.forClass(LambdaUpdateWrapper.class);
|
||||
verify(loanPricingWorkflowMapper).update(any(), updateWrapperCaptor.capture());
|
||||
String sqlSet = updateWrapperCaptor.getValue().getSqlSet();
|
||||
assertTrue(sqlSet.contains("res_cover"), sqlSet);
|
||||
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);
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectUpdateWhenCustomerTypeDoesNotMatch()
|
||||
{
|
||||
LoanPricingWorkflow workflow = editableWorkflow("个人", "若依-admin");
|
||||
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
|
||||
|
||||
CorporateLoanPricingCreateDTO dto = new CorporateLoanPricingCreateDTO();
|
||||
dto.setCustIsn("CORP001");
|
||||
dto.setGuarType("信用");
|
||||
dto.setApplyAmt("300000");
|
||||
dto.setBusinessType("新增");
|
||||
dto.setLoanTerm("5");
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> loanPricingWorkflowService.updateCorporateLoanPricing("P20260525001", dto));
|
||||
|
||||
assertEquals("客户类型不匹配,不能编辑该流程", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectUpdateWhenCurrentUserIsNotCreator()
|
||||
{
|
||||
LoanPricingWorkflow workflow = editableWorkflow("个人", "其他用户-8920001");
|
||||
when(loanPricingWorkflowMapper.selectOne(any())).thenReturn(workflow);
|
||||
|
||||
PersonalLoanPricingCreateDTO dto = new PersonalLoanPricingCreateDTO();
|
||||
dto.setCustIsn("CUST001");
|
||||
dto.setGuarType("信用");
|
||||
dto.setApplyAmt("200000");
|
||||
dto.setBusinessType("新增");
|
||||
dto.setLoanTerm("3");
|
||||
|
||||
ServiceException exception = assertThrows(ServiceException.class,
|
||||
() -> loanPricingWorkflowService.updatePersonalLoanPricing("P20260525001", dto));
|
||||
|
||||
assertEquals("只有创建者可以编辑该流程", exception.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldUseRetailModelOutputFinalCalculateRateForWorkflowDetail()
|
||||
{
|
||||
@@ -369,34 +626,31 @@ class LoanPricingWorkflowServiceImplTest
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMaskCustNameAndIdNumWhenReturningWorkflowDetail()
|
||||
void shouldReturnPlainCustNameAndIdNumWhenReturningWorkflowDetail()
|
||||
{
|
||||
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");
|
||||
when(loanPricingSensitiveDisplayService.maskCustName("张三")).thenReturn("张*");
|
||||
when(loanPricingSensitiveDisplayService.maskIdNum("110101199001011234")).thenReturn("1101********1234");
|
||||
|
||||
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001");
|
||||
|
||||
assertEquals("张*", result.getLoanPricingWorkflow().getCustName());
|
||||
assertEquals("1101********1234", result.getLoanPricingWorkflow().getIdNum());
|
||||
assertEquals("张三", result.getLoanPricingWorkflow().getCustName());
|
||||
assertEquals("110101199001011234", result.getLoanPricingWorkflow().getIdNum());
|
||||
verify(sensitiveFieldCryptoService, never()).decrypt(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMaskCustNameAndIdNumInRetailModelOutputBasicInfo()
|
||||
void shouldReturnPlainCustNameAndIdNumInRetailModelOutputBasicInfo()
|
||||
{
|
||||
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();
|
||||
@@ -406,15 +660,11 @@ 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");
|
||||
when(loanPricingSensitiveDisplayService.maskCustName("张三")).thenReturn("张*");
|
||||
when(loanPricingSensitiveDisplayService.maskIdNum("110101199001011234")).thenReturn("1101********1234");
|
||||
|
||||
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("P20260328001");
|
||||
|
||||
assertEquals("张*", result.getModelRetailOutputFields().getCustName());
|
||||
assertEquals("1101********1234", result.getModelRetailOutputFields().getIdNum());
|
||||
assertEquals("张三", result.getModelRetailOutputFields().getCustName());
|
||||
assertEquals("110101199001011234", result.getModelRetailOutputFields().getIdNum());
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -441,13 +691,13 @@ class LoanPricingWorkflowServiceImplTest
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldMaskCustNameAndIdNumInCorporateModelOutputBasicInfo()
|
||||
void shouldReturnPlainCustNameAndIdNumInCorporateModelOutputBasicInfo()
|
||||
{
|
||||
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();
|
||||
@@ -457,15 +707,79 @@ 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");
|
||||
when(loanPricingSensitiveDisplayService.maskCustName("测试科技有限公司")).thenReturn("测试****公司");
|
||||
when(loanPricingSensitiveDisplayService.maskIdNum("91110000100000000X")).thenReturn("91*************00X");
|
||||
|
||||
LoanPricingWorkflowVO result = loanPricingWorkflowService.selectLoanPricingBySerialNum("C20260328001");
|
||||
|
||||
assertEquals("测试****公司", result.getModelCorpOutputFields().getCustName());
|
||||
assertEquals("91*************00X", result.getModelCorpOutputFields().getIdNum());
|
||||
assertEquals("测试科技有限公司", result.getModelCorpOutputFields().getCustName());
|
||||
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()
|
||||
@@ -479,6 +793,25 @@ class LoanPricingWorkflowServiceImplTest
|
||||
return workflow;
|
||||
}
|
||||
|
||||
private LoanPricingWorkflow editableWorkflow(String custType, String createBy)
|
||||
{
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setId(101L);
|
||||
workflow.setSerialNum("个人".equals(custType) ? "P20260525001" : "C20260525001");
|
||||
workflow.setModelOutputId(201L);
|
||||
workflow.setCustIsn("CUST001");
|
||||
workflow.setCustType(custType);
|
||||
workflow.setCustName("张三");
|
||||
workflow.setIdType("身份证");
|
||||
workflow.setIdNum("110101199001011234");
|
||||
workflow.setGuarType("信用");
|
||||
workflow.setApplyAmt("100000");
|
||||
workflow.setBusinessType("新增");
|
||||
workflow.setLoanTerm("3");
|
||||
workflow.setCreateBy(createBy);
|
||||
return workflow;
|
||||
}
|
||||
|
||||
private Page<LoanPricingWorkflowListVO> emptyPageResult()
|
||||
{
|
||||
Page<LoanPricingWorkflowListVO> pageResult = new Page<>(1, 10);
|
||||
@@ -487,13 +820,19 @@ class LoanPricingWorkflowServiceImplTest
|
||||
}
|
||||
|
||||
private void setLoginUser(Long userId, String username, String nickName, SysRole... roles)
|
||||
{
|
||||
setLoginUser(userId, 100L, username, nickName, roles);
|
||||
}
|
||||
|
||||
private void setLoginUser(Long userId, Long deptId, String username, String nickName, SysRole... roles)
|
||||
{
|
||||
SysUser user = new SysUser();
|
||||
user.setUserId(userId);
|
||||
user.setDeptId(deptId);
|
||||
user.setUserName(username);
|
||||
user.setNickName(nickName);
|
||||
user.setRoles(java.util.Arrays.asList(roles));
|
||||
LoginUser loginUser = new LoginUser(userId, 100L, user, Collections.emptySet());
|
||||
LoginUser loginUser = new LoginUser(userId, deptId, user, Collections.emptySet());
|
||||
UsernamePasswordAuthenticationToken authentication =
|
||||
new UsernamePasswordAuthenticationToken(loginUser, null, Collections.emptyList());
|
||||
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 上虞利率定价系统
|
||||
VUE_APP_TITLE = 贷款利率定价系统
|
||||
|
||||
# 开发环境配置
|
||||
ENV = 'development'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 上虞利率定价系统
|
||||
VUE_APP_TITLE = 贷款利率定价系统
|
||||
|
||||
# 生产环境配置
|
||||
ENV = 'production'
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
# 页面标题
|
||||
VUE_APP_TITLE = 上虞利率定价系统
|
||||
VUE_APP_TITLE = 贷款利率定价系统
|
||||
|
||||
BABEL_ENV = production
|
||||
|
||||
|
||||
@@ -17,6 +17,14 @@ export function getWorkflow(serialNum) {
|
||||
})
|
||||
}
|
||||
|
||||
// 查询利率定价流程编辑数据
|
||||
export function getWorkflowEdit(serialNum) {
|
||||
return request({
|
||||
url: '/loanPricing/workflow/' + serialNum + '/edit',
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
// 创建个人客户利率定价流程
|
||||
export function createPersonalWorkflow(data) {
|
||||
return request({
|
||||
@@ -26,6 +34,15 @@ export function createPersonalWorkflow(data) {
|
||||
})
|
||||
}
|
||||
|
||||
// 编辑个人客户利率定价流程
|
||||
export function updatePersonalWorkflow(serialNum, data) {
|
||||
return request({
|
||||
url: '/loanPricing/workflow/' + serialNum + '/personal',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 创建企业客户利率定价流程
|
||||
export function createCorporateWorkflow(data) {
|
||||
return request({
|
||||
@@ -35,6 +52,15 @@ export function createCorporateWorkflow(data) {
|
||||
})
|
||||
}
|
||||
|
||||
// 编辑企业客户利率定价流程
|
||||
export function updateCorporateWorkflow(serialNum, data) {
|
||||
return request({
|
||||
url: '/loanPricing/workflow/' + serialNum + '/corporate',
|
||||
method: 'put',
|
||||
data: data
|
||||
})
|
||||
}
|
||||
|
||||
// 查询个人客户号映射
|
||||
export function queryPersonalCustomerMap(custId) {
|
||||
return request({
|
||||
|
||||
14
ruoyi-ui/src/utils/rate.js
Normal file
14
ruoyi-ui/src/utils/rate.js
Normal file
@@ -0,0 +1,14 @@
|
||||
export function formatRate(value, emptyText = '-') {
|
||||
if (value === null || value === undefined || value === '') {
|
||||
return emptyText
|
||||
}
|
||||
const stringValue = String(value).trim()
|
||||
if (stringValue === '') {
|
||||
return emptyText
|
||||
}
|
||||
const numberValue = Number(stringValue)
|
||||
if (Number.isNaN(numberValue)) {
|
||||
return stringValue
|
||||
}
|
||||
return numberValue.toFixed(2)
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-dialog title="新增企业利率定价流程" :visible.sync="dialogVisible" width="900px" append-to-body
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="900px" append-to-body
|
||||
@close="handleClose">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="140px" class="workflow-create-form">
|
||||
<!-- 基本信息 -->
|
||||
@@ -59,6 +59,11 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="余值覆盖" prop="resCover">
|
||||
<el-switch v-model="form.resCover"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
@@ -72,7 +77,7 @@
|
||||
</el-col>
|
||||
<el-col :span="12" v-if="isStockTransfer">
|
||||
<el-form-item label="历史贷款利率" prop="loanRateHistory">
|
||||
<el-input v-model="form.loanRateHistory" placeholder="请选择历史贷款合同" :readonly="true"/>
|
||||
<el-input :value="formatRate(form.loanRateHistory, '')" placeholder="请选择历史贷款合同" :readonly="true"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
@@ -95,7 +100,33 @@
|
||||
<!-- 抵质押信息 -->
|
||||
<el-divider v-if="isCollateralGuarantee" content-position="left">抵质押信息</el-divider>
|
||||
<el-row v-if="isCollateralGuarantee">
|
||||
<el-col :span="12">
|
||||
<template v-if="isMortgage">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="抵押物性质" prop="collateralNature">
|
||||
<el-select v-model="form.collateralNature" placeholder="请选择抵押物性质" style="width: 100%">
|
||||
<el-option v-for="item in collateralNatureOptions" :key="item" :label="item" :value="item"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否属于九地" prop="isNineArea">
|
||||
<div class="nine-area-control">
|
||||
<el-select
|
||||
v-model="form.isNineArea"
|
||||
:disabled="!requiresNineArea"
|
||||
:placeholder="requiresNineArea ? '请选择是否属于九地' : '无需选择'"
|
||||
>
|
||||
<el-option label="是" value="是"/>
|
||||
<el-option label="否" value="否"/>
|
||||
</el-select>
|
||||
<el-tooltip content="九地是指绍兴、杭州、宁波、上海、北京、广州、深圳、南京、苏州" placement="top">
|
||||
<i class="el-icon-question nine-area-help" tabindex="0" aria-label="查看九地说明"></i>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
<el-col :span="12" v-if="isPledge">
|
||||
<el-form-item label="抵质押类型" prop="collType">
|
||||
<el-select v-model="form.collType" placeholder="请选择抵质押类型" style="width: 100%">
|
||||
<el-option v-for="item in collateralTypeOptions" :key="item" :label="item" :value="item"/>
|
||||
@@ -128,8 +159,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {createCorporateWorkflow, queryHistoryContracts} from "@/api/loanPricing/workflow"
|
||||
import {createCorporateWorkflow, queryHistoryContracts, updateCorporateWorkflow} from "@/api/loanPricing/workflow"
|
||||
import HistoryContractSelector from "./HistoryContractSelector"
|
||||
import {formatRate} from "@/utils/rate"
|
||||
|
||||
export default {
|
||||
name: "CorporateCreateDialog",
|
||||
@@ -144,6 +176,10 @@ export default {
|
||||
customerMap: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
editData: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -171,6 +207,30 @@ export default {
|
||||
callback()
|
||||
}
|
||||
|
||||
const validateCollateralType = (rule, value, callback) => {
|
||||
if (this.isPledge && !value) {
|
||||
callback(new Error('请选择抵质押类型'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
const validateCollateralNature = (rule, value, callback) => {
|
||||
if (this.isMortgage && !value) {
|
||||
callback(new Error('请选择抵押物性质'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
const validateNineArea = (rule, value, callback) => {
|
||||
if (this.requiresNineArea && !value) {
|
||||
callback(new Error('请选择是否属于九地'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
// 贷款期限验证
|
||||
const validateLoanTerm = (rule, value, callback) => {
|
||||
if (!value && value !== 0) {
|
||||
@@ -190,6 +250,7 @@ export default {
|
||||
'1', '2', '3', '4', '5', '6', '7', '8', '9', '10'
|
||||
],
|
||||
submitting: false,
|
||||
resettingForm: false,
|
||||
showHistorySelector: false,
|
||||
historyLoading: false,
|
||||
historyContracts: [],
|
||||
@@ -206,9 +267,12 @@ export default {
|
||||
loanTerm: undefined,
|
||||
businessType: undefined,
|
||||
loanRateHistory: undefined,
|
||||
resCover: false,
|
||||
isGreenLoan: false,
|
||||
isTradeBuildEnt: false,
|
||||
collType: undefined,
|
||||
collateralNature: undefined,
|
||||
isNineArea: undefined,
|
||||
collThirdParty: false,
|
||||
couponRate: undefined
|
||||
},
|
||||
@@ -243,7 +307,13 @@ export default {
|
||||
{required: true, message: "请选择历史贷款合同", trigger: "change"}
|
||||
],
|
||||
collType: [
|
||||
{required: true, message: "请选择抵质押类型", trigger: "change"}
|
||||
{validator: validateCollateralType, trigger: "change"}
|
||||
],
|
||||
collateralNature: [
|
||||
{validator: validateCollateralNature, trigger: "change"}
|
||||
],
|
||||
isNineArea: [
|
||||
{validator: validateNineArea, trigger: "change"}
|
||||
],
|
||||
couponRate: [
|
||||
{validator: validateCouponRate, trigger: "blur"}
|
||||
@@ -263,20 +333,35 @@ export default {
|
||||
isCollateralGuarantee() {
|
||||
return this.form.guarType === '抵押' || this.form.guarType === '质押'
|
||||
},
|
||||
isMortgage() {
|
||||
return this.form.guarType === '抵押'
|
||||
},
|
||||
isPledge() {
|
||||
return this.form.guarType === '质押'
|
||||
},
|
||||
requiresNineArea() {
|
||||
return this.isMortgage && ['普通住宅、土地、厂房', '商业性质不动产'].includes(this.form.collateralNature)
|
||||
},
|
||||
isStockTransfer() {
|
||||
return this.form.businessType === '存量转贷'
|
||||
},
|
||||
isCertificatePledge() {
|
||||
return this.form.guarType === '质押' && this.form.collType === '存单质押'
|
||||
},
|
||||
isEdit() {
|
||||
return !!(this.editData && this.editData.serialNum)
|
||||
},
|
||||
dialogTitle() {
|
||||
return this.isEdit ? '编辑企业利率定价流程' : '新增企业利率定价流程'
|
||||
},
|
||||
collateralTypeOptions() {
|
||||
if (this.form.guarType === '抵押') {
|
||||
return ['一类', '二类', '三类', '四类', '排污权抵押', '设备等其他不动产抵押']
|
||||
}
|
||||
if (this.form.guarType === '质押') {
|
||||
if (this.isPledge) {
|
||||
return ['存单质押', '股权质押', '其他质押']
|
||||
}
|
||||
return []
|
||||
},
|
||||
collateralNatureOptions() {
|
||||
return ['普通住宅、土地、厂房', '商业性质不动产', '排污权', '其他不动产']
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -286,18 +371,69 @@ export default {
|
||||
}
|
||||
},
|
||||
'form.guarType'(val, oldVal) {
|
||||
if (this.resettingForm) {
|
||||
return
|
||||
}
|
||||
if (val !== oldVal) {
|
||||
this.resetCollateralFields()
|
||||
}
|
||||
},
|
||||
'form.collType'() {
|
||||
if (this.resettingForm) {
|
||||
return
|
||||
}
|
||||
this.resetCouponRateIfNotCertificatePledge()
|
||||
},
|
||||
'form.collateralNature'(val, oldVal) {
|
||||
if (this.resettingForm || val === oldVal) {
|
||||
return
|
||||
}
|
||||
this.handleCollateralNatureChange()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatRate,
|
||||
/** 表单重置 */
|
||||
reset() {
|
||||
this.form = {
|
||||
this.resettingForm = true
|
||||
this.form = this.buildForm()
|
||||
this.submitting = false
|
||||
this.showHistorySelector = false
|
||||
this.historyLoading = false
|
||||
this.historyContracts = []
|
||||
this.selectedHistoryContract = null
|
||||
this.$nextTick(() => {
|
||||
this.resettingForm = false
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.clearValidate()
|
||||
}
|
||||
})
|
||||
},
|
||||
buildForm() {
|
||||
if (this.isEdit) {
|
||||
return {
|
||||
orgCode: this.editData.orgCode || '892000',
|
||||
runType: this.editData.runType || '1',
|
||||
custIsn: this.editData.custIsn,
|
||||
custName: this.editData.custName,
|
||||
idType: this.editData.idType,
|
||||
idNum: this.editData.idNum,
|
||||
guarType: this.editData.guarType,
|
||||
applyAmt: this.editData.applyAmt,
|
||||
loanTerm: this.editData.loanTerm,
|
||||
businessType: this.editData.businessType,
|
||||
loanRateHistory: this.editData.loanRateHistory,
|
||||
resCover: this.isOne(this.editData.resCover),
|
||||
isGreenLoan: this.isOne(this.editData.isGreenLoan),
|
||||
isTradeBuildEnt: this.isOne(this.editData.isTradeBuildEnt),
|
||||
collType: this.editData.guarType === '质押' ? this.editData.collType : undefined,
|
||||
collateralNature: this.editData.collateralNature,
|
||||
isNineArea: this.editData.isNineArea,
|
||||
collThirdParty: this.isOne(this.editData.collThirdParty),
|
||||
couponRate: this.editData.couponRate
|
||||
}
|
||||
}
|
||||
return {
|
||||
orgCode: '892000',
|
||||
runType: '1',
|
||||
custIsn: this.customerMap ? this.customerMap.cust_isn : undefined,
|
||||
@@ -309,22 +445,18 @@ export default {
|
||||
loanTerm: undefined,
|
||||
businessType: undefined,
|
||||
loanRateHistory: undefined,
|
||||
resCover: false,
|
||||
isGreenLoan: false,
|
||||
isTradeBuildEnt: false,
|
||||
collType: undefined,
|
||||
collateralNature: undefined,
|
||||
isNineArea: undefined,
|
||||
collThirdParty: false,
|
||||
couponRate: undefined
|
||||
}
|
||||
this.submitting = false
|
||||
this.showHistorySelector = false
|
||||
this.historyLoading = false
|
||||
this.historyContracts = []
|
||||
this.selectedHistoryContract = null
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.clearValidate()
|
||||
}
|
||||
})
|
||||
},
|
||||
isOne(value) {
|
||||
return value === true || value === 'true' || value === '1'
|
||||
},
|
||||
/** 对话框关闭处理 */
|
||||
handleClose() {
|
||||
@@ -338,14 +470,38 @@ export default {
|
||||
/** 清空抵质押字段 */
|
||||
resetCollateralFields() {
|
||||
this.form.collType = undefined
|
||||
this.form.collateralNature = undefined
|
||||
this.form.isNineArea = undefined
|
||||
this.form.collThirdParty = false
|
||||
this.resetCouponRateIfNotCertificatePledge()
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.clearValidate(['collType', 'collThirdParty', 'couponRate'])
|
||||
this.$refs.form.clearValidate(['collType', 'collateralNature', 'isNineArea', 'collThirdParty', 'couponRate'])
|
||||
}
|
||||
})
|
||||
},
|
||||
handleCollateralNatureChange() {
|
||||
this.form.isNineArea = undefined
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.clearValidate(['isNineArea'])
|
||||
}
|
||||
})
|
||||
},
|
||||
resolveMortgageCollateralType() {
|
||||
if (this.form.collateralNature === '普通住宅、土地、厂房') {
|
||||
return this.form.isNineArea === '是' ? '一类' : '二类'
|
||||
}
|
||||
if (this.form.collateralNature === '商业性质不动产') {
|
||||
return this.form.isNineArea === '是' ? '二类' : '三类'
|
||||
}
|
||||
if (this.form.collateralNature === '排污权') {
|
||||
return '排污权抵押'
|
||||
}
|
||||
if (this.form.collateralNature === '其他不动产') {
|
||||
return '其他不动产抵押'
|
||||
}
|
||||
},
|
||||
resetCouponRateIfNotCertificatePledge() {
|
||||
if (!this.isCertificatePledge) {
|
||||
this.form.couponRate = undefined
|
||||
@@ -413,9 +569,16 @@ export default {
|
||||
// 转换开关值为字符串
|
||||
const data = {
|
||||
...this.form,
|
||||
resCover: this.form.resCover ? '1' : '0',
|
||||
isGreenLoan: this.form.isGreenLoan ? '1' : '0',
|
||||
isTradeBuildEnt: this.form.isTradeBuildEnt ? '1' : '0'
|
||||
}
|
||||
if (this.isMortgage) {
|
||||
data.collType = this.resolveMortgageCollateralType()
|
||||
} else {
|
||||
delete data.collateralNature
|
||||
delete data.isNineArea
|
||||
}
|
||||
if (this.isCollateralGuarantee) {
|
||||
data.collThirdParty = this.form.collThirdParty ? '1' : '0'
|
||||
} else {
|
||||
@@ -429,8 +592,11 @@ export default {
|
||||
delete data.couponRate
|
||||
}
|
||||
|
||||
createCorporateWorkflow(data).then(response => {
|
||||
this.$modal.msgSuccess("新增成功")
|
||||
const submitRequest = this.isEdit
|
||||
? updateCorporateWorkflow(this.editData.serialNum, data)
|
||||
: createCorporateWorkflow(data)
|
||||
submitRequest.then(response => {
|
||||
this.$modal.msgSuccess(this.isEdit ? "编辑成功" : "新增成功")
|
||||
this.dialogVisible = false
|
||||
this.$emit('success')
|
||||
}).catch(error => {
|
||||
@@ -453,6 +619,24 @@ export default {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.nine-area-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.nine-area-control .el-select {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.nine-area-help {
|
||||
flex: 0 0 auto;
|
||||
color: #909399;
|
||||
cursor: help;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.workflow-create-form .el-col {
|
||||
width: 100% !important;
|
||||
|
||||
@@ -13,14 +13,10 @@
|
||||
<el-descriptions-item label="客户名称">{{ detailData.custName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户类型">{{ detailData.custType }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请金额">{{ detailData.applyAmt }} 元</el-descriptions-item>
|
||||
<el-descriptions-item label="基准利率">
|
||||
<span class="rate-value">{{ getBaseLoanRate() }}</span> %
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="浮动BP">
|
||||
<span class="total-bp-value">{{ getTotalBp() }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="最终测算利率">
|
||||
<span class="calculate-rate">{{ getCalculateRate() }}</span> %
|
||||
<el-descriptions-item label="期限">{{ detailData.loanTerm || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="担保方式">{{ detailData.guarType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="产品最低利率下限">
|
||||
<span class="rate-value">{{ formatRate(corpOutput && corpOutput.minRateProduct) }}</span> %
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="执行利率">
|
||||
<div class="execute-rate-input-wrapper">
|
||||
@@ -46,13 +42,6 @@
|
||||
|
||||
<!-- 右侧面板 -->
|
||||
<div class="right-panel">
|
||||
<!-- 模型输出卡片 -->
|
||||
<ModelOutputDisplay
|
||||
:cust-type="detailData.custType"
|
||||
:retail-output="null"
|
||||
:corp-output="corpOutput"
|
||||
/>
|
||||
|
||||
<!-- 流程详情卡片 -->
|
||||
<el-card class="detail-card">
|
||||
<div slot="header" class="card-header">
|
||||
@@ -64,11 +53,10 @@
|
||||
<h4 class="section-title">基本信息</h4>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="机构编码">{{ detailData.orgCode }}</el-descriptions-item>
|
||||
<el-descriptions-item label="运行模式">{{ detailData.runType }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户内码">{{ detailData.custIsn }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户名称">{{ detailData.custName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="证件类型">{{ detailData.idType }}</el-descriptions-item>
|
||||
<el-descriptions-item label="证件号码">{{ detailData.idNum }}</el-descriptions-item>
|
||||
<el-descriptions-item label="贷款期限">{{ detailData.loanTerm || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ detailData.createTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建者">{{ detailData.createBy }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
@@ -81,9 +69,17 @@
|
||||
<el-descriptions-item label="担保方式">{{ detailData.guarType }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请金额">{{ detailData.applyAmt }} 元</el-descriptions-item>
|
||||
<el-descriptions-item label="业务种类">{{ detailData.businessType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="历史贷款利率">{{ detailData.loanRateHistory || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="贷款期限">{{ detailData.loanTerm || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="历史贷款利率">{{ formatRate(detailData.loanRateHistory) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="余值覆盖">{{ formatBoolean(detailData.resCover) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="绿色贷款">{{ formatBoolean(detailData.isGreenLoan) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="贸易和建筑业企业">{{ formatBoolean(detailData.isTradeBuildEnt) }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detailData.guarType === '抵押'" label="抵押物性质">
|
||||
{{ detailData.collateralNature || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detailData.guarType === '抵押'" label="是否属于九地">
|
||||
{{ detailData.isNineArea || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="抵质押类型">{{ detailData.collType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="抵质押物是否三方所有">{{
|
||||
formatBoolean(detailData.collThirdParty)
|
||||
@@ -93,6 +89,14 @@
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 模型输出卡片 -->
|
||||
<ModelOutputDisplay
|
||||
:cust-type="detailData.custType"
|
||||
:guar-type="detailData.guarType"
|
||||
:retail-output="null"
|
||||
:corp-output="corpOutput"
|
||||
/>
|
||||
|
||||
<!-- 议价池卡片 -->
|
||||
<BargainingPoolDisplay
|
||||
v-if="bargainingPool"
|
||||
@@ -110,6 +114,7 @@
|
||||
import {setExecuteRate} from "@/api/loanPricing/workflow"
|
||||
import ModelOutputDisplay from "./ModelOutputDisplay.vue"
|
||||
import BargainingPoolDisplay from "./BargainingPoolDisplay.vue"
|
||||
import {formatRate} from "@/utils/rate"
|
||||
|
||||
export default {
|
||||
name: "CorporateWorkflowDetail",
|
||||
@@ -140,30 +145,19 @@ export default {
|
||||
watch: {
|
||||
'detailData.executeRate': {
|
||||
handler(newVal) {
|
||||
this.executeRateInput = newVal || ''
|
||||
this.executeRateInput = formatRate(newVal, '')
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatRate,
|
||||
/** 格式化布尔值为中文 */
|
||||
formatBoolean(value) {
|
||||
if (value === 'true' || value === true || value === '1' || value === 1) return '是'
|
||||
if (value === 'false' || value === false || value === '0' || value === 0) return '否'
|
||||
return value || '-'
|
||||
},
|
||||
/** 获取基准利率 */
|
||||
getBaseLoanRate() {
|
||||
return this.corpOutput?.baseLoanRate || '-'
|
||||
},
|
||||
/** 获取浮动BP */
|
||||
getTotalBp() {
|
||||
return this.corpOutput?.totalBp || '-'
|
||||
},
|
||||
/** 获取最终测算利率 */
|
||||
getCalculateRate() {
|
||||
return this.corpOutput?.finalCalculateRate || '-'
|
||||
},
|
||||
/** 设定执行利率 */
|
||||
handleSetExecuteRate() {
|
||||
const value = this.executeRateInput
|
||||
@@ -245,16 +239,6 @@ export default {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.total-bp-value {
|
||||
color: #e6a23c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.calculate-rate {
|
||||
color: #f56c6c;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -3,7 +3,8 @@
|
||||
:close-on-click-modal="false" @close="handleClose">
|
||||
<el-form :model="queryForm" inline size="small">
|
||||
<el-form-item label="客户号">
|
||||
<el-input v-model="queryForm.custId" placeholder="请输入客户号" clearable @keyup.enter.native="handleQuery"/>
|
||||
<el-input v-model="queryForm.custId" placeholder="请输入客户号" clearable
|
||||
@keydown.enter.native.prevent="handleQuery"/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" :loading="loading" @click="handleQuery">查询</el-button>
|
||||
|
||||
@@ -18,7 +18,11 @@
|
||||
<el-table-column label="历史贷款合同号" prop="loan_contract_history" align="center" :show-overflow-tooltip="true"/>
|
||||
<el-table-column label="历史贷款担保方式" prop="guar_type_history" align="center"/>
|
||||
<el-table-column label="历史贷款产品代码" prop="product_code_history" align="center"/>
|
||||
<el-table-column label="历史贷款利率" prop="loan_rate_history" align="center"/>
|
||||
<el-table-column label="历史贷款利率" prop="loan_rate_history" align="center">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatRate(scope.row.loan_rate_history) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="历史贷款金额" prop="loan_amount_history" align="center"/>
|
||||
<el-table-column label="历史贷款签订时间" prop="loan_sign_date_history" align="center" width="150"/>
|
||||
</el-table>
|
||||
@@ -30,6 +34,8 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {formatRate} from "@/utils/rate"
|
||||
|
||||
export default {
|
||||
name: "HistoryContractSelector",
|
||||
props: {
|
||||
@@ -63,6 +69,7 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatRate,
|
||||
contractRadioValue(row, index) {
|
||||
return row.loan_contract_history || `${row.cust_isn || ''}-${index}`
|
||||
},
|
||||
|
||||
@@ -6,209 +6,184 @@
|
||||
<div class="output-sections">
|
||||
<!-- 个人客户模型输出 -->
|
||||
<template v-if="custType === '个人' && retailOutput">
|
||||
<div class="output-section">
|
||||
<h4 class="section-title">基本信息</h4>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="客户内码">{{ retailOutput.custIsn || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户名称">{{ retailOutput.custName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="证件类型">{{ retailOutput.idType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="证件号码">{{ retailOutput.idNum || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="基准利率"><span class="rate-value">{{ retailOutput.baseLoanRate || '-' }}</span> %</el-descriptions-item>
|
||||
<el-descriptions-item label="灰黑名单客户">{{ formatOutputValue(retailOutput.greyBlackCust) }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<div class="output-section">
|
||||
<h4 class="section-title">测算结果</h4>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions :column="3" border size="small">
|
||||
<el-descriptions-item label="基准利率"><span class="rate-value">{{ formatRate(retailOutput.baseLoanRate) }}</span> %</el-descriptions-item>
|
||||
<el-descriptions-item label="浮动BP"><span class="total-bp-value">{{ retailOutput.totalBp || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="测算利率"><span class="calculate-rate">{{ retailOutput.calculateRate || '-' }}</span> %</el-descriptions-item>
|
||||
<el-descriptions-item label="历史利率">{{ retailOutput.loanRateHistory || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="产品最低利率下限">{{ retailOutput.minRateProduct || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="平滑幅度">{{ retailOutput.smoothRange || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="参考利率"><span class="calculate-rate">{{ retailOutput.referenceRate || '-' }}</span> %</el-descriptions-item>
|
||||
<el-descriptions-item label="最终测算利率"><span class="calculate-rate">{{ retailOutput.finalCalculateRate || '-' }}</span> %</el-descriptions-item>
|
||||
<el-descriptions-item label="模型测算利率"><span class="calculate-rate">{{ formatRate(retailOutput.calculateRate) }}</span> %</el-descriptions-item>
|
||||
<el-descriptions-item label="历史利率">{{ formatRate(retailOutput.loanRateHistory) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="平滑幅度">{{ formatRate(retailOutput.smoothRange) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="平滑利率"><span class="calculate-rate">{{ formatRate(retailOutput.finalCalculateRate) }}</span> %</el-descriptions-item>
|
||||
<el-descriptions-item label="行社下调BP">{{ formatOutputValue(retailOutput.branchBp) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="参考利率"><span class="calculate-rate">{{ formatRate(retailOutput.referenceRate) }}</span> %</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<div class="output-section">
|
||||
<h4 class="section-title">忠诚度分析</h4>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="我行首贷客户">{{ formatBoolean(retailOutput.isFirstLoan) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用信天数">{{ retailOutput.faithDay || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户年龄">{{ retailOutput.custAge || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="BP_首贷"><span class="bp-value">{{ retailOutput.bpFirstLoan || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="BP_贷龄"><span class="bp-value">{{ retailOutput.bpAgeLoan || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="BP_年龄"><span class="bp-value">{{ retailOutput.bpAge || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="TOTAL_BP_忠诚度" :span="2"><span class="total-bp-value">{{ retailOutput.totalBpLoyalty || '-' }}</span></el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<table class="output-analysis-table">
|
||||
<colgroup><col class="result-column"><col class="bp-column"></colgroup>
|
||||
<thead><tr><th>模型输出结果</th><th>影响BP</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, index) in retailLoyaltyRows" :key="`retail-loyalty-${index}`" :class="{ 'is-total-row': row.total }">
|
||||
<td><span v-if="row.summary">汇总</span><div v-else v-for="item in row.results" :key="item.label" class="result-item"><span class="result-label">{{ item.label }}</span><span class="result-value">{{ item.value }}</span></div></td>
|
||||
<td><span :class="row.total ? 'total-bp-value' : 'bp-value'">{{ row.bpLabel }}:{{ row.bpValue }}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="output-section">
|
||||
<h4 class="section-title">贡献度分析</h4>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="存款年日均">{{ retailOutput.balanceAvg || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="贷款年日均">{{ retailOutput.loanAvg || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="派生率">{{ retailOutput.derivationRate || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="TOTAL_BP_贡献度"><span class="total-bp-value">{{ retailOutput.totalBpContribution || '-' }}</span></el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<table class="output-analysis-table">
|
||||
<colgroup><col class="result-column"><col class="bp-column"></colgroup>
|
||||
<thead><tr><th>模型输出结果</th><th>影响BP</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, index) in retailContributionRows" :key="`retail-contribution-${index}`" :class="{ 'is-total-row': row.total }">
|
||||
<td><span v-if="row.summary">汇总</span><div v-else v-for="item in row.results" :key="item.label" class="result-item"><span class="result-label">{{ item.label }}</span><span class="result-value">{{ item.value }}</span></div></td>
|
||||
<td>
|
||||
<span v-if="row.bpLabel" :class="row.total ? 'total-bp-value' : 'bp-value'">
|
||||
{{ row.bpLabel }}:{{ row.bpValue }}
|
||||
<el-tooltip v-if="row.bpTooltip" :content="row.bpTooltip" placement="top">
|
||||
<i class="el-icon-question analysis-help" tabindex="0" :aria-label="row.bpTooltipAriaLabel"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="output-section">
|
||||
<h4 class="section-title">关联度分析</h4>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="中间业务_个人_信用卡">{{ formatBoolean(retailOutput.midPerCard) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中间业务_个人_一码通">{{ formatBoolean(retailOutput.midPerPass) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中间业务_个人_丰收互联">{{ formatBoolean(retailOutput.midPerHarvest) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中间业务_个人_有效客户">{{ formatBoolean(retailOutput.midPerEffect) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中间业务_个人_快捷支付">{{ formatBoolean(retailOutput.midPerQuickPay) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中间业务_个人_电费代扣">{{ formatBoolean(retailOutput.midPerEleDdc) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中间业务_个人_水费代扣">{{ formatBoolean(retailOutput.midPerWaterDdc) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中间业务_个人_华数费代扣">{{ formatBoolean(retailOutput.midPerHuashuDdc) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中间业务_个人_煤气费代扣">{{ formatBoolean(retailOutput.MidPerGasDdc) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中间业务_个人_市民卡">{{ formatBoolean(retailOutput.midPerCitizencard) }}</el-descriptions-item>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="output-section">
|
||||
<h4 class="section-title">贷款特征</h4>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="申请金额">{{ retailOutput.applyAmt || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="BP_贷款额度"><span class="bp-value">{{ retailOutput.bpLoanAmount || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="贷款用途">{{ formatLoanPurpose(retailOutput.loanPurpose) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否有经营佐证">{{ formatBoolean(retailOutput.bizProof) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="BP_贷款用途"><span class="bp-value">{{ retailOutput.bpLoanUse || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="循环功能">{{ formatBoolean(retailOutput.loanLoop) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="BP_循环功能"><span class="bp-value">{{ retailOutput.bpLoanLoop || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="抵质押类型">{{ retailOutput.collType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="抵质押物三方所有">{{ formatBoolean(retailOutput.collThirdParty) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="BP_抵押物"><span class="bp-value">{{ retailOutput.bpCollateral || '-' }}</span></el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<table class="output-analysis-table">
|
||||
<colgroup><col class="result-column"><col class="bp-column"></colgroup>
|
||||
<thead><tr><th>模型输出结果</th><th>影响BP</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, index) in retailRelevanceRows" :key="`retail-relevance-${index}`" :class="{ 'is-total-row': row.total }">
|
||||
<td>
|
||||
<span v-if="row.summary">汇总</span>
|
||||
<div v-else v-for="item in row.results" :key="item.label" class="result-item">
|
||||
<span class="result-label">{{ item.label }}</span>
|
||||
<span v-if="item.showValue !== false" class="result-value">{{ item.value }}</span>
|
||||
</div>
|
||||
</td>
|
||||
<td>
|
||||
<span :class="row.total ? 'total-bp-value' : 'bp-value'">
|
||||
<template v-if="row.bpLabel">
|
||||
{{ row.bpLabel }}:
|
||||
</template>{{ row.bpValue }}
|
||||
<el-tooltip v-if="row.bpTooltip" :content="row.bpTooltip" placement="top">
|
||||
<i class="el-icon-question analysis-help" tabindex="0" :aria-label="row.bpTooltipAriaLabel"></i>
|
||||
</el-tooltip>
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="output-section">
|
||||
<h4 class="section-title">风险度分析</h4>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="灰名单客户">{{ formatBoolean(retailOutput.greyCust) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="本金逾期">{{ formatBoolean(retailOutput.prinOverdue) }}</el-descriptions-item>
|
||||
<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>
|
||||
<table class="output-analysis-table">
|
||||
<colgroup><col class="result-column"><col class="bp-column"></colgroup>
|
||||
<thead><tr><th>模型输出结果</th><th>影响BP</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, index) in retailRiskRows" :key="`retail-risk-${index}`" :class="{ 'is-total-row': row.total }">
|
||||
<td><span v-if="row.summary">汇总</span><div v-else v-for="item in row.results" :key="item.label" class="result-item"><span class="result-label">{{ item.label }}</span><span class="result-value">{{ item.value }}</span></div></td>
|
||||
<td><span :class="row.total ? 'total-bp-value' : 'bp-value'">{{ row.bpLabel }}:{{ row.bpValue }}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
|
||||
<!-- 企业客户模型输出 -->
|
||||
<template v-else-if="custType === '企业' && corpOutput">
|
||||
<div class="output-section">
|
||||
<h4 class="section-title">基本信息</h4>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="客户内码">{{ corpOutput.custIsn || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户名称">{{ corpOutput.custName || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="证件类型">{{ corpOutput.idType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="证件号码">{{ corpOutput.idNum || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="基准利率"><span class="rate-value">{{ corpOutput.baseLoanRate || '-' }}</span> %</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<div class="output-section">
|
||||
<h4 class="section-title">测算结果</h4>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions :column="3" border size="small">
|
||||
<el-descriptions-item label="基准利率"><span class="rate-value">{{ formatRate(corpOutput.baseLoanRate) }}</span> %</el-descriptions-item>
|
||||
<el-descriptions-item label="浮动BP"><span class="total-bp-value">{{ corpOutput.totalBp || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="测算利率"><span class="calculate-rate">{{ corpOutput.calculateRate || '-' }}</span> %</el-descriptions-item>
|
||||
<el-descriptions-item label="历史利率">{{ corpOutput.loanRateHistory || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="产品最低利率下限">{{ corpOutput.minRateProduct || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="平滑幅度">{{ corpOutput.smoothRange || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="参考利率"><span class="calculate-rate">{{ corpOutput.referenceRate || '-' }}</span> %</el-descriptions-item>
|
||||
<el-descriptions-item label="最终测算利率"><span class="calculate-rate">{{ corpOutput.finalCalculateRate || '-' }}</span> %</el-descriptions-item>
|
||||
<el-descriptions-item label="模型测算利率"><span class="calculate-rate">{{ formatRate(corpOutput.calculateRate) }}</span> %</el-descriptions-item>
|
||||
<el-descriptions-item label="历史利率">{{ formatRate(corpOutput.loanRateHistory) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="平滑幅度">{{ formatRate(corpOutput.smoothRange) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="平滑利率"><span class="calculate-rate">{{ formatRate(corpOutput.finalCalculateRate) }}</span> %</el-descriptions-item>
|
||||
<el-descriptions-item label="行社下调BP">{{ formatOutputValue(corpOutput.branchBp) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="参考利率"><span class="calculate-rate">{{ formatRate(corpOutput.referenceRate) }}</span> %</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<div class="output-section">
|
||||
<h4 class="section-title">忠诚度分析</h4>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="我行首贷客户">{{ formatBoolean(corpOutput.isFirstLoan) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="用信天数">{{ corpOutput.faithDay || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="BP_首贷"><span class="bp-value">{{ corpOutput.bpFirstLoan || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="BP_贷龄"><span class="bp-value">{{ corpOutput.bpAgeLoan || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="TOTAL_BP_忠诚度" :span="2"><span class="total-bp-value">{{ corpOutput.totalBpLoyalty || '-' }}</span></el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<table class="output-analysis-table">
|
||||
<colgroup><col class="result-column"><col class="bp-column"></colgroup>
|
||||
<thead><tr><th>模型输出结果</th><th>影响BP</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, index) in corporateLoyaltyRows" :key="`corporate-loyalty-${index}`" :class="{ 'is-total-row': row.total }">
|
||||
<td><span v-if="row.summary">汇总</span><div v-else v-for="item in row.results" :key="item.label" class="result-item"><span class="result-label">{{ item.label }}</span><span class="result-value">{{ item.value }}</span></div></td>
|
||||
<td><span :class="row.total ? 'total-bp-value' : 'bp-value'">{{ row.bpLabel }}:{{ row.bpValue }}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="output-section">
|
||||
<h4 class="section-title">贡献度分析</h4>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="存款年日均">{{ corpOutput.balanceAvg || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="贷款年日均">{{ corpOutput.loanAvg || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="派生率">{{ corpOutput.derivationRate || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="TOTAL_BP_贡献度"><span class="total-bp-value">{{ corpOutput.totalBpContribution || '-' }}</span></el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<table class="output-analysis-table">
|
||||
<colgroup><col class="result-column"><col class="bp-column"></colgroup>
|
||||
<thead><tr><th>模型输出结果</th><th>影响BP</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, index) in corporateContributionRows" :key="`corporate-contribution-${index}`" :class="{ 'is-total-row': row.total }">
|
||||
<td><span v-if="row.summary">汇总</span><div v-else v-for="item in row.results" :key="item.label" class="result-item"><span class="result-label">{{ item.label }}</span><span class="result-value">{{ item.value }}</span></div></td>
|
||||
<td><span v-if="row.bpLabel" :class="row.total ? 'total-bp-value' : 'bp-value'">{{ row.bpLabel }}:{{ row.bpValue }}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="output-section">
|
||||
<h4 class="section-title">关联度分析</h4>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="中间业务_企业_企业互联">{{ formatBoolean(corpOutput.midEntConnect) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中间业务_企业_有效价值客户">{{ formatBoolean(corpOutput.midEntEffect) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中间业务_企业_国际业务">{{ formatBoolean(corpOutput.midEntInter) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中间业务_企业_承兑">{{ formatBoolean(corpOutput.midEntAccept) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中间业务_企业_贴现">{{ formatBoolean(corpOutput.midEntDiscount) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中间业务_企业_电费代扣">{{ formatBoolean(corpOutput.midEntEleDdc) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="中间业务_企业_水费代扣">{{ formatBoolean(corpOutput.midEntWaterDdc) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="BP_中间业务"><span class="bp-value">{{ corpOutput.bpMid || '-' }}</span></el-descriptions-item>
|
||||
<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>
|
||||
</div>
|
||||
|
||||
<div class="output-section">
|
||||
<h4 class="section-title">企业类别</h4>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="净身企业">{{ formatBoolean(corpOutput.isCleanEnt) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="开立基本结算账户">{{ formatBoolean(corpOutput.hasSettleAcct) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="绿色贷款">{{ formatBoolean(corpOutput.isGreenLoan) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="科技型企业">{{ formatBoolean(corpOutput.isTechEnt) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="贸易和建筑业企业">{{ formatBoolean(corpOutput.isTradeBuildEnt) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="BP_企业客户类别"><span class="bp-value">{{ corpOutput.bpEntType || '-' }}</span></el-descriptions-item>
|
||||
</el-descriptions>
|
||||
</div>
|
||||
|
||||
<div class="output-section">
|
||||
<h4 class="section-title">贷款特征</h4>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="借款期限">{{ corpOutput.loanTerm || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="BP_贷款期限"><span class="bp-value">{{ corpOutput.bpLoanTerm || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="申请金额">{{ corpOutput.applyAmt || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="BP_贷款额度"><span class="bp-value">{{ corpOutput.bpLoanAmount || '-' }}</span></el-descriptions-item>
|
||||
<el-descriptions-item label="抵质押类型">{{ corpOutput.collType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="抵质押物三方所有">{{ formatBoolean(corpOutput.collThirdParty) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="BP_抵押物"><span class="bp-value">{{ corpOutput.bpCollateral || '-' }}</span></el-descriptions-item>
|
||||
</el-descriptions>
|
||||
<table class="output-analysis-table">
|
||||
<colgroup><col class="result-column"><col class="bp-column"></colgroup>
|
||||
<thead><tr><th>模型输出结果</th><th>影响BP</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, index) in corporateRelevanceRows" :key="`corporate-relevance-${index}`" :class="{ 'is-total-row': row.total }">
|
||||
<td><span v-if="row.summary">汇总</span><template v-else><div v-if="row.category" class="result-category">{{ row.category }}</div><div v-for="item in row.results" :key="item.label" class="result-item"><span class="result-label">{{ item.label }}</span><span v-if="item.showValue !== false" class="result-value">{{ item.value }}</span></div></template></td>
|
||||
<td>
|
||||
<span :class="row.total ? 'total-bp-value' : 'bp-value'">
|
||||
<template v-if="row.bpLabel">{{ row.bpLabel }}:</template>{{ row.bpValue }}
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="output-section">
|
||||
<h4 class="section-title">风险度分析</h4>
|
||||
<el-descriptions :column="2" border size="small">
|
||||
<el-descriptions-item label="灰名单客户">{{ formatBoolean(corpOutput.greyCust) }}</el-descriptions-item>
|
||||
<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>
|
||||
<table class="output-analysis-table">
|
||||
<colgroup><col class="result-column"><col class="bp-column"></colgroup>
|
||||
<thead><tr><th>模型输出结果</th><th>影响BP</th></tr></thead>
|
||||
<tbody>
|
||||
<tr v-for="(row, index) in corporateRiskRows" :key="`corporate-risk-${index}`" :class="{ 'is-total-row': row.total }">
|
||||
<td><span v-if="row.summary">汇总</span><div v-else v-for="item in row.results" :key="item.label" class="result-item"><span class="result-label">{{ item.label }}</span><span class="result-value">{{ item.value }}</span></div></td>
|
||||
<td><span :class="row.total ? 'total-bp-value' : 'bp-value'">{{ row.bpLabel }}:{{ row.bpValue }}</span></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
</template>
|
||||
</div>
|
||||
</el-card>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {formatRate} from "@/utils/rate"
|
||||
|
||||
export default {
|
||||
name: "ModelOutputDisplay",
|
||||
props: {
|
||||
@@ -223,9 +198,156 @@ export default {
|
||||
corpOutput: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
guarType: {
|
||||
type: String,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
retailLoyaltyRows() {
|
||||
return [
|
||||
this.analysisRow('我行首贷客户', this.formatBoolean(this.retailOutput.isFirstLoan), 'BP_首贷', this.retailOutput.bpFirstLoan),
|
||||
this.analysisRow('用信天数', this.retailOutput.faithDay, 'BP_贷龄', this.retailOutput.bpAgeLoan),
|
||||
this.analysisRow('客户年龄', this.retailOutput.custAge, 'BP_年龄', this.retailOutput.bpAge),
|
||||
this.totalRow('TOTAL_BP_忠诚度', this.retailOutput.totalBpLoyalty)
|
||||
]
|
||||
},
|
||||
retailContributionRows() {
|
||||
return [
|
||||
this.groupedAnalysisRow([
|
||||
this.resultItem('存款年日均', this.retailOutput.balanceAvg),
|
||||
this.resultItem('贷款年日均', this.retailOutput.loanAvg),
|
||||
this.resultItem('派生率', this.formatRate(this.retailOutput.derivationRate))
|
||||
]),
|
||||
this.totalRow('TOTAL_BP_贡献度', this.retailOutput.totalBpContribution, '最高上限100BP', '查看贡献度汇总上限')
|
||||
]
|
||||
},
|
||||
retailRelevanceRows() {
|
||||
return [
|
||||
this.middleBusinessRow('中间业务_个人_信用卡', this.retailOutput.midPerCard, '办理减5个BP,信用卡额度1万元及以上的再减10个BP'),
|
||||
this.middleBusinessRow('中间业务_个人_一码通', this.retailOutput.midPerPass),
|
||||
this.middleBusinessRow('中间业务_个人_丰收互联', this.retailOutput.midPerHarvest),
|
||||
this.middleBusinessRow('中间业务_个人_有效客户', this.retailOutput.midPerEffect),
|
||||
this.middleBusinessRow('中间业务_个人_快捷支付', this.retailOutput.midPerQuickPay),
|
||||
this.middleBusinessRow('中间业务_个人_电费代扣', this.retailOutput.midPerEleDdc),
|
||||
this.middleBusinessRow('中间业务_个人_水费代扣', this.retailOutput.midPerWaterDdc),
|
||||
this.middleBusinessRow('中间业务_个人_华数费代扣', this.retailOutput.midPerHuashuDdc),
|
||||
this.middleBusinessRow('中间业务_个人_煤气费代扣', this.retailOutput.midPerGasDdc),
|
||||
this.middleBusinessRow('中间业务_个人_市民卡', this.retailOutput.midPerCitizencard),
|
||||
this.middleBusinessRow('中间业务_个人_理财业务', this.retailOutput.midPerFinMan),
|
||||
this.middleBusinessRow('中间业务_个人_etc', this.retailOutput.midPerEtc),
|
||||
this.totalRow('TOTAL_BP_关联度', this.retailOutput.totalBpRelevance, '最高上限-30BP', '查看中间业务BP上限')
|
||||
]
|
||||
},
|
||||
retailRiskRows() {
|
||||
const rows = [
|
||||
this.analysisRow('申请金额', this.retailOutput.applyAmt, 'BP_贷款额度', this.retailOutput.bpLoanAmount),
|
||||
this.groupedAnalysisRow([
|
||||
this.resultItem('贷款用途', this.formatLoanPurpose(this.retailOutput.loanPurpose)),
|
||||
this.resultItem('营业执照', this.formatBoolean(this.retailOutput.bizProof))
|
||||
], 'BP_贷款用途', this.retailOutput.bpLoanUse),
|
||||
this.analysisRow('循环功能', this.formatBoolean(this.retailOutput.loanLoop), 'BP_循环功能', this.retailOutput.bpLoanLoop)
|
||||
]
|
||||
if (this.guarType === '抵押' || this.guarType === '质押') {
|
||||
rows.push(this.analysisRow('抵押物第三方所有', this.formatBoolean(this.retailOutput.collThirdParty), 'BP_抵押物', this.retailOutput.bpCollateral))
|
||||
}
|
||||
rows.push(
|
||||
this.analysisRow('本金逾期', this.formatBoolean(this.retailOutput.prinOverdue), 'BP_本金逾期', this.retailOutput.bpPrinOverdue),
|
||||
this.analysisRow('利息逾期', this.formatBoolean(this.retailOutput.interestOverdue), 'BP_利息逾期', this.retailOutput.bpIinterestOverdue),
|
||||
this.analysisRow('信用卡逾期', this.formatBoolean(this.retailOutput.cardOverdue), 'BP_信用卡逾期', this.retailOutput.bpCardOverdue),
|
||||
this.totalRow('TOTAL_BP_风险度', this.retailOutput.totalBpRisk)
|
||||
)
|
||||
return rows
|
||||
},
|
||||
corporateLoyaltyRows() {
|
||||
return [
|
||||
this.analysisRow('我行首贷客户', this.formatBoolean(this.corpOutput.isFirstLoan), 'BP_首贷', this.corpOutput.bpFirstLoan),
|
||||
this.analysisRow('用信天数', this.corpOutput.faithDay, 'BP_贷龄', this.corpOutput.bpAgeLoan),
|
||||
this.totalRow('TOTAL_BP_忠诚度', this.corpOutput.totalBpLoyalty)
|
||||
]
|
||||
},
|
||||
corporateContributionRows() {
|
||||
return [
|
||||
this.groupedAnalysisRow([
|
||||
this.resultItem('存款年日均', this.corpOutput.balanceAvg),
|
||||
this.resultItem('贷款年日均', this.corpOutput.loanAvg),
|
||||
this.resultItem('派生率', this.formatRate(this.corpOutput.derivationRate))
|
||||
]),
|
||||
this.totalRow('TOTAL_BP_贡献度', this.corpOutput.totalBpContribution)
|
||||
]
|
||||
},
|
||||
corporateRelevanceRows() {
|
||||
return [
|
||||
this.middleBusinessRow('中间业务_企业_企业互联', this.corpOutput.midEntConnect),
|
||||
this.middleBusinessRow('中间业务_企业_有效价值客户', this.corpOutput.midEntEffect),
|
||||
this.middleBusinessRow('中间业务_企业_国际业务', this.corpOutput.midEntInter),
|
||||
this.middleBusinessRow('中间业务_企业_承兑', this.corpOutput.midEntAccept),
|
||||
this.middleBusinessRow('中间业务_企业_贴现', this.corpOutput.midEntDiscount),
|
||||
this.middleBusinessRow('中间业务_企业_电费代扣', this.corpOutput.midEntEleDdc),
|
||||
this.middleBusinessRow('中间业务_企业_水费代扣', this.corpOutput.midEntWaterDdc),
|
||||
this.groupedAnalysisRow([
|
||||
this.resultItem('代发工资户数', this.corpOutput.payroll),
|
||||
this.resultItem('存量贷款余额', this.corpOutput.invLoanAmount)
|
||||
], 'BP_代发工资', this.corpOutput.bpPayroll, false, '代发工资'),
|
||||
this.groupedAnalysisRow([
|
||||
this.resultItem('净身企业', this.formatBoolean(this.corpOutput.isCleanEnt)),
|
||||
this.resultItem('开立基本结算账户', this.formatBoolean(this.corpOutput.hasSettleAcct)),
|
||||
this.resultItem('绿色贷款', this.formatBoolean(this.corpOutput.isGreenLoan)),
|
||||
this.resultItem('科技型企业', this.formatBoolean(this.corpOutput.isTechEnt)),
|
||||
this.resultItem('贸易和建筑业企业', this.formatBoolean(this.corpOutput.isTradeBuildEnt))
|
||||
], 'BP_企业客户类别', this.corpOutput.bpEntType, false, '客户类型'),
|
||||
this.totalRow('TOTAL_BP_关联度', this.corpOutput.totalBpRelevance)
|
||||
]
|
||||
},
|
||||
corporateRiskRows() {
|
||||
const rows = [
|
||||
this.analysisRow('借款期限', this.corpOutput.loanTerm, 'BP_贷款期限', this.corpOutput.bpLoanTerm),
|
||||
this.analysisRow('申请金额', this.corpOutput.applyAmt, 'BP_贷款额度', this.corpOutput.bpLoanAmount)
|
||||
]
|
||||
if (this.guarType === '抵押' || this.guarType === '质押') {
|
||||
rows.push(this.groupedAnalysisRow([
|
||||
this.resultItem('抵质押类型', this.corpOutput.collType),
|
||||
this.resultItem('抵质押物三方所有', this.formatBoolean(this.corpOutput.collThirdParty))
|
||||
], 'BP_抵押物', this.corpOutput.bpCollateral))
|
||||
}
|
||||
rows.push(
|
||||
this.groupedAnalysisRow([
|
||||
this.resultItem('灰名单客户', this.formatBoolean(this.corpOutput.greyCust)),
|
||||
this.resultItem('黑名单客户', this.formatBoolean(this.corpOutput.blackCust))
|
||||
], 'BP_灰名单与逾期', this.corpOutput.bpGreyOverdue),
|
||||
this.analysisRow('本金逾期', this.formatBoolean(this.corpOutput.prinOverdue), 'BP_本金逾期', this.corpOutput.bpPrinOverdue),
|
||||
this.analysisRow('利息逾期', this.corpOutput.interestOverdue, 'BP_利息逾期', this.corpOutput.bpIinterestOverdue),
|
||||
this.analysisRow('信用卡逾期', this.formatBoolean(this.corpOutput.cardOverdue), 'BP_信用卡逾期', this.corpOutput.bpCardOverdue),
|
||||
this.totalRow('TOTAL_BP_风险度', this.corpOutput.totalBpRisk)
|
||||
)
|
||||
return rows
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatRate,
|
||||
resultItem(label, value, showValue = true) {
|
||||
return {label, value: this.formatOutputValue(value), showValue}
|
||||
},
|
||||
groupedAnalysisRow(results, bpLabel, bpValue, total = false, category = null) {
|
||||
return {results, bpLabel, bpValue: this.formatOutputValue(bpValue), total, category}
|
||||
},
|
||||
analysisRow(resultLabel, resultValue, bpLabel, bpValue) {
|
||||
return this.groupedAnalysisRow([this.resultItem(resultLabel, resultValue)], bpLabel, bpValue)
|
||||
},
|
||||
middleBusinessRow(resultLabel, bpValue, tooltip = null) {
|
||||
return {
|
||||
results: [this.resultItem(resultLabel, null, false)],
|
||||
bpLabel: null,
|
||||
bpValue: this.formatOutputValue(bpValue),
|
||||
total: false,
|
||||
bpTooltip: tooltip,
|
||||
bpTooltipAriaLabel: tooltip ? `${resultLabel}提示` : null
|
||||
}
|
||||
},
|
||||
totalRow(bpLabel, bpValue, bpTooltip = null, bpTooltipAriaLabel = null) {
|
||||
return {summary: true, bpLabel, bpValue: this.formatOutputValue(bpValue), total: true, bpTooltip, bpTooltipAriaLabel}
|
||||
},
|
||||
/** 格式化布尔值为中文 */
|
||||
formatBoolean(value) {
|
||||
if (value === 'true' || value === true || value === '1' || value === 1) return '是'
|
||||
@@ -280,6 +402,98 @@ export default {
|
||||
color: #303133;
|
||||
}
|
||||
|
||||
.analysis-help {
|
||||
margin-left: 4px;
|
||||
color: #909399;
|
||||
cursor: help;
|
||||
font-size: 14px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
.output-analysis-table {
|
||||
width: 100%;
|
||||
table-layout: fixed;
|
||||
border-collapse: collapse;
|
||||
border: 1px solid #ebeef5;
|
||||
color: #606266;
|
||||
font-size: 13px;
|
||||
|
||||
.result-column {
|
||||
width: 60%;
|
||||
}
|
||||
|
||||
.bp-column {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
th,
|
||||
td {
|
||||
padding: 10px 12px;
|
||||
border-right: 1px solid #ebeef5;
|
||||
border-bottom: 1px solid #ebeef5;
|
||||
text-align: left;
|
||||
vertical-align: top;
|
||||
overflow-wrap: anywhere;
|
||||
word-break: break-word;
|
||||
}
|
||||
|
||||
th:last-child,
|
||||
td:last-child {
|
||||
border-right: 0;
|
||||
}
|
||||
|
||||
tbody tr:last-child td {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
th {
|
||||
background: #f5f7fa;
|
||||
color: #909399;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.result-item {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 4px 12px;
|
||||
}
|
||||
|
||||
.result-item + .result-item {
|
||||
margin-top: 6px;
|
||||
padding-top: 6px;
|
||||
border-top: 1px dashed #ebeef5;
|
||||
}
|
||||
|
||||
.result-category {
|
||||
margin-bottom: 8px;
|
||||
color: #303133;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.result-label {
|
||||
min-width: 0;
|
||||
color: #606266;
|
||||
}
|
||||
|
||||
.result-value {
|
||||
margin-left: auto;
|
||||
color: #303133;
|
||||
font-weight: 500;
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.is-total-row {
|
||||
background: #fffaf0;
|
||||
|
||||
td:first-child {
|
||||
color: #606266;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// BP 值样式
|
||||
.bp-value {
|
||||
color: #409eff;
|
||||
@@ -305,4 +519,44 @@ export default {
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.model-output-card {
|
||||
::v-deep .el-card__body {
|
||||
padding: 16px;
|
||||
}
|
||||
|
||||
.output-analysis-table {
|
||||
th,
|
||||
td {
|
||||
padding: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media print {
|
||||
.model-output-card {
|
||||
break-inside: auto !important;
|
||||
page-break-inside: auto !important;
|
||||
|
||||
.output-section {
|
||||
break-inside: auto;
|
||||
page-break-inside: auto;
|
||||
}
|
||||
|
||||
.output-analysis-table {
|
||||
color: #000;
|
||||
|
||||
thead {
|
||||
display: table-header-group;
|
||||
}
|
||||
|
||||
tr {
|
||||
break-inside: avoid !important;
|
||||
page-break-inside: avoid !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<template>
|
||||
<el-dialog title="新增个人利率定价流程" :visible.sync="dialogVisible" width="900px" append-to-body
|
||||
<el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="900px" append-to-body
|
||||
@close="handleClose">
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="140px" class="workflow-create-form">
|
||||
<!-- 基本信息 -->
|
||||
@@ -52,6 +52,14 @@
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="贷款用途" prop="loanPurpose">
|
||||
<el-select v-model="form.loanPurpose" placeholder="请选择贷款用途" style="width: 100%">
|
||||
<el-option label="经营" value="business"/>
|
||||
<el-option label="消费" value="consumer"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="借款期限(年)" prop="loanTerm">
|
||||
<el-select v-model="form.loanTerm" placeholder="请选择借款期限" style="width: 100%">
|
||||
@@ -72,11 +80,16 @@
|
||||
</el-col>
|
||||
<el-col :span="12" v-if="isStockTransfer">
|
||||
<el-form-item label="历史贷款利率" prop="loanRateHistory">
|
||||
<el-input v-model="form.loanRateHistory" placeholder="请选择历史贷款合同" :readonly="true"/>
|
||||
<el-input :value="formatRate(form.loanRateHistory, '')" placeholder="请选择历史贷款合同" :readonly="true"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否有营业执照" prop="bizProof">
|
||||
<el-switch v-model="form.bizProof"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="循环功能" prop="loanLoop">
|
||||
<el-switch v-model="form.loanLoop"/>
|
||||
@@ -87,7 +100,33 @@
|
||||
<!-- 抵质押信息 -->
|
||||
<el-divider v-if="isCollateralGuarantee" content-position="left">抵质押信息</el-divider>
|
||||
<el-row v-if="isCollateralGuarantee">
|
||||
<el-col :span="12">
|
||||
<template v-if="isMortgage">
|
||||
<el-col :span="12">
|
||||
<el-form-item label="抵押物性质" prop="collateralNature">
|
||||
<el-select v-model="form.collateralNature" placeholder="请选择抵押物性质" style="width: 100%">
|
||||
<el-option v-for="item in collateralNatureOptions" :key="item" :label="item" :value="item"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="是否属于九地" prop="isNineArea">
|
||||
<div class="nine-area-control">
|
||||
<el-select
|
||||
v-model="form.isNineArea"
|
||||
:disabled="!requiresNineArea"
|
||||
:placeholder="requiresNineArea ? '请选择是否属于九地' : '无需选择'"
|
||||
>
|
||||
<el-option label="是" value="是"/>
|
||||
<el-option label="否" value="否"/>
|
||||
</el-select>
|
||||
<el-tooltip content="九地是指绍兴、杭州、宁波、上海、北京、广州、深圳、南京、苏州" placement="top">
|
||||
<i class="el-icon-question nine-area-help" tabindex="0" aria-label="查看九地说明"></i>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</template>
|
||||
<el-col :span="12" v-if="isPledge">
|
||||
<el-form-item label="抵质押类型" prop="collType">
|
||||
<el-select v-model="form.collType" placeholder="请选择抵质押类型" style="width: 100%">
|
||||
<el-option v-for="item in collateralTypeOptions" :key="item" :label="item" :value="item"/>
|
||||
@@ -120,8 +159,9 @@
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {createPersonalWorkflow, queryHistoryContracts} from "@/api/loanPricing/workflow"
|
||||
import {createPersonalWorkflow, queryHistoryContracts, updatePersonalWorkflow} from "@/api/loanPricing/workflow"
|
||||
import HistoryContractSelector from "./HistoryContractSelector"
|
||||
import {formatRate} from "@/utils/rate"
|
||||
|
||||
export default {
|
||||
name: "PersonalCreateDialog",
|
||||
@@ -136,6 +176,10 @@ export default {
|
||||
customerMap: {
|
||||
type: Object,
|
||||
default: null
|
||||
},
|
||||
editData: {
|
||||
type: Object,
|
||||
default: null
|
||||
}
|
||||
},
|
||||
data() {
|
||||
@@ -163,11 +207,36 @@ export default {
|
||||
callback()
|
||||
}
|
||||
|
||||
const validateCollateralType = (rule, value, callback) => {
|
||||
if (this.isPledge && !value) {
|
||||
callback(new Error('请选择抵质押类型'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
const validateCollateralNature = (rule, value, callback) => {
|
||||
if (this.isMortgage && !value) {
|
||||
callback(new Error('请选择抵押物性质'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
const validateNineArea = (rule, value, callback) => {
|
||||
if (this.requiresNineArea && !value) {
|
||||
callback(new Error('请选择是否属于九地'))
|
||||
return
|
||||
}
|
||||
callback()
|
||||
}
|
||||
|
||||
return {
|
||||
loanTermOptions: [
|
||||
'1', '2', '3', '4', '5', '6'
|
||||
],
|
||||
submitting: false,
|
||||
resettingForm: false,
|
||||
showHistorySelector: false,
|
||||
historyLoading: false,
|
||||
historyContracts: [],
|
||||
@@ -181,11 +250,15 @@ export default {
|
||||
idNum: this.customerMap ? (this.customerMap.cust_id || '').substring(3) : undefined,
|
||||
guarType: undefined,
|
||||
applyAmt: undefined,
|
||||
loanPurpose: undefined,
|
||||
loanTerm: undefined,
|
||||
businessType: undefined,
|
||||
loanRateHistory: undefined,
|
||||
bizProof: false,
|
||||
loanLoop: false,
|
||||
collType: undefined,
|
||||
collateralNature: undefined,
|
||||
isNineArea: undefined,
|
||||
collThirdParty: false,
|
||||
couponRate: undefined
|
||||
},
|
||||
@@ -210,6 +283,9 @@ export default {
|
||||
applyAmt: [
|
||||
{required: true, validator: validateApplyAmt, trigger: "blur"}
|
||||
],
|
||||
loanPurpose: [
|
||||
{required: true, message: "请选择贷款用途", trigger: "change"}
|
||||
],
|
||||
loanTerm: [
|
||||
{required: true, message: "请选择借款期限", trigger: "change"}
|
||||
],
|
||||
@@ -220,7 +296,13 @@ export default {
|
||||
{required: true, message: "请选择历史贷款合同", trigger: "change"}
|
||||
],
|
||||
collType: [
|
||||
{required: true, message: "请选择抵质押类型", trigger: "change"}
|
||||
{validator: validateCollateralType, trigger: "change"}
|
||||
],
|
||||
collateralNature: [
|
||||
{validator: validateCollateralNature, trigger: "change"}
|
||||
],
|
||||
isNineArea: [
|
||||
{validator: validateNineArea, trigger: "change"}
|
||||
],
|
||||
couponRate: [
|
||||
{validator: validateCouponRate, trigger: "blur"}
|
||||
@@ -240,20 +322,35 @@ export default {
|
||||
isCollateralGuarantee() {
|
||||
return this.form.guarType === '抵押' || this.form.guarType === '质押'
|
||||
},
|
||||
isMortgage() {
|
||||
return this.form.guarType === '抵押'
|
||||
},
|
||||
isPledge() {
|
||||
return this.form.guarType === '质押'
|
||||
},
|
||||
requiresNineArea() {
|
||||
return this.isMortgage && ['普通住宅、土地、厂房', '商业性质不动产'].includes(this.form.collateralNature)
|
||||
},
|
||||
isStockTransfer() {
|
||||
return this.form.businessType === '存量转贷'
|
||||
},
|
||||
isCertificatePledge() {
|
||||
return this.form.guarType === '质押' && this.form.collType === '存单质押'
|
||||
},
|
||||
isEdit() {
|
||||
return !!(this.editData && this.editData.serialNum)
|
||||
},
|
||||
dialogTitle() {
|
||||
return this.isEdit ? '编辑个人利率定价流程' : '新增个人利率定价流程'
|
||||
},
|
||||
collateralTypeOptions() {
|
||||
if (this.form.guarType === '抵押') {
|
||||
return ['一线', '一类', '二类', '三类']
|
||||
}
|
||||
if (this.form.guarType === '质押') {
|
||||
if (this.isPledge) {
|
||||
return ['存单质押', '其他质押']
|
||||
}
|
||||
return []
|
||||
},
|
||||
collateralNatureOptions() {
|
||||
return ['普通住宅、土地、厂房', '商业性质不动产', '其他抵押财产']
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
@@ -263,18 +360,69 @@ export default {
|
||||
}
|
||||
},
|
||||
'form.guarType'(val, oldVal) {
|
||||
if (this.resettingForm) {
|
||||
return
|
||||
}
|
||||
if (val !== oldVal) {
|
||||
this.resetCollateralFields()
|
||||
}
|
||||
},
|
||||
'form.collType'() {
|
||||
if (this.resettingForm) {
|
||||
return
|
||||
}
|
||||
this.resetCouponRateIfNotCertificatePledge()
|
||||
},
|
||||
'form.collateralNature'(val, oldVal) {
|
||||
if (this.resettingForm || val === oldVal) {
|
||||
return
|
||||
}
|
||||
this.handleCollateralNatureChange()
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatRate,
|
||||
/** 表单重置 */
|
||||
reset() {
|
||||
this.form = {
|
||||
this.resettingForm = true
|
||||
this.form = this.buildForm()
|
||||
this.submitting = false
|
||||
this.showHistorySelector = false
|
||||
this.historyLoading = false
|
||||
this.historyContracts = []
|
||||
this.selectedHistoryContract = null
|
||||
this.$nextTick(() => {
|
||||
this.resettingForm = false
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.clearValidate()
|
||||
}
|
||||
})
|
||||
},
|
||||
buildForm() {
|
||||
if (this.isEdit) {
|
||||
return {
|
||||
orgCode: this.editData.orgCode || '892000',
|
||||
runType: this.editData.runType || '1',
|
||||
custIsn: this.editData.custIsn,
|
||||
custName: this.editData.custName,
|
||||
idType: this.editData.idType,
|
||||
idNum: this.editData.idNum,
|
||||
guarType: this.editData.guarType,
|
||||
applyAmt: this.editData.applyAmt,
|
||||
loanPurpose: this.editData.loanPurpose,
|
||||
loanTerm: this.editData.loanTerm,
|
||||
businessType: this.editData.businessType,
|
||||
loanRateHistory: this.editData.loanRateHistory,
|
||||
bizProof: this.isOne(this.editData.bizProof),
|
||||
loanLoop: this.isOne(this.editData.loanLoop),
|
||||
collType: this.editData.guarType === '质押' ? this.editData.collType : undefined,
|
||||
collateralNature: this.editData.collateralNature,
|
||||
isNineArea: this.editData.isNineArea,
|
||||
collThirdParty: this.isOne(this.editData.collThirdParty),
|
||||
couponRate: this.editData.couponRate
|
||||
}
|
||||
}
|
||||
return {
|
||||
orgCode: '892000',
|
||||
runType: '1',
|
||||
custIsn: this.customerMap ? this.customerMap.cust_isn : undefined,
|
||||
@@ -283,24 +431,21 @@ export default {
|
||||
idNum: this.customerMap ? (this.customerMap.cust_id || '').substring(3) : undefined,
|
||||
guarType: undefined,
|
||||
applyAmt: undefined,
|
||||
loanPurpose: undefined,
|
||||
loanTerm: undefined,
|
||||
businessType: undefined,
|
||||
loanRateHistory: undefined,
|
||||
bizProof: false,
|
||||
loanLoop: false,
|
||||
collType: undefined,
|
||||
collateralNature: undefined,
|
||||
isNineArea: undefined,
|
||||
collThirdParty: false,
|
||||
couponRate: undefined
|
||||
}
|
||||
this.submitting = false
|
||||
this.showHistorySelector = false
|
||||
this.historyLoading = false
|
||||
this.historyContracts = []
|
||||
this.selectedHistoryContract = null
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.clearValidate()
|
||||
}
|
||||
})
|
||||
},
|
||||
isOne(value) {
|
||||
return value === true || value === 'true' || value === '1'
|
||||
},
|
||||
/** 对话框关闭处理 */
|
||||
handleClose() {
|
||||
@@ -314,14 +459,35 @@ export default {
|
||||
/** 清空抵质押字段 */
|
||||
resetCollateralFields() {
|
||||
this.form.collType = undefined
|
||||
this.form.collateralNature = undefined
|
||||
this.form.isNineArea = undefined
|
||||
this.form.collThirdParty = false
|
||||
this.resetCouponRateIfNotCertificatePledge()
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.clearValidate(['collType', 'collThirdParty', 'couponRate'])
|
||||
this.$refs.form.clearValidate(['collType', 'collateralNature', 'isNineArea', 'collThirdParty', 'couponRate'])
|
||||
}
|
||||
})
|
||||
},
|
||||
handleCollateralNatureChange() {
|
||||
this.form.isNineArea = undefined
|
||||
this.$nextTick(() => {
|
||||
if (this.$refs.form) {
|
||||
this.$refs.form.clearValidate(['isNineArea'])
|
||||
}
|
||||
})
|
||||
},
|
||||
resolveMortgageCollateralType() {
|
||||
if (this.form.collateralNature === '普通住宅、土地、厂房') {
|
||||
return this.form.isNineArea === '是' ? '一类' : '二类'
|
||||
}
|
||||
if (this.form.collateralNature === '商业性质不动产') {
|
||||
return this.form.isNineArea === '是' ? '二类' : '三类'
|
||||
}
|
||||
if (this.form.collateralNature === '其他抵押财产') {
|
||||
return '四类'
|
||||
}
|
||||
},
|
||||
resetCouponRateIfNotCertificatePledge() {
|
||||
if (!this.isCertificatePledge) {
|
||||
this.form.couponRate = undefined
|
||||
@@ -389,8 +555,15 @@ export default {
|
||||
// 转换开关值为字符串
|
||||
const data = {
|
||||
...this.form,
|
||||
bizProof: this.form.bizProof ? '1' : '0',
|
||||
loanLoop: this.form.loanLoop ? '1' : '0'
|
||||
}
|
||||
if (this.isMortgage) {
|
||||
data.collType = this.resolveMortgageCollateralType()
|
||||
} else {
|
||||
delete data.collateralNature
|
||||
delete data.isNineArea
|
||||
}
|
||||
if (this.isCollateralGuarantee) {
|
||||
data.collThirdParty = this.form.collThirdParty ? '1' : '0'
|
||||
} else {
|
||||
@@ -404,8 +577,11 @@ export default {
|
||||
delete data.couponRate
|
||||
}
|
||||
|
||||
createPersonalWorkflow(data).then(response => {
|
||||
this.$modal.msgSuccess("新增成功")
|
||||
const submitRequest = this.isEdit
|
||||
? updatePersonalWorkflow(this.editData.serialNum, data)
|
||||
: createPersonalWorkflow(data)
|
||||
submitRequest.then(response => {
|
||||
this.$modal.msgSuccess(this.isEdit ? "编辑成功" : "新增成功")
|
||||
this.dialogVisible = false
|
||||
this.$emit('success')
|
||||
}).catch(error => {
|
||||
@@ -428,6 +604,24 @@ export default {
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.nine-area-control {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.nine-area-control .el-select {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.nine-area-help {
|
||||
flex: 0 0 auto;
|
||||
color: #909399;
|
||||
cursor: help;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.workflow-create-form .el-col {
|
||||
width: 100% !important;
|
||||
|
||||
@@ -13,14 +13,10 @@
|
||||
<el-descriptions-item label="客户名称">{{ detailData.custName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户类型">{{ detailData.custType }}</el-descriptions-item>
|
||||
<el-descriptions-item label="申请金额">{{ detailData.applyAmt }} 元</el-descriptions-item>
|
||||
<el-descriptions-item label="基准利率">
|
||||
<span class="rate-value">{{ getBaseLoanRate() }}</span> %
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="浮动BP">
|
||||
<span class="total-bp-value">{{ getTotalBp() }}</span>
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="最终测算利率">
|
||||
<span class="calculate-rate">{{ getCalculateRate() }}</span> %
|
||||
<el-descriptions-item label="期限">{{ detailData.loanTerm || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="担保方式">{{ detailData.guarType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="产品最低利率下限">
|
||||
<span class="rate-value">{{ formatRate(retailOutput && retailOutput.minRateProduct) }}</span> %
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="执行利率">
|
||||
<div class="execute-rate-input-wrapper">
|
||||
@@ -46,13 +42,6 @@
|
||||
|
||||
<!-- 右侧面板 -->
|
||||
<div class="right-panel">
|
||||
<!-- 模型输出卡片 -->
|
||||
<ModelOutputDisplay
|
||||
:cust-type="detailData.custType"
|
||||
:retail-output="retailOutput"
|
||||
:corp-output="null"
|
||||
/>
|
||||
|
||||
<!-- 流程详情卡片 -->
|
||||
<el-card class="detail-card">
|
||||
<div slot="header" class="card-header">
|
||||
@@ -64,10 +53,18 @@
|
||||
<h4 class="section-title">基本信息</h4>
|
||||
<el-descriptions :column="2" border>
|
||||
<el-descriptions-item label="机构编码">{{ detailData.orgCode }}</el-descriptions-item>
|
||||
<el-descriptions-item label="运行模式">{{ detailData.runType }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户内码">{{ detailData.custIsn }}</el-descriptions-item>
|
||||
<el-descriptions-item label="客户名称">{{ detailData.custName }}</el-descriptions-item>
|
||||
<el-descriptions-item label="证件类型">{{ detailData.idType }}</el-descriptions-item>
|
||||
<el-descriptions-item label="证件号码">{{ detailData.idNum }}</el-descriptions-item>
|
||||
<el-descriptions-item label="灰名单客户">{{
|
||||
formatBoolean(retailOutput && retailOutput.greyCust)
|
||||
}}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="黑名单客户">{{
|
||||
formatBoolean(retailOutput && retailOutput.blackCust)
|
||||
}}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="创建时间">{{ detailData.createTime }}</el-descriptions-item>
|
||||
<el-descriptions-item label="创建者">{{ detailData.createBy }}</el-descriptions-item>
|
||||
</el-descriptions>
|
||||
@@ -81,13 +78,19 @@
|
||||
<el-descriptions-item label="申请金额">{{ detailData.applyAmt }} 元</el-descriptions-item>
|
||||
<el-descriptions-item label="贷款用途">{{ formatLoanPurpose(detailData.loanPurpose) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="业务种类">{{ detailData.businessType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="历史贷款利率">{{ detailData.loanRateHistory || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="历史贷款利率">{{ formatRate(detailData.loanRateHistory) }}</el-descriptions-item>
|
||||
<el-descriptions-item label="借款期限">{{ detailData.loanTerm || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="是否有经营佐证">{{
|
||||
<el-descriptions-item label="是否有营业执照">{{
|
||||
formatBoolean(detailData.bizProof)
|
||||
}}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="循环功能">{{ formatBoolean(detailData.loanLoop) }}</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detailData.guarType === '抵押'" label="抵押物性质">
|
||||
{{ detailData.collateralNature || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item v-if="detailData.guarType === '抵押'" label="是否属于九地">
|
||||
{{ detailData.isNineArea || '-' }}
|
||||
</el-descriptions-item>
|
||||
<el-descriptions-item label="抵质押类型">{{ detailData.collType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="抵质押物是否三方所有">{{
|
||||
formatBoolean(detailData.collThirdParty)
|
||||
@@ -97,6 +100,14 @@
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<!-- 模型输出卡片 -->
|
||||
<ModelOutputDisplay
|
||||
:cust-type="detailData.custType"
|
||||
:guar-type="detailData.guarType"
|
||||
:retail-output="retailOutput"
|
||||
:corp-output="null"
|
||||
/>
|
||||
|
||||
<!-- 议价池卡片 -->
|
||||
<BargainingPoolDisplay
|
||||
v-if="bargainingPool"
|
||||
@@ -114,6 +125,7 @@
|
||||
import {setExecuteRate} from "@/api/loanPricing/workflow"
|
||||
import ModelOutputDisplay from "./ModelOutputDisplay.vue"
|
||||
import BargainingPoolDisplay from "./BargainingPoolDisplay.vue"
|
||||
import {formatRate} from "@/utils/rate"
|
||||
|
||||
export default {
|
||||
name: "PersonalWorkflowDetail",
|
||||
@@ -144,12 +156,13 @@ export default {
|
||||
watch: {
|
||||
'detailData.executeRate': {
|
||||
handler(newVal) {
|
||||
this.executeRateInput = newVal || ''
|
||||
this.executeRateInput = formatRate(newVal, '')
|
||||
},
|
||||
immediate: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatRate,
|
||||
/** 格式化布尔值为中文 */
|
||||
formatBoolean(value) {
|
||||
if (value === 'true' || value === true || value === '1' || value === 1) return '是'
|
||||
@@ -162,18 +175,6 @@ export default {
|
||||
if (value === 'business') return '经营'
|
||||
return value || '-'
|
||||
},
|
||||
/** 获取基准利率 */
|
||||
getBaseLoanRate() {
|
||||
return this.retailOutput?.baseLoanRate || '-'
|
||||
},
|
||||
/** 获取浮动BP */
|
||||
getTotalBp() {
|
||||
return this.retailOutput?.totalBp || '-'
|
||||
},
|
||||
/** 获取最终测算利率 */
|
||||
getCalculateRate() {
|
||||
return this.retailOutput?.finalCalculateRate || '-'
|
||||
},
|
||||
/** 设定执行利率 */
|
||||
handleSetExecuteRate() {
|
||||
const value = this.executeRateInput
|
||||
@@ -255,16 +256,6 @@ export default {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.total-bp-value {
|
||||
color: #e6a23c;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.calculate-rate {
|
||||
color: #f56c6c;
|
||||
font-weight: 600;
|
||||
font-size: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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,32 +1,67 @@
|
||||
<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
|
||||
:model="queryParams"
|
||||
ref="queryForm"
|
||||
size="small"
|
||||
:inline="true"
|
||||
v-show="showSearch"
|
||||
label-width="68px"
|
||||
@submit.native.prevent="handleQuery"
|
||||
>
|
||||
<el-form-item label="客户信息" prop="custIsn">
|
||||
<el-input
|
||||
v-model="queryParams.custIsn"
|
||||
placeholder="请输入客户内码"
|
||||
placeholder="请输入客户内码/证件号/客户名"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
@keydown.enter.native.prevent="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="创建者" prop="createBy">
|
||||
<el-form-item v-if="showAllSearchItems" label="客户类型" prop="custType">
|
||||
<el-select v-model="queryParams.custType" placeholder="请选择客户类型" clearable style="width: 160px">
|
||||
<el-option label="个人" value="个人" />
|
||||
<el-option label="企业" value="企业" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="showAllSearchItems" label="担保方式" prop="guarType">
|
||||
<el-select v-model="queryParams.guarType" placeholder="请选择担保方式" clearable style="width: 160px">
|
||||
<el-option label="信用" value="信用" />
|
||||
<el-option label="保证" value="保证" />
|
||||
<el-option label="抵押" value="抵押" />
|
||||
<el-option label="质押" value="质押" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item v-if="showAllSearchItems" label="创建者" prop="createBy">
|
||||
<el-input
|
||||
v-model="queryParams.createBy"
|
||||
placeholder="请输入创建者"
|
||||
clearable
|
||||
@keyup.enter.native="handleQuery"
|
||||
@keydown.enter.native.prevent="handleQuery"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="机构号" prop="orgCode">
|
||||
<el-input
|
||||
v-model="queryParams.orgCode"
|
||||
placeholder="请输入机构号"
|
||||
<el-form-item v-if="showAllSearchItems" 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 v-if="showAllSearchItems" label="创建时间">
|
||||
<el-date-picker
|
||||
v-model="dateRange"
|
||||
type="daterange"
|
||||
value-format="yyyy-MM-dd"
|
||||
range-separator="-"
|
||||
start-placeholder="开始日期"
|
||||
end-placeholder="结束日期"
|
||||
style="width: 240px"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" @click="handleQuery">搜索</el-button>
|
||||
<el-button type="primary" icon="el-icon-search" size="mini" native-type="submit">搜索</el-button>
|
||||
<el-button icon="el-icon-refresh" size="mini" @click="resetQuery">重置</el-button>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
@@ -44,21 +79,31 @@
|
||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||
</el-row>
|
||||
|
||||
<el-table v-loading="loading" :data="workflowList">
|
||||
<el-table-column label="业务方流水号" align="center" prop="serialNum" width="180" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="客户名称" align="center" prop="custName" :show-overflow-tooltip="true" />
|
||||
<el-table-column label="客户类型" align="center" prop="custType" width="100" />
|
||||
<el-table-column label="担保方式" align="center" prop="guarType" width="100" />
|
||||
<el-table-column label="申请金额(元)" align="center" prop="applyAmt" width="120" />
|
||||
<el-table-column label="测算利率(%)" align="center" prop="calculateRate" width="100" />
|
||||
<el-table-column label="执行利率(%)" align="center" prop="executeRate" width="100" />
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" width="160">
|
||||
<el-table v-loading="loading" :data="workflowList" class="workflow-table" style="width: 100%">
|
||||
<el-table-column label="业务方流水号" align="center" prop="serialNum" min-width="320" class-name="workflow-important-column" />
|
||||
<el-table-column label="客户内码" align="center" prop="custIsn" min-width="320" class-name="workflow-important-column" />
|
||||
<el-table-column label="客户名称" align="center" prop="custName" min-width="240" class-name="workflow-important-column" />
|
||||
<el-table-column label="客户类型" align="center" prop="custType" min-width="110" />
|
||||
<el-table-column label="担保方式" align="center" prop="guarType" min-width="120" />
|
||||
<el-table-column label="申请金额(元)" align="center" prop="applyAmt" min-width="140" />
|
||||
<el-table-column label="最终测算利率(%)" align="center" prop="calculateRate" min-width="150">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatRate(scope.row.calculateRate) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="执行利率(%)" align="center" prop="executeRate" min-width="120">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ formatRate(scope.row.executeRate) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建时间" align="center" prop="createTime" min-width="170">
|
||||
<template slot-scope="scope">
|
||||
<span>{{ parseTime(scope.row.createTime) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="创建者" align="center" prop="createBy" width="120" />
|
||||
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
|
||||
<el-table-column label="创建者" align="center" prop="createBy" min-width="220" class-name="workflow-important-column" />
|
||||
<el-table-column label="机构号" align="center" prop="deptId" min-width="110" />
|
||||
<el-table-column label="操作" align="center" fixed="right" min-width="150" class-name="small-padding fixed-width">
|
||||
<template slot-scope="scope">
|
||||
<el-button
|
||||
size="mini"
|
||||
@@ -66,6 +111,13 @@
|
||||
icon="el-icon-view"
|
||||
@click="handleView(scope.row)"
|
||||
>查看</el-button>
|
||||
<el-button
|
||||
v-if="canEdit(scope.row)"
|
||||
size="mini"
|
||||
type="text"
|
||||
icon="el-icon-edit"
|
||||
@click="handleEdit(scope.row)"
|
||||
>编辑</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
@@ -92,6 +144,7 @@
|
||||
<personal-create-dialog
|
||||
:visible.sync="showPersonalDialog"
|
||||
:customer-map="selectedCustomerMap"
|
||||
:edit-data="editWorkflow"
|
||||
@success="handleCreateSuccess"
|
||||
/>
|
||||
|
||||
@@ -99,21 +152,28 @@
|
||||
<corporate-create-dialog
|
||||
:visible.sync="showCorporateDialog"
|
||||
:customer-map="selectedCustomerMap"
|
||||
:edit-data="editWorkflow"
|
||||
@success="handleCreateSuccess"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import {listWorkflow} from "@/api/loanPricing/workflow"
|
||||
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,
|
||||
@@ -129,6 +189,10 @@ export default {
|
||||
total: 0,
|
||||
// 利率定价流程表格数据
|
||||
workflowList: [],
|
||||
// 机构树选项
|
||||
deptOptions: [],
|
||||
// 创建时间范围
|
||||
dateRange: [],
|
||||
// 是否显示客户类型选择弹出层
|
||||
showTypeSelector: false,
|
||||
// 是否显示客户号查询选择弹出层
|
||||
@@ -137,6 +201,8 @@ export default {
|
||||
selectedCustomerType: undefined,
|
||||
// 当前选择的客户号映射记录
|
||||
selectedCustomerMap: null,
|
||||
// 当前编辑的流程记录
|
||||
editWorkflow: null,
|
||||
// 是否显示个人客户创建弹出层
|
||||
showPersonalDialog: false,
|
||||
// 是否显示企业客户创建弹出层
|
||||
@@ -146,13 +212,29 @@ export default {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
custIsn: undefined,
|
||||
custType: undefined,
|
||||
guarType: undefined,
|
||||
createBy: undefined,
|
||||
orgCode: undefined
|
||||
deptId: undefined
|
||||
}
|
||||
}
|
||||
},
|
||||
created() {
|
||||
this.getList()
|
||||
this.getDeptTree()
|
||||
},
|
||||
computed: {
|
||||
...mapGetters([
|
||||
'name',
|
||||
'nickName',
|
||||
'roles'
|
||||
]),
|
||||
showAllSearchItems() {
|
||||
return !this.roles.includes('common')
|
||||
},
|
||||
currentCreateBy() {
|
||||
return `${this.nickName}-${this.name}`
|
||||
}
|
||||
},
|
||||
activated() {
|
||||
this.getList()
|
||||
@@ -170,10 +252,23 @@ export default {
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
formatRate,
|
||||
/** 查询机构下拉树结构 */
|
||||
getDeptTree() {
|
||||
deptTreeSelect().then(response => {
|
||||
this.deptOptions = response.data
|
||||
})
|
||||
},
|
||||
/** 查询利率定价流程列表 */
|
||||
getList() {
|
||||
this.loading = true
|
||||
listWorkflow(this.queryParams).then(response => {
|
||||
const hasDateRange = Array.isArray(this.dateRange) && this.dateRange.length === 2
|
||||
const query = {
|
||||
...this.queryParams,
|
||||
beginCreateTime: hasDateRange ? `${this.dateRange[0]} 00:00:00` : undefined,
|
||||
endCreateTime: hasDateRange ? `${this.dateRange[1]} 23:59:59` : undefined
|
||||
}
|
||||
listWorkflow(query).then(response => {
|
||||
this.workflowList = response.rows
|
||||
this.total = response.total
|
||||
this.loading = false
|
||||
@@ -186,6 +281,7 @@ export default {
|
||||
},
|
||||
/** 重置按钮操作 */
|
||||
resetQuery() {
|
||||
this.dateRange = []
|
||||
this.resetForm("queryForm")
|
||||
this.handleQuery()
|
||||
},
|
||||
@@ -196,8 +292,26 @@ export default {
|
||||
params: { serialNum: row.serialNum }
|
||||
})
|
||||
},
|
||||
/** 是否允许编辑 */
|
||||
canEdit(row) {
|
||||
return row && row.createBy === this.currentCreateBy
|
||||
},
|
||||
/** 编辑操作 */
|
||||
handleEdit(row) {
|
||||
getWorkflowEdit(row.serialNum).then(response => {
|
||||
this.editWorkflow = response.data
|
||||
this.selectedCustomerMap = null
|
||||
this.selectedCustomerType = undefined
|
||||
if (this.editWorkflow.custType === '个人') {
|
||||
this.showPersonalDialog = true
|
||||
} else if (this.editWorkflow.custType === '企业') {
|
||||
this.showCorporateDialog = true
|
||||
}
|
||||
})
|
||||
},
|
||||
/** 新增按钮操作 */
|
||||
handleAdd() {
|
||||
this.editWorkflow = null
|
||||
this.showTypeSelector = true
|
||||
},
|
||||
/** 选择客户类型回调 */
|
||||
@@ -208,6 +322,7 @@ export default {
|
||||
},
|
||||
/** 选择客户号映射记录回调 */
|
||||
handleCustomerMapSelect(row) {
|
||||
this.editWorkflow = null
|
||||
this.selectedCustomerMap = row
|
||||
if (this.selectedCustomerType === 'personal') {
|
||||
this.showPersonalDialog = true
|
||||
@@ -224,7 +339,17 @@ export default {
|
||||
clearSelectedCustomer() {
|
||||
this.selectedCustomerMap = null
|
||||
this.selectedCustomerType = undefined
|
||||
this.editWorkflow = null
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.workflow-table ::v-deep .workflow-important-column .cell {
|
||||
overflow: hidden;
|
||||
text-overflow: clip;
|
||||
white-space: nowrap;
|
||||
word-break: normal;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -95,7 +95,7 @@
|
||||
</el-row>
|
||||
|
||||
<!-- 添加或修改用户配置对话框 -->
|
||||
<el-dialog :title="title" :visible.sync="open" width="600px" append-to-body>
|
||||
<el-dialog :title="title" :visible.sync="open" width="80%" append-to-body>
|
||||
<el-form ref="form" :model="form" :rules="rules" label-width="80px">
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
@@ -562,4 +562,4 @@ export default {
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</script>
|
||||
|
||||
@@ -10,10 +10,21 @@ const personalCreateDialog = read('src/views/loanPricing/workflow/components/Per
|
||||
const personalDetail = read('src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue')
|
||||
|
||||
assert(
|
||||
!personalCreateDialog.includes('label="贷款用途"') &&
|
||||
!personalCreateDialog.includes('prop="loanPurpose"') &&
|
||||
!personalCreateDialog.includes('loanPurpose:'),
|
||||
'个人新增弹窗不应继续保留贷款用途字段'
|
||||
personalCreateDialog.includes('updatePersonalWorkflow') &&
|
||||
personalCreateDialog.includes("this.isEdit ? '编辑个人利率定价流程' : '新增个人利率定价流程'") &&
|
||||
personalCreateDialog.includes('editData.serialNum') &&
|
||||
personalCreateDialog.includes('updatePersonalWorkflow(this.editData.serialNum, data)') &&
|
||||
personalCreateDialog.includes(': createPersonalWorkflow(data)'),
|
||||
'个人弹窗应支持编辑模式回显并调用个人更新接口'
|
||||
)
|
||||
|
||||
assert(
|
||||
personalCreateDialog.includes('label="贷款用途"') &&
|
||||
personalCreateDialog.includes('prop="loanPurpose"') &&
|
||||
personalCreateDialog.includes('loanPurpose: undefined') &&
|
||||
personalCreateDialog.includes('loanPurpose: this.editData.loanPurpose') &&
|
||||
personalCreateDialog.includes('请选择贷款用途'),
|
||||
'个人新增弹窗缺少贷款用途、必填校验或编辑回显'
|
||||
)
|
||||
|
||||
assert(
|
||||
@@ -22,8 +33,9 @@ assert(
|
||||
)
|
||||
|
||||
assert(
|
||||
!personalCreateDialog.includes("value=\"consumer\"") && !personalCreateDialog.includes("value=\"business\""),
|
||||
'个人新增弹窗不应继续保留贷款用途选项'
|
||||
personalCreateDialog.includes('label="经营" value="business"') &&
|
||||
personalCreateDialog.includes('label="消费" value="consumer"'),
|
||||
'个人新增弹窗贷款用途选项或协议值不正确'
|
||||
)
|
||||
|
||||
assert(
|
||||
@@ -43,25 +55,49 @@ assert(
|
||||
|
||||
assert(
|
||||
personalCreateDialog.includes('collateralTypeOptions') &&
|
||||
personalCreateDialog.includes("return ['一线', '一类', '二类', '三类']") &&
|
||||
personalCreateDialog.includes("return ['存单质押', '其他质押']"),
|
||||
'个人新增弹窗抵质押类型选项未按担保方式动态切换'
|
||||
'个人新增弹窗应保留质押类型选项'
|
||||
)
|
||||
|
||||
assert(
|
||||
personalCreateDialog.includes('{required: true, message: "请选择抵质押类型", trigger: "change"}'),
|
||||
'个人新增弹窗抵质押类型应为必填'
|
||||
personalCreateDialog.includes('label="抵押物性质"') &&
|
||||
personalCreateDialog.includes('prop="collateralNature"') &&
|
||||
personalCreateDialog.includes("return ['普通住宅、土地、厂房', '商业性质不动产', '其他抵押财产']") &&
|
||||
personalCreateDialog.includes('label="是否属于九地"') &&
|
||||
personalCreateDialog.includes('九地是指绍兴、杭州、宁波、上海、北京、广州、深圳、南京、苏州') &&
|
||||
personalCreateDialog.includes(':disabled="!requiresNineArea"') &&
|
||||
personalCreateDialog.includes("'无需选择'"),
|
||||
'个人新增弹窗缺少抵押物性质、九地选择或九地说明'
|
||||
)
|
||||
|
||||
assert(
|
||||
!personalCreateDialog.includes('label="是否有经营佐证"') &&
|
||||
!personalCreateDialog.includes('prop="bizProof"') &&
|
||||
!personalCreateDialog.includes('bizProof:') &&
|
||||
personalCreateDialog.includes("return this.form.isNineArea === '是' ? '一类' : '二类'") &&
|
||||
personalCreateDialog.includes("return this.form.isNineArea === '是' ? '二类' : '三类'") &&
|
||||
personalCreateDialog.includes("return '四类'"),
|
||||
'个人新增弹窗抵押物类型映射不完整'
|
||||
)
|
||||
|
||||
assert(
|
||||
personalCreateDialog.includes("if (this.isPledge && !value)") &&
|
||||
personalCreateDialog.includes("if (this.isMortgage && !value)") &&
|
||||
personalCreateDialog.includes("if (this.requiresNineArea && !value)"),
|
||||
'个人新增弹窗缺少抵押和质押的条件校验'
|
||||
)
|
||||
|
||||
assert(
|
||||
personalCreateDialog.includes('label="是否有营业执照"') &&
|
||||
personalCreateDialog.includes('prop="bizProof"') &&
|
||||
personalCreateDialog.includes('bizProof: false') &&
|
||||
personalCreateDialog.includes('bizProof: this.isOne(this.editData.bizProof)') &&
|
||||
personalCreateDialog.includes("bizProof: this.form.bizProof ? '1' : '0'") &&
|
||||
personalCreateDialog.includes("loanLoop: this.form.loanLoop ? '1' : '0'") &&
|
||||
personalCreateDialog.includes("data.collThirdParty = this.form.collThirdParty ? '1' : '0'") &&
|
||||
personalCreateDialog.includes('data.collType = this.resolveMortgageCollateralType()') &&
|
||||
personalCreateDialog.includes('collateralNature: this.editData.collateralNature') &&
|
||||
personalCreateDialog.includes('isNineArea: this.editData.isNineArea') &&
|
||||
personalCreateDialog.includes('delete data.collType') &&
|
||||
personalCreateDialog.includes('delete data.collThirdParty'),
|
||||
'个人新增弹窗开关字段或非抵质押提交字段处理不正确'
|
||||
'个人新增弹窗营业执照、抵押来源字段、开关字段或非抵质押提交字段处理不正确'
|
||||
)
|
||||
|
||||
assert(
|
||||
@@ -69,6 +105,19 @@ assert(
|
||||
'个人详情页缺少贷款用途展示'
|
||||
)
|
||||
|
||||
assert(
|
||||
personalDetail.includes('label="是否有营业执照"') && personalDetail.includes('detailData.bizProof'),
|
||||
'个人详情页缺少营业执照展示'
|
||||
)
|
||||
|
||||
assert(
|
||||
personalDetail.includes('label="抵押物性质"') &&
|
||||
personalDetail.includes('detailData.collateralNature') &&
|
||||
personalDetail.includes('label="是否属于九地"') &&
|
||||
personalDetail.includes('detailData.isNineArea'),
|
||||
'个人详情页缺少抵押物性质或九地展示'
|
||||
)
|
||||
|
||||
assert(
|
||||
personalDetail.includes("value === '1'") && personalDetail.includes("value === '0'"),
|
||||
'个人详情页布尔格式化未兼容 1/0'
|
||||
|
||||
@@ -7,15 +7,22 @@ function read(relativePath) {
|
||||
}
|
||||
|
||||
const personalDetail = read('src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue')
|
||||
const summaryStart = personalDetail.indexOf('<el-card class="summary-card">')
|
||||
const summaryEnd = personalDetail.indexOf('</el-card>', summaryStart)
|
||||
const summaryCard = personalDetail.slice(summaryStart, summaryEnd)
|
||||
|
||||
assert(summaryStart !== -1 && summaryEnd !== -1, '个人流程详情缺少关键信息卡片')
|
||||
|
||||
assert(
|
||||
/label="最终测算利率"/.test(personalDetail),
|
||||
'个人流程详情左侧缺少“最终测算利率”标签'
|
||||
!/label="(?:基准利率|浮动BP|最终测算利率)"/.test(summaryCard),
|
||||
'个人流程详情关键信息不应展示基准利率、浮动BP或最终测算利率'
|
||||
)
|
||||
|
||||
assert(
|
||||
/return this\.retailOutput\?\.finalCalculateRate \|\| '-'/.test(personalDetail),
|
||||
'个人流程详情没有使用 finalCalculateRate 展示最终测算利率'
|
||||
/label="期限"/.test(summaryCard)
|
||||
&& /label="担保方式"/.test(summaryCard)
|
||||
&& /label="产品最低利率下限"/.test(summaryCard),
|
||||
'个人流程详情关键信息缺少期限、担保方式或产品最低利率下限'
|
||||
)
|
||||
|
||||
console.log('personal final calculate rate display assertions passed')
|
||||
console.log('personal key information display assertions passed')
|
||||
|
||||
@@ -9,18 +9,55 @@ function read(relativePath) {
|
||||
const personalDetail = read('src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue')
|
||||
const modelOutput = read('src/views/loanPricing/workflow/components/ModelOutputDisplay.vue')
|
||||
|
||||
function extractCard(source, className) {
|
||||
const start = source.indexOf(`<el-card class="${className}">`)
|
||||
const end = source.indexOf('</el-card>', start)
|
||||
assert(start !== -1 && end !== -1, `缺少卡片: ${className}`)
|
||||
return source.slice(start, end)
|
||||
}
|
||||
|
||||
const summaryCard = extractCard(personalDetail, 'summary-card')
|
||||
const detailCard = extractCard(personalDetail, 'detail-card')
|
||||
const businessInfoStart = detailCard.indexOf('<h4 class="section-title">业务信息</h4>')
|
||||
const basicInfo = detailCard.slice(0, businessInfoStart)
|
||||
const businessInfo = detailCard.slice(businessInfoStart)
|
||||
|
||||
assert(
|
||||
personalDetail.includes('label="借款期限"') && personalDetail.includes('detailData.loanTerm'),
|
||||
'个人详情页缺少借款期限展示'
|
||||
!basicInfo.includes('label="借款期限"')
|
||||
&& businessInfo.includes('label="借款期限"')
|
||||
&& businessInfo.includes('detailData.loanTerm'),
|
||||
'个人详情页借款期限未展示在业务信息中'
|
||||
)
|
||||
|
||||
;['期限', '担保方式', '产品最低利率下限'].forEach((label) => {
|
||||
assert(summaryCard.includes(`label="${label}"`), `个人流程详情关键信息缺少字段: ${label}`)
|
||||
})
|
||||
|
||||
;['基准利率', '浮动BP', '最终测算利率'].forEach((label) => {
|
||||
assert(!summaryCard.includes(`label="${label}"`), `个人流程详情关键信息不应展示字段: ${label}`)
|
||||
})
|
||||
|
||||
assert(
|
||||
personalDetail.includes('formatRate(retailOutput && retailOutput.minRateProduct)'),
|
||||
'个人流程详情关键信息未展示产品最低利率下限'
|
||||
)
|
||||
|
||||
assert(
|
||||
personalDetail.includes(':guar-type="detailData.guarType"'),
|
||||
'个人模型输出未使用流程担保方式控制抵押物行'
|
||||
)
|
||||
|
||||
const requiredRetailFields = [
|
||||
'retailOutput.greyBlackCust',
|
||||
'retailOutput.loanRateHistory',
|
||||
'retailOutput.minRateProduct',
|
||||
'retailOutput.smoothRange',
|
||||
'retailOutput.finalCalculateRate',
|
||||
'retailOutput.referenceRate'
|
||||
'retailOutput.referenceRate',
|
||||
'retailOutput.branchBp',
|
||||
'retailOutput.bpLoanUse',
|
||||
'retailOutput.midPerGasDdc',
|
||||
'retailOutput.bpPrinOverdue',
|
||||
'retailOutput.bpIinterestOverdue',
|
||||
'retailOutput.bpCardOverdue'
|
||||
]
|
||||
|
||||
requiredRetailFields.forEach((field) => {
|
||||
@@ -28,9 +65,34 @@ requiredRetailFields.forEach((field) => {
|
||||
})
|
||||
|
||||
assert(
|
||||
modelOutput.indexOf('label="灰黑名单客户"') > modelOutput.indexOf('<h4 class="section-title">基本信息</h4>')
|
||||
&& modelOutput.indexOf('label="灰黑名单客户"') < modelOutput.indexOf('<h4 class="section-title">测算结果</h4>'),
|
||||
'灰黑名单客户未展示在个人模型输出基础信息中'
|
||||
modelOutput.includes("this.resultItem('营业执照', this.formatBoolean(this.retailOutput.bizProof))"),
|
||||
'个人风险度缺少营业执照或 BP_贷款用途关联展示'
|
||||
)
|
||||
|
||||
assert(
|
||||
basicInfo.includes('label="灰名单客户"')
|
||||
&& basicInfo.includes('retailOutput && retailOutput.greyCust')
|
||||
&& basicInfo.includes('label="黑名单客户"')
|
||||
&& basicInfo.includes('retailOutput && retailOutput.blackCust'),
|
||||
'个人流程详情基本信息缺少灰名单客户或黑名单客户'
|
||||
)
|
||||
|
||||
assert(
|
||||
!modelOutput.includes("this.resultItem('灰名单客户', this.formatBoolean(this.retailOutput.greyCust))")
|
||||
&& !modelOutput.includes("this.resultItem('黑名单客户', this.formatBoolean(this.retailOutput.blackCust))")
|
||||
&& !modelOutput.includes("'BP_灰名单与逾期', this.retailOutput.bpGreyOverdue"),
|
||||
'个人风险度不应继续展示灰黑客户或旧聚合BP'
|
||||
)
|
||||
|
||||
assert(
|
||||
!basicInfo.includes('label="基准利率"')
|
||||
&& !basicInfo.includes('retailOutput && retailOutput.baseLoanRate'),
|
||||
'个人流程详情基本信息不应展示基准利率'
|
||||
)
|
||||
|
||||
assert(
|
||||
!detailCard.includes('label="运行模式"') && !modelOutput.includes('<h4 class="section-title">基本信息</h4>'),
|
||||
'个人详情仍展示运行模式或模型输出基本信息'
|
||||
)
|
||||
|
||||
console.log('retail display fields assertions passed')
|
||||
|
||||
@@ -6,22 +6,22 @@ function read(relativePath) {
|
||||
return fs.readFileSync(path.join(__dirname, '..', relativePath), 'utf8')
|
||||
}
|
||||
|
||||
function assertModelOutputBeforeDetailCard(source, label) {
|
||||
function assertDetailCardBeforeModelOutput(source, label) {
|
||||
const modelOutputIndex = source.indexOf('<ModelOutputDisplay')
|
||||
const detailCardIndex = source.indexOf('<el-card class="detail-card">')
|
||||
|
||||
assert(modelOutputIndex !== -1, `${label} 缺少模型输出卡片`)
|
||||
assert(detailCardIndex !== -1, `${label} 缺少流程详情卡片`)
|
||||
assert(
|
||||
modelOutputIndex < detailCardIndex,
|
||||
`${label} 的模型输出卡片应位于流程详情卡片上方`
|
||||
detailCardIndex < modelOutputIndex,
|
||||
`${label} 的流程详情卡片应位于模型输出卡片上方`
|
||||
)
|
||||
}
|
||||
|
||||
const personalDetail = read('src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue')
|
||||
const corporateDetail = read('src/views/loanPricing/workflow/components/CorporateWorkflowDetail.vue')
|
||||
|
||||
assertModelOutputBeforeDetailCard(personalDetail, '个人流程详情')
|
||||
assertModelOutputBeforeDetailCard(corporateDetail, '企业流程详情')
|
||||
assertDetailCardBeforeModelOutput(personalDetail, '个人流程详情')
|
||||
assertDetailCardBeforeModelOutput(corporateDetail, '企业流程详情')
|
||||
|
||||
console.log('workflow detail card order assertions passed')
|
||||
|
||||
@@ -20,7 +20,18 @@ function loadComponentOptions(filePath) {
|
||||
}
|
||||
|
||||
const stubImports = importNames.map(name => `const ${name} = {};`).join('\n')
|
||||
const transformed = `${stubImports}\n${scriptMatch[1]}`
|
||||
const namedImportStubs = `
|
||||
const listWorkflow = function() {};
|
||||
const getWorkflowEdit = function() {};
|
||||
const formatRate = function(value) { return value; };
|
||||
const mapGetters = function(names) {
|
||||
const result = {};
|
||||
names.forEach((name) => {
|
||||
result[name] = function() { return ''; };
|
||||
});
|
||||
return result;
|
||||
};`
|
||||
const transformed = `${stubImports}\n${namedImportStubs}\n${scriptMatch[1]}`
|
||||
.replace(/^import .*$/gm, '')
|
||||
.replace(/export default/, 'module.exports =')
|
||||
|
||||
@@ -37,8 +48,30 @@ function loadComponentOptions(filePath) {
|
||||
|
||||
const filePath = path.resolve(__dirname, '../src/views/loanPricing/workflow/index.vue')
|
||||
const component = loadComponentOptions(filePath)
|
||||
const source = fs.readFileSync(filePath, 'utf8')
|
||||
|
||||
assert.strictEqual(typeof component.activated, 'function', '流程列表页应在激活时刷新数据')
|
||||
assert(source.includes('icon="el-icon-edit"') && source.includes('>编辑</el-button>'), '流程列表操作列应包含编辑按钮')
|
||||
assert(source.includes('v-if="canEdit(scope.row)"'), '流程列表编辑按钮应只对可编辑行显示')
|
||||
assert(source.includes('row.createBy === this.currentCreateBy'), '流程列表应按创建者判断编辑按钮权限')
|
||||
assert(source.includes('getWorkflowEdit(row.serialNum)'), '编辑操作应先查询编辑数据')
|
||||
assert(source.includes('v-model="queryParams.custType"'), '流程列表应包含客户类型筛选')
|
||||
assert(source.includes('v-model="queryParams.guarType"'), '流程列表应包含担保方式筛选')
|
||||
assert(source.includes('v-model="dateRange"'), '流程列表应包含创建时间范围筛选')
|
||||
assert(source.includes('value-format="yyyy-MM-dd"'), '流程列表创建时间筛选应使用日期值')
|
||||
assert(source.includes('beginCreateTime: hasDateRange ? `${this.dateRange[0]} 00:00:00` : undefined'), '流程列表应传递当天零点的顶层创建时间起始参数')
|
||||
assert(source.includes('endCreateTime: hasDateRange ? `${this.dateRange[1]} 23:59:59` : undefined'), '流程列表应传递当天末秒的顶层创建时间结束参数')
|
||||
assert(source.includes('label="机构号"') && source.includes('prop="deptId"'), '流程列表应展示机构号')
|
||||
assert(source.indexOf('label="机构号"') > source.indexOf('label="创建者"'), '机构号列应位于创建者列之后')
|
||||
assert.strictEqual((source.match(/v-if="showAllSearchItems"/g) || []).length, 5, '五个高级搜索项均应根据角色显示')
|
||||
|
||||
assert.strictEqual(component.computed.showAllSearchItems.call({
|
||||
roles: ['common']
|
||||
}), false, '客户经理只应展示客户信息搜索项')
|
||||
|
||||
assert.strictEqual(component.computed.showAllSearchItems.call({
|
||||
roles: ['headAdmin']
|
||||
}), true, '其他角色应展示全部搜索项')
|
||||
|
||||
let refreshCount = 0
|
||||
component.activated.call({
|
||||
@@ -49,4 +82,16 @@ component.activated.call({
|
||||
|
||||
assert.strictEqual(refreshCount, 1, '流程列表页激活时应调用一次 getList')
|
||||
|
||||
assert.strictEqual(component.methods.canEdit.call({
|
||||
currentCreateBy: '张三-8920001'
|
||||
}, {
|
||||
createBy: '张三-8920001'
|
||||
}), true, '创建者应允许编辑')
|
||||
|
||||
assert.strictEqual(component.methods.canEdit.call({
|
||||
currentCreateBy: '张三-8920001'
|
||||
}, {
|
||||
createBy: '李四-8920002'
|
||||
}), false, '非创建者不应允许编辑')
|
||||
|
||||
console.log('workflow-index-refresh test passed')
|
||||
|
||||
16
sql/20260814-production.sql
Normal file
16
sql/20260814-production.sql
Normal file
@@ -0,0 +1,16 @@
|
||||
-- 2026-08-14 production database changes
|
||||
|
||||
-- Personal model output fields
|
||||
ALTER TABLE `model_retail_output_fields`
|
||||
ADD COLUMN `black_cust` varchar(100) DEFAULT NULL COMMENT '黑名单客户' AFTER `grey_cust`,
|
||||
ADD COLUMN `branch_bp` varchar(100) DEFAULT NULL COMMENT '行社下调BP' AFTER `reference_rate`;
|
||||
|
||||
-- Corporate model output fields
|
||||
ALTER TABLE `model_corp_output_fields`
|
||||
ADD COLUMN `black_cust` varchar(100) DEFAULT NULL COMMENT '黑名单客户' AFTER `grey_cust`,
|
||||
ADD COLUMN `branch_bp` varchar(100) DEFAULT NULL COMMENT '行社下调BP' AFTER `reference_rate`;
|
||||
|
||||
-- Workflow collateral source fields
|
||||
ALTER TABLE `loan_pricing_workflow`
|
||||
ADD COLUMN `collateral_nature` varchar(50) DEFAULT NULL COMMENT '抵押物性质' AFTER `loan_loop`,
|
||||
ADD COLUMN `is_nine_area` varchar(10) DEFAULT NULL COMMENT '是否属于九地: 是/否' AFTER `collateral_nature`;
|
||||
5
sql/20260824-corporate-workflow-optimization.sql
Normal file
5
sql/20260824-corporate-workflow-optimization.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- 2026-08-24 corporate model output overdue BP fields
|
||||
ALTER TABLE `model_corp_output_fields`
|
||||
ADD COLUMN `bp_prin_overdue` varchar(100) DEFAULT NULL COMMENT 'BP_本金逾期' AFTER `prin_overdue`,
|
||||
ADD COLUMN `bp_iinterest_overdue` varchar(100) DEFAULT NULL COMMENT 'BP_利息逾期' AFTER `interest_overdue`,
|
||||
ADD COLUMN `bp_card_overdue` varchar(100) DEFAULT NULL COMMENT 'BP_信用卡逾期' AFTER `card_overdue`;
|
||||
5
sql/20260824-personal-workflow-optimization.sql
Normal file
5
sql/20260824-personal-workflow-optimization.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- 2026-08-24 personal model output overdue BP fields
|
||||
ALTER TABLE `model_retail_output_fields`
|
||||
ADD COLUMN `bp_prin_overdue` varchar(100) DEFAULT NULL COMMENT 'BP_本金逾期' AFTER `prin_overdue`,
|
||||
ADD COLUMN `bp_iinterest_overdue` varchar(100) DEFAULT NULL COMMENT 'BP_利息逾期' AFTER `interest_overdue`,
|
||||
ADD COLUMN `bp_card_overdue` varchar(100) DEFAULT NULL COMMENT 'BP_信用卡逾期' AFTER `card_overdue`;
|
||||
5
sql/add_workflow_dept_id_20260518.sql
Normal file
5
sql/add_workflow_dept_id_20260518.sql
Normal file
@@ -0,0 +1,5 @@
|
||||
-- 为流程表增加创建人部门ID,用于支行管理员流程列表数据权限
|
||||
ALTER TABLE `loan_pricing_workflow`
|
||||
ADD COLUMN `dept_id` bigint(20) DEFAULT NULL COMMENT '创建人部门ID' AFTER `is_inclusive_finance`;
|
||||
|
||||
CREATE INDEX `idx_dept_id` ON `loan_pricing_workflow` (`dept_id`);
|
||||
286
sql/insert_sys_user_role_892_20260519.sql
Normal file
286
sql/insert_sys_user_role_892_20260519.sql
Normal file
@@ -0,0 +1,286 @@
|
||||
-- 根据 /Users/wkc/Downloads/892.xlsx Sheet1 生成 sys_user_role 角色关系
|
||||
-- 生成日期: 2026-05-20
|
||||
-- Excel 数据行数: 257
|
||||
-- 角色分布: 管理员 4, 支行管理员 37, 客户经理 213
|
||||
-- 重复柜员号按支行管理员处理,不再插入客户经理角色。
|
||||
-- user_id 取值规则: Excel 柜员号 -> sys_user.user_name -> sys_user.user_id。
|
||||
-- 本脚本不创建临时表,三条 INSERT 语句分别插入三种角色。
|
||||
-- 执行前需确保 sys_user 已按柜员号写入 user_name,sys_role 已存在对应角色名。
|
||||
|
||||
-- 插入管理员角色关系,共 4 行
|
||||
INSERT IGNORE INTO sys_user_role (user_id, role_id)
|
||||
SELECT DISTINCT u.user_id, r.role_id
|
||||
FROM (
|
||||
SELECT '8925071' AS teller_no -- Excel 第 255 行,吴津江,机构 892000
|
||||
UNION ALL SELECT '8923774' -- Excel 第 256 行,陈夏超,机构 892000
|
||||
UNION ALL SELECT '8925041' -- Excel 第 257 行,鲁琳洁,机构 892000
|
||||
UNION ALL SELECT '8923490' -- Excel 第 258 行,王赛金,机构 892000
|
||||
) src
|
||||
JOIN sys_user u ON u.user_name = src.teller_no AND u.del_flag = '0'
|
||||
JOIN sys_role r ON r.role_name = '管理员' AND r.status = '0' AND r.del_flag = '0';
|
||||
|
||||
-- 插入支行管理员角色关系,共 37 行
|
||||
INSERT IGNORE INTO sys_user_role (user_id, role_id)
|
||||
SELECT DISTINCT u.user_id, r.role_id
|
||||
FROM (
|
||||
SELECT '8922653' AS teller_no -- Excel 第 2 行,赵健君,机构 892160
|
||||
UNION ALL SELECT '8922319' -- Excel 第 3 行,俞宏,机构 892200
|
||||
UNION ALL SELECT '8923428' -- Excel 第 4 行,胡海松,机构 892060
|
||||
UNION ALL SELECT '8922185' -- Excel 第 5 行,朱江龙,机构 892110
|
||||
UNION ALL SELECT '8923393' -- Excel 第 6 行,常斌斌,机构 892020
|
||||
UNION ALL SELECT '8922645' -- Excel 第 7 行,陈勇毅,机构 892100
|
||||
UNION ALL SELECT '8923511' -- Excel 第 8 行,胡卓奇,机构 892170
|
||||
UNION ALL SELECT '8923706' -- Excel 第 9 行,夏俊杰,机构 892190
|
||||
UNION ALL SELECT '8927547' -- Excel 第 10 行,鲁文倬,机构 892130
|
||||
UNION ALL SELECT '8923370' -- Excel 第 11 行,杨力,机构 892050
|
||||
UNION ALL SELECT '8923612' -- Excel 第 12 行,沈萍,机构 892220
|
||||
UNION ALL SELECT '8923105' -- Excel 第 13 行,王忠,机构 892070
|
||||
UNION ALL SELECT '8923326' -- Excel 第 14 行,陈燕,机构 892140
|
||||
UNION ALL SELECT '8922341' -- Excel 第 15 行,王丽娜,机构 892210
|
||||
UNION ALL SELECT '8922250' -- Excel 第 16 行,孙银峰,机构 892030
|
||||
UNION ALL SELECT '8923475' -- Excel 第 17 行,张绒,机构 892010
|
||||
UNION ALL SELECT '8923249' -- Excel 第 18 行,朱珊珊,机构 892090
|
||||
UNION ALL SELECT '8923244' -- Excel 第 19 行,汤琴华,机构 892080
|
||||
UNION ALL SELECT '8923215' -- Excel 第 113 行,徐国栋,机构 892100
|
||||
UNION ALL SELECT '8923351' -- Excel 第 114 行,刘春燕,机构 892050
|
||||
UNION ALL SELECT '8923384' -- Excel 第 115 行,马钦,机构 892090
|
||||
UNION ALL SELECT '8922686' -- Excel 第 116 行,许训华,机构 892020
|
||||
UNION ALL SELECT '8922557' -- Excel 第 117 行,裘朝山,机构 892220
|
||||
UNION ALL SELECT '8923504' -- Excel 第 118 行,陈俊杰,机构 892170
|
||||
UNION ALL SELECT '8922344' -- Excel 第 119 行,徐浩锋,机构 892110
|
||||
UNION ALL SELECT '8923510' -- Excel 第 120 行,陆波,机构 892130
|
||||
UNION ALL SELECT '8922667' -- Excel 第 121 行,徐华源,机构 892080
|
||||
UNION ALL SELECT '8923190' -- Excel 第 122 行,倪舟军,机构 892160
|
||||
UNION ALL SELECT '8922309' -- Excel 第 123 行,刘炯,机构 892210
|
||||
UNION ALL SELECT '8922630' -- Excel 第 124 行,李文江,机构 892200
|
||||
UNION ALL SELECT '8922257' -- Excel 第 125 行,傅泽民,机构 892080
|
||||
UNION ALL SELECT '8928849' -- Excel 第 126 行,赵红江,机构 892030
|
||||
UNION ALL SELECT '8923354' -- Excel 第 127 行,马斯,机构 892060
|
||||
UNION ALL SELECT '8923102' -- Excel 第 128 行,徐峰,机构 892010
|
||||
UNION ALL SELECT '8922534' -- Excel 第 129 行,谢铭杰,机构 892170
|
||||
UNION ALL SELECT '8923229' -- Excel 第 130 行,杜铜方,机构 892190
|
||||
UNION ALL SELECT '8921245' -- Excel 第 131 行,徐红玲,机构 892140
|
||||
) src
|
||||
JOIN sys_user u ON u.user_name = src.teller_no AND u.del_flag = '0'
|
||||
JOIN sys_role r ON r.role_name = '支行管理员' AND r.status = '0' AND r.del_flag = '0';
|
||||
|
||||
-- 插入客户经理角色关系,共 213 行
|
||||
INSERT IGNORE INTO sys_user_role (user_id, role_id)
|
||||
SELECT DISTINCT u.user_id, r.role_id
|
||||
FROM (
|
||||
SELECT '8923387' AS teller_no -- Excel 第 20 行,倪嫣倩,机构 892020
|
||||
UNION ALL SELECT '8923976' -- Excel 第 21 行,俞丽佳,机构 892020
|
||||
UNION ALL SELECT '8922535' -- Excel 第 22 行,金军勇,机构 892142
|
||||
UNION ALL SELECT '8923566' -- Excel 第 23 行,范水月,机构 892213
|
||||
UNION ALL SELECT '8923179' -- Excel 第 24 行,章恩耀,机构 892014
|
||||
UNION ALL SELECT '8923388' -- Excel 第 25 行,陈立波,机构 892080
|
||||
UNION ALL SELECT '8928990' -- Excel 第 27 行,郑永锋,机构 892200
|
||||
UNION ALL SELECT '8925312' -- Excel 第 29 行,俞一凡,机构 892020
|
||||
UNION ALL SELECT '8922316' -- Excel 第 30 行,陈建囡,机构 892080
|
||||
UNION ALL SELECT '8923253' -- Excel 第 31 行,潘烨,机构 892214
|
||||
UNION ALL SELECT '8923188' -- Excel 第 32 行,卢丰,机构 892140
|
||||
UNION ALL SELECT '8923872' -- Excel 第 33 行,戴魏杰,机构 892050
|
||||
UNION ALL SELECT '8923478' -- Excel 第 34 行,陈泽宇,机构 892102
|
||||
UNION ALL SELECT '8923315' -- Excel 第 35 行,赵政,机构 892010
|
||||
UNION ALL SELECT '8925714' -- Excel 第 36 行,陈承,机构 892030
|
||||
UNION ALL SELECT '8923752' -- Excel 第 37 行,金佳裕,机构 892160
|
||||
UNION ALL SELECT '8922227' -- Excel 第 38 行,谢冬青,机构 892050
|
||||
UNION ALL SELECT '8925040' -- Excel 第 39 行,谢顺达,机构 892193
|
||||
UNION ALL SELECT '8923242' -- Excel 第 40 行,夏德鑫,机构 892030
|
||||
UNION ALL SELECT '8922392' -- Excel 第 41 行,阮寺均,机构 892100
|
||||
UNION ALL SELECT '8923327' -- Excel 第 42 行,屠超众,机构 892110
|
||||
UNION ALL SELECT '8923223' -- Excel 第 43 行,沈佳敏,机构 892022
|
||||
UNION ALL SELECT '8923309' -- Excel 第 44 行,徐岳锋,机构 892070
|
||||
UNION ALL SELECT '8923561' -- Excel 第 45 行,赵斌,机构 892220
|
||||
UNION ALL SELECT '8923421' -- Excel 第 46 行,王燕薇,机构 892210
|
||||
UNION ALL SELECT '8923324' -- Excel 第 47 行,孙绍平,机构 892170
|
||||
UNION ALL SELECT '8923766' -- Excel 第 48 行,孙章丽,机构 892021
|
||||
UNION ALL SELECT '8923429' -- Excel 第 49 行,袁叶晨,机构 892192
|
||||
UNION ALL SELECT '8923262' -- Excel 第 50 行,黄滨,机构 892111
|
||||
UNION ALL SELECT '8923838' -- Excel 第 51 行,潘晋洋,机构 892011
|
||||
UNION ALL SELECT '8923317' -- Excel 第 52 行,朱鋆,机构 892051
|
||||
UNION ALL SELECT '8923271' -- Excel 第 53 行,陈军,机构 892060
|
||||
UNION ALL SELECT '8923981' -- Excel 第 54 行,王鋆盈,机构 892200
|
||||
UNION ALL SELECT '8922473' -- Excel 第 55 行,徐伟达,机构 892170
|
||||
UNION ALL SELECT '8922323' -- Excel 第 56 行,赵杰,机构 892141
|
||||
UNION ALL SELECT '8923799' -- Excel 第 57 行,赵焱,机构 892090
|
||||
UNION ALL SELECT '8922399' -- Excel 第 58 行,陈烈,机构 892010
|
||||
UNION ALL SELECT '8923379' -- Excel 第 59 行,金鑫,机构 892191
|
||||
UNION ALL SELECT '8923101' -- Excel 第 60 行,娄瑛,机构 892030
|
||||
UNION ALL SELECT '8923773' -- Excel 第 61 行,叶鑫,机构 892090
|
||||
UNION ALL SELECT '8922625' -- Excel 第 62 行,李银钢,机构 892140
|
||||
UNION ALL SELECT '8923871' -- Excel 第 63 行,高飞雍,机构 892082
|
||||
UNION ALL SELECT '8925534' -- Excel 第 64 行,龚泽烨,机构 892010
|
||||
UNION ALL SELECT '8925220' -- Excel 第 65 行,陈方正,机构 892166
|
||||
UNION ALL SELECT '8923820' -- Excel 第 67 行,徐金燕,机构 892170
|
||||
UNION ALL SELECT '8923104' -- Excel 第 68 行,阮钟威,机构 892160
|
||||
UNION ALL SELECT '8923278' -- Excel 第 69 行,楼建灿,机构 892200
|
||||
UNION ALL SELECT '8923435' -- Excel 第 70 行,王金锦,机构 892035
|
||||
UNION ALL SELECT '8923501' -- Excel 第 71 行,范少材,机构 892041
|
||||
UNION ALL SELECT '8923848' -- Excel 第 72 行,沈燕燕,机构 892220
|
||||
UNION ALL SELECT '8923231' -- Excel 第 73 行,张何,机构 892161
|
||||
UNION ALL SELECT '8923368' -- Excel 第 74 行,王钗钗,机构 892133
|
||||
UNION ALL SELECT '8923816' -- Excel 第 75 行,陈姝莹,机构 892101
|
||||
UNION ALL SELECT '8923209' -- Excel 第 76 行,徐敏敏,机构 892050
|
||||
UNION ALL SELECT '8923549' -- Excel 第 77 行,陆颖琳,机构 892130
|
||||
UNION ALL SELECT '8923118' -- Excel 第 78 行,邵琴,机构 892172
|
||||
UNION ALL SELECT '8923914' -- Excel 第 79 行,陆佳英,机构 892226
|
||||
UNION ALL SELECT '8921232' -- Excel 第 80 行,陈曙光,机构 892010
|
||||
UNION ALL SELECT '8923747' -- Excel 第 81 行,王祝芳,机构 892060
|
||||
UNION ALL SELECT '8923254' -- Excel 第 82 行,章霄,机构 892030
|
||||
UNION ALL SELECT '8923434' -- Excel 第 83 行,姚明权,机构 892200
|
||||
UNION ALL SELECT '8923502' -- Excel 第 84 行,邵钰凤,机构 892021
|
||||
UNION ALL SELECT '8923390' -- Excel 第 85 行,谢羽青,机构 892170
|
||||
UNION ALL SELECT '8923550' -- Excel 第 86 行,俞李鲲,机构 892211
|
||||
UNION ALL SELECT '8922425' -- Excel 第 87 行,罗国杰,机构 892100
|
||||
UNION ALL SELECT '8923540' -- Excel 第 88 行,王帅,机构 892190
|
||||
UNION ALL SELECT '8923224' -- Excel 第 89 行,应振宇,机构 892020
|
||||
UNION ALL SELECT '8923894' -- Excel 第 90 行,任鹏飞,机构 892021
|
||||
UNION ALL SELECT '8923222' -- Excel 第 91 行,朱烨,机构 892010
|
||||
UNION ALL SELECT '8923357' -- Excel 第 92 行,陈滟,机构 892110
|
||||
UNION ALL SELECT '8923812' -- Excel 第 93 行,周虹,机构 892083
|
||||
UNION ALL SELECT '8922167' -- Excel 第 94 行,魏铁柱,机构 892182
|
||||
UNION ALL SELECT '8922248' -- Excel 第 95 行,吴智光,机构 892070
|
||||
UNION ALL SELECT '8922247' -- Excel 第 96 行,吴俊勋,机构 892060
|
||||
UNION ALL SELECT '8923383' -- Excel 第 97 行,王宽,机构 892140
|
||||
UNION ALL SELECT '8923350' -- Excel 第 98 行,徐骏妮,机构 892200
|
||||
UNION ALL SELECT '8923352' -- Excel 第 99 行,韩镭,机构 892146
|
||||
UNION ALL SELECT '8923861' -- Excel 第 100 行,严禹彬,机构 892160
|
||||
UNION ALL SELECT '8922743' -- Excel 第 101 行,刘建军,机构 892130
|
||||
UNION ALL SELECT '8923963' -- Excel 第 102 行,王一,机构 892030
|
||||
UNION ALL SELECT '8923922' -- Excel 第 103 行,徐鹤立,机构 892113
|
||||
UNION ALL SELECT '8925176' -- Excel 第 104 行,徐家栋,机构 892210
|
||||
UNION ALL SELECT '8922258' -- Excel 第 105 行,朱蓉蓉,机构 892161
|
||||
UNION ALL SELECT '8923247' -- Excel 第 106 行,王陈炯,机构 892031
|
||||
UNION ALL SELECT '8923117' -- Excel 第 107 行,黄辉,机构 892223
|
||||
UNION ALL SELECT '8923929' -- Excel 第 108 行,俞佳娜,机构 892090
|
||||
UNION ALL SELECT '8925461' -- Excel 第 109 行,厉佳贝,机构 892224
|
||||
UNION ALL SELECT '8923751' -- Excel 第 110 行,劳泽涛,机构 892132
|
||||
UNION ALL SELECT '8923458' -- Excel 第 111 行,徐泽钏,机构 892145
|
||||
UNION ALL SELECT '8923415' -- Excel 第 112 行,吴璟琳,机构 892200
|
||||
UNION ALL SELECT '8922687' -- Excel 第 132 行,张蓉,机构 892133
|
||||
UNION ALL SELECT '8923320' -- Excel 第 133 行,方一锋,机构 892021
|
||||
UNION ALL SELECT '8923046' -- Excel 第 134 行,吴晓萍,机构 892012
|
||||
UNION ALL SELECT '8923363' -- Excel 第 135 行,朱晖,机构 892223
|
||||
UNION ALL SELECT '8925213' -- Excel 第 136 行,何佳文,机构 892014
|
||||
UNION ALL SELECT '8923810' -- Excel 第 137 行,金彬,机构 892083
|
||||
UNION ALL SELECT '8925535' -- Excel 第 138 行,何志琦,机构 892060
|
||||
UNION ALL SELECT '8925532' -- Excel 第 139 行,董栋,机构 892110
|
||||
UNION ALL SELECT '8923256' -- Excel 第 140 行,黄勇,机构 892060
|
||||
UNION ALL SELECT '8925159' -- Excel 第 141 行,吴学舟,机构 892145
|
||||
UNION ALL SELECT '8925462' -- Excel 第 142 行,李佳彬,机构 892146
|
||||
UNION ALL SELECT '8923493' -- Excel 第 143 行,张磊,机构 892060
|
||||
UNION ALL SELECT '8923599' -- Excel 第 144 行,徐江琴,机构 892210
|
||||
UNION ALL SELECT '8925208' -- Excel 第 145 行,潘列当,机构 892110
|
||||
UNION ALL SELECT '8925539' -- Excel 第 146 行,秦佳浩,机构 892100
|
||||
UNION ALL SELECT '8922824' -- Excel 第 147 行,许伟松,机构 892170
|
||||
UNION ALL SELECT '8923477' -- Excel 第 148 行,陈淑清,机构 892213
|
||||
UNION ALL SELECT '8925393' -- Excel 第 149 行,余恒涛,机构 892190
|
||||
UNION ALL SELECT '8923865' -- Excel 第 150 行,金洁琼,机构 892210
|
||||
UNION ALL SELECT '8923920' -- Excel 第 151 行,华盛榆,机构 892183
|
||||
UNION ALL SELECT '8923239' -- Excel 第 152 行,赵宏,机构 892213
|
||||
UNION ALL SELECT '8923369' -- Excel 第 153 行,陈丽君,机构 892050
|
||||
UNION ALL SELECT '8923541' -- Excel 第 154 行,赵珊,机构 892220
|
||||
UNION ALL SELECT '8925419' -- Excel 第 155 行,阮昕岚,机构 892014
|
||||
UNION ALL SELECT '8923119' -- Excel 第 156 行,李莹,机构 892035
|
||||
UNION ALL SELECT '8925596' -- Excel 第 157 行,郑炫奇,机构 892214
|
||||
UNION ALL SELECT '8925544' -- Excel 第 158 行,徐洁汀,机构 892090
|
||||
UNION ALL SELECT '8923723' -- Excel 第 159 行,王未,机构 892200
|
||||
UNION ALL SELECT '8923974' -- Excel 第 160 行,余菲菲,机构 892140
|
||||
UNION ALL SELECT '8923171' -- Excel 第 161 行,陈洲焰,机构 892101
|
||||
UNION ALL SELECT '8922199' -- Excel 第 162 行,章国栋,机构 892223
|
||||
UNION ALL SELECT '8923418' -- Excel 第 163 行,阮俊杰,机构 892030
|
||||
UNION ALL SELECT '8923708' -- Excel 第 164 行,严思培,机构 892050
|
||||
UNION ALL SELECT '8923607' -- Excel 第 165 行,刘明亮,机构 892160
|
||||
UNION ALL SELECT '8923839' -- Excel 第 166 行,董生超,机构 892161
|
||||
UNION ALL SELECT '8921175' -- Excel 第 167 行,俞曙光,机构 892041
|
||||
UNION ALL SELECT '8923308' -- Excel 第 168 行,叶超,机构 892172
|
||||
UNION ALL SELECT '8925564' -- Excel 第 169 行,张镇涛,机构 892083
|
||||
UNION ALL SELECT '8925067' -- Excel 第 170 行,袁江锋,机构 892211
|
||||
UNION ALL SELECT '8923531' -- Excel 第 171 行,夏威,机构 892200
|
||||
UNION ALL SELECT '8923813' -- Excel 第 172 行,赵佳伟,机构 892080
|
||||
UNION ALL SELECT '8923371' -- Excel 第 173 行,朱益浪,机构 892166
|
||||
UNION ALL SELECT '8923979' -- Excel 第 174 行,徐嘉婕,机构 892021
|
||||
UNION ALL SELECT '8925086' -- Excel 第 175 行,陈坚,机构 892051
|
||||
UNION ALL SELECT '8922166' -- Excel 第 176 行,任静钰,机构 892210
|
||||
UNION ALL SELECT '8923585' -- Excel 第 177 行,金鹏飞,机构 892200
|
||||
UNION ALL SELECT '8923548' -- Excel 第 178 行,阮锦薇,机构 892161
|
||||
UNION ALL SELECT '8925177' -- Excel 第 179 行,严翔,机构 892166
|
||||
UNION ALL SELECT '8925420' -- Excel 第 180 行,郑清枫,机构 892022
|
||||
UNION ALL SELECT '8925217' -- Excel 第 181 行,屠林洁,机构 892030
|
||||
UNION ALL SELECT '8922462' -- Excel 第 182 行,何文达,机构 892226
|
||||
UNION ALL SELECT '8923551' -- Excel 第 183 行,陈文波,机构 892132
|
||||
UNION ALL SELECT '8923587' -- Excel 第 184 行,徐霜霜,机构 892211
|
||||
UNION ALL SELECT '8923823' -- Excel 第 185 行,董佳杨,机构 892022
|
||||
UNION ALL SELECT '8923377' -- Excel 第 186 行,魏子涵,机构 892200
|
||||
UNION ALL SELECT '8922192' -- Excel 第 187 行,金智煌,机构 892223
|
||||
UNION ALL SELECT '8923654' -- Excel 第 188 行,杜圣磊,机构 892010
|
||||
UNION ALL SELECT '8925152' -- Excel 第 189 行,阮益权,机构 892192
|
||||
UNION ALL SELECT '8923580' -- Excel 第 190 行,金陈坷,机构 892170
|
||||
UNION ALL SELECT '8925311' -- Excel 第 191 行,章天豪,机构 892100
|
||||
UNION ALL SELECT '8925157' -- Excel 第 192 行,潘彬彬,机构 892080
|
||||
UNION ALL SELECT '8925068' -- Excel 第 193 行,沈天阳,机构 892161
|
||||
UNION ALL SELECT '8923689' -- Excel 第 194 行,傅俊杰,机构 892224
|
||||
UNION ALL SELECT '8923874' -- Excel 第 195 行,连郡,机构 892142
|
||||
UNION ALL SELECT '8923604' -- Excel 第 196 行,顾涵,机构 892142
|
||||
UNION ALL SELECT '8923899' -- Excel 第 197 行,杜燕燕,机构 892160
|
||||
UNION ALL SELECT '8923986' -- Excel 第 198 行,阮婷,机构 892090
|
||||
UNION ALL SELECT '8922523' -- Excel 第 199 行,金强,机构 892010
|
||||
UNION ALL SELECT '8925630' -- Excel 第 200 行,何一东,机构 892070
|
||||
UNION ALL SELECT '8925385' -- Excel 第 201 行,陈浩,机构 892160
|
||||
UNION ALL SELECT '8923579' -- Excel 第 202 行,吴滢榆,机构 892102
|
||||
UNION ALL SELECT '8923629' -- Excel 第 203 行,王洁,机构 892200
|
||||
UNION ALL SELECT '8925478' -- Excel 第 204 行,叶铃君,机构 892140
|
||||
UNION ALL SELECT '8923537' -- Excel 第 205 行,章吉丽,机构 892031
|
||||
UNION ALL SELECT '8925215' -- Excel 第 206 行,姚橙枫,机构 892090
|
||||
UNION ALL SELECT '8925038' -- Excel 第 207 行,曹铠宇,机构 892011
|
||||
UNION ALL SELECT '8923219' -- Excel 第 208 行,许蕾,机构 892011
|
||||
UNION ALL SELECT '8923836' -- Excel 第 209 行,徐宏来,机构 892050
|
||||
UNION ALL SELECT '8925128' -- Excel 第 210 行,陈梦宇,机构 892070
|
||||
UNION ALL SELECT '8923902' -- Excel 第 211 行,徐玉玮,机构 892030
|
||||
UNION ALL SELECT '8923258' -- Excel 第 212 行,严金锋,机构 892132
|
||||
UNION ALL SELECT '8923124' -- Excel 第 213 行,徐雪荣,机构 892191
|
||||
UNION ALL SELECT '8922187' -- Excel 第 214 行,沈颖冲,机构 892100
|
||||
UNION ALL SELECT '8925604' -- Excel 第 215 行,陈一鸣,机构 892166
|
||||
UNION ALL SELECT '8923627' -- Excel 第 216 行,徐佳斌,机构 892193
|
||||
UNION ALL SELECT '8923703' -- Excel 第 217 行,王河杰,机构 892080
|
||||
UNION ALL SELECT '8923211' -- Excel 第 218 行,龚世炎,机构 892012
|
||||
UNION ALL SELECT '8923647' -- Excel 第 219 行,沈冰妮,机构 892145
|
||||
UNION ALL SELECT '8925392' -- Excel 第 220 行,任琳,机构 892020
|
||||
UNION ALL SELECT '8923260' -- Excel 第 221 行,郑芦康,机构 892050
|
||||
UNION ALL SELECT '8925206' -- Excel 第 222 行,刘威,机构 892020
|
||||
UNION ALL SELECT '8925212' -- Excel 第 223 行,俞洁,机构 892101
|
||||
UNION ALL SELECT '8925427' -- Excel 第 224 行,阮天宇,机构 892020
|
||||
UNION ALL SELECT '8925028' -- Excel 第 225 行,李益斐,机构 892133
|
||||
UNION ALL SELECT '8923567' -- Excel 第 226 行,俞琴,机构 892030
|
||||
UNION ALL SELECT '8923293' -- Excel 第 227 行,马岚,机构 892080
|
||||
UNION ALL SELECT '8923875' -- Excel 第 228 行,吕姗珊,机构 892020
|
||||
UNION ALL SELECT '8923509' -- Excel 第 229 行,杜俊杭,机构 892214
|
||||
UNION ALL SELECT '8923720' -- Excel 第 230 行,陈云,机构 892031
|
||||
UNION ALL SELECT '8923323' -- Excel 第 231 行,王微,机构 892200
|
||||
UNION ALL SELECT '8925227' -- Excel 第 232 行,朱恒杰,机构 892171
|
||||
UNION ALL SELECT '8925423' -- Excel 第 233 行,章佳妍,机构 892070
|
||||
UNION ALL SELECT '8925221' -- Excel 第 234 行,柳奕帆,机构 892140
|
||||
UNION ALL SELECT '8923500' -- Excel 第 235 行,王锦瑜,机构 892200
|
||||
UNION ALL SELECT '8923971' -- Excel 第 236 行,任依婷,机构 892182
|
||||
UNION ALL SELECT '8923302' -- Excel 第 237 行,朱杰权,机构 892160
|
||||
UNION ALL SELECT '8923701' -- Excel 第 238 行,沈炜汉,机构 892130
|
||||
UNION ALL SELECT '8923292' -- Excel 第 239 行,阮严波,机构 892082
|
||||
UNION ALL SELECT '8925119' -- Excel 第 240 行,吕丹旋,机构 892141
|
||||
UNION ALL SELECT '8925125' -- Excel 第 241 行,范梦煖,机构 892010
|
||||
UNION ALL SELECT '8925477' -- Excel 第 242 行,徐天柱,机构 892113
|
||||
UNION ALL SELECT '8923515' -- Excel 第 243 行,高齐天,机构 892141
|
||||
UNION ALL SELECT '8923307' -- Excel 第 244 行,孙威,机构 892170
|
||||
UNION ALL SELECT '8923757' -- Excel 第 245 行,徐煊楷,机构 892183
|
||||
UNION ALL SELECT '8925396' -- Excel 第 246 行,陈益未,机构 892110
|
||||
UNION ALL SELECT '8923316' -- Excel 第 247 行,徐小雪,机构 892190
|
||||
UNION ALL SELECT '8925605' -- Excel 第 248 行,陈礼炀,机构 892220
|
||||
UNION ALL SELECT '8923459' -- Excel 第 249 行,章丽,机构 892224
|
||||
UNION ALL SELECT '8923616' -- Excel 第 250 行,宋灵蓉,机构 892111
|
||||
UNION ALL SELECT '8923389' -- Excel 第 251 行,张丹凤,机构 892200
|
||||
UNION ALL SELECT '8923870' -- Excel 第 252 行,曹坚,机构 892130
|
||||
UNION ALL SELECT '8923214' -- Excel 第 253 行,冯佳,机构 892112
|
||||
UNION ALL SELECT '8923759' -- Excel 第 254 行,吕琦,机构 892182
|
||||
) src
|
||||
JOIN sys_user u ON u.user_name = src.teller_no AND u.del_flag = '0'
|
||||
JOIN sys_role r ON r.role_name = '客户经理' AND r.status = '0' AND r.del_flag = '0';
|
||||
@@ -766,6 +766,7 @@ CREATE TABLE `loan_pricing_workflow` (
|
||||
`business_type` varchar(20) DEFAULT NULL COMMENT '业务种类',
|
||||
`loan_rate_history` varchar(100) DEFAULT NULL COMMENT '历史贷款利率',
|
||||
`coupon_rate` varchar(100) DEFAULT NULL COMMENT '存单票面利率',
|
||||
`res_cover` varchar(10) DEFAULT NULL COMMENT '余值覆盖: 0/1',
|
||||
`biz_proof` varchar(10) DEFAULT NULL COMMENT '是否有经营佐证: true/false',
|
||||
`loan_loop` varchar(10) DEFAULT NULL COMMENT '循环功能: true/false(贷款合同是否开通循环功能)',
|
||||
`coll_type` varchar(20) DEFAULT NULL COMMENT '抵质押类型: 一类/二类/三类/四类',
|
||||
@@ -776,6 +777,7 @@ CREATE TABLE `loan_pricing_workflow` (
|
||||
`id_type` varchar(50) DEFAULT NULL COMMENT '证件类型',
|
||||
`id_num` varchar(100) DEFAULT NULL COMMENT '证件号码',
|
||||
`is_inclusive_finance` varchar(10) DEFAULT NULL COMMENT '是否普惠小微借款人: true/false',
|
||||
`dept_id` bigint(20) DEFAULT NULL COMMENT '创建人部门ID',
|
||||
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
|
||||
@@ -783,6 +785,7 @@ CREATE TABLE `loan_pricing_workflow` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_serial_num` (`serial_num`),
|
||||
KEY `idx_org_code` (`org_code`),
|
||||
KEY `idx_dept_id` (`dept_id`),
|
||||
KEY `idx_create_by` (`create_by`),
|
||||
KEY `idx_cust_name` (`cust_name`),
|
||||
KEY `idx_update_time` (`update_time`)
|
||||
|
||||
@@ -348,6 +348,7 @@ CREATE TABLE `loan_pricing_workflow` (
|
||||
`business_type` varchar(20) DEFAULT NULL COMMENT '业务种类',
|
||||
`loan_rate_history` varchar(100) DEFAULT NULL COMMENT '历史贷款利率',
|
||||
`coupon_rate` varchar(100) DEFAULT NULL COMMENT '存单票面利率',
|
||||
`res_cover` varchar(10) DEFAULT NULL COMMENT '余值覆盖: 0/1',
|
||||
`biz_proof` varchar(10) DEFAULT NULL COMMENT '是否有经营佐证: true/false',
|
||||
`loan_loop` varchar(10) DEFAULT NULL COMMENT '循环功能: true/false(贷款合同是否开通循环功能)',
|
||||
`coll_type` varchar(20) DEFAULT NULL COMMENT '抵质押类型: 一类/二类/三类/四类',
|
||||
@@ -358,6 +359,7 @@ CREATE TABLE `loan_pricing_workflow` (
|
||||
`id_type` varchar(50) DEFAULT NULL COMMENT '证件类型',
|
||||
`id_num` varchar(100) DEFAULT NULL COMMENT '证件号码',
|
||||
`is_inclusive_finance` varchar(10) DEFAULT NULL COMMENT '是否普惠小微借款人: true/false',
|
||||
`dept_id` bigint(20) DEFAULT NULL COMMENT '创建人部门ID',
|
||||
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
|
||||
@@ -365,6 +367,7 @@ CREATE TABLE `loan_pricing_workflow` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_serial_num` (`serial_num`),
|
||||
KEY `idx_org_code` (`org_code`),
|
||||
KEY `idx_dept_id` (`dept_id`),
|
||||
KEY `idx_create_by` (`create_by`),
|
||||
KEY `idx_cust_name` (`cust_name`),
|
||||
KEY `idx_update_time` (`update_time`)
|
||||
|
||||
@@ -29,6 +29,7 @@ CREATE TABLE `loan_pricing_workflow` (
|
||||
`business_type` varchar(20) DEFAULT NULL COMMENT '业务种类',
|
||||
`loan_rate_history` varchar(100) DEFAULT NULL COMMENT '历史贷款利率',
|
||||
`coupon_rate` varchar(100) DEFAULT NULL COMMENT '存单票面利率',
|
||||
`res_cover` varchar(10) DEFAULT NULL COMMENT '余值覆盖: 0/1',
|
||||
`biz_proof` varchar(10) DEFAULT NULL COMMENT '是否有经营佐证: true/false',
|
||||
`loan_loop` varchar(10) DEFAULT NULL COMMENT '循环功能: true/false(贷款合同是否开通循环功能)',
|
||||
`coll_type` varchar(20) DEFAULT NULL COMMENT '抵质押类型: 一类/二类/三类/四类',
|
||||
@@ -39,6 +40,7 @@ CREATE TABLE `loan_pricing_workflow` (
|
||||
`id_type` varchar(50) DEFAULT NULL COMMENT '证件类型',
|
||||
`id_num` varchar(100) DEFAULT NULL COMMENT '证件号码',
|
||||
`is_inclusive_finance` varchar(10) DEFAULT NULL COMMENT '是否普惠小微借款人: true/false',
|
||||
`dept_id` bigint(20) DEFAULT NULL COMMENT '创建人部门ID',
|
||||
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
|
||||
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
|
||||
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
|
||||
@@ -46,6 +48,7 @@ CREATE TABLE `loan_pricing_workflow` (
|
||||
PRIMARY KEY (`id`),
|
||||
UNIQUE KEY `uk_serial_num` (`serial_num`),
|
||||
KEY `idx_org_code` (`org_code`),
|
||||
KEY `idx_dept_id` (`dept_id`),
|
||||
KEY `idx_create_by` (`create_by`),
|
||||
KEY `idx_cust_name` (`cust_name`),
|
||||
KEY `idx_update_time` (`update_time`)
|
||||
|
||||
Reference in New Issue
Block a user