diff --git a/.DS_Store b/.DS_Store index bccf925..43b5d80 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/.playwright-cli/page-2026-04-09T09-31-22-009Z.yml b/.playwright-cli/page-2026-04-09T09-31-22-009Z.yml new file mode 100644 index 0000000..2db2870 --- /dev/null +++ b/.playwright-cli/page-2026-04-09T09-31-22-009Z.yml @@ -0,0 +1,6 @@ +- generic [ref=e2]: + - heading "Example Domain" [level=1] [ref=e3] + - paragraph [ref=e4]: This domain is for use in documentation examples without needing permission. Avoid use in operations. + - paragraph [ref=e5]: + - link "Learn more" [ref=e6] [cursor=pointer]: + - /url: https://iana.org/domains/example \ No newline at end of file diff --git a/.playwright-cli/page-2026-04-09T09-39-00-226Z.yml b/.playwright-cli/page-2026-04-09T09-39-00-226Z.yml new file mode 100644 index 0000000..2db2870 --- /dev/null +++ b/.playwright-cli/page-2026-04-09T09-39-00-226Z.yml @@ -0,0 +1,6 @@ +- generic [ref=e2]: + - heading "Example Domain" [level=1] [ref=e3] + - paragraph [ref=e4]: This domain is for use in documentation examples without needing permission. Avoid use in operations. + - paragraph [ref=e5]: + - link "Learn more" [ref=e6] [cursor=pointer]: + - /url: https://iana.org/domains/example \ No newline at end of file diff --git a/.playwright-cli/page-2026-04-10T06-51-19-033Z.yml b/.playwright-cli/page-2026-04-10T06-51-19-033Z.yml new file mode 100644 index 0000000..711875f --- /dev/null +++ b/.playwright-cli/page-2026-04-10T06-51-19-033Z.yml @@ -0,0 +1,18 @@ +- generic [ref=e2]: + - generic [ref=e3]: + - generic [ref=e4]: + - heading "上虞利率定价系统" [level=3] [ref=e5] + - generic [ref=e8]: + - textbox "账号" [ref=e9] + - img [ref=e11] + - generic [ref=e15]: + - textbox "密码" [ref=e16] + - img [ref=e18] + - generic [ref=e20] [cursor=pointer]: + - generic [ref=e21]: + - checkbox "记住密码" + - generic [ref=e23]: 记住密码 + - button "登 录" [ref=e26] [cursor=pointer]: + - generic [ref=e27]: 登 录 + - generic [ref=e28]: Copyright © 2018-2026 RuoYi. All Rights Reserved. + - text:  \ No newline at end of file diff --git a/.playwright-cli/page-2026-04-10T06-52-06-995Z.yml b/.playwright-cli/page-2026-04-10T06-52-06-995Z.yml new file mode 100644 index 0000000..0cab900 --- /dev/null +++ b/.playwright-cli/page-2026-04-10T06-52-06-995Z.yml @@ -0,0 +1,22 @@ +- generic [active] [ref=e1]: + - generic [ref=e2]: + - generic [ref=e3]: + - generic [ref=e4]: + - heading "上虞利率定价系统" [level=3] [ref=e5] + - generic [ref=e8]: + - textbox "账号" [ref=e9]: admin + - img [ref=e11] + - generic [ref=e15]: + - textbox "密码" [ref=e16]: "123456" + - img [ref=e18] + - generic [ref=e20] [cursor=pointer]: + - generic [ref=e21]: + - checkbox "记住密码" + - generic [ref=e23]: 记住密码 + - button "登 录" [ref=e26] [cursor=pointer]: + - generic [ref=e27]: 登 录 + - generic [ref=e28]: Copyright © 2018-2026 RuoYi. All Rights Reserved. + - text:  + - alert [ref=e29]: + - generic [ref=e30]:  + - paragraph [ref=e31]: 用户不存在/密码错误 \ No newline at end of file diff --git a/.playwright-cli/page-2026-04-10T06-54-38-568Z.yml b/.playwright-cli/page-2026-04-10T06-54-38-568Z.yml new file mode 100644 index 0000000..711875f --- /dev/null +++ b/.playwright-cli/page-2026-04-10T06-54-38-568Z.yml @@ -0,0 +1,18 @@ +- generic [ref=e2]: + - generic [ref=e3]: + - generic [ref=e4]: + - heading "上虞利率定价系统" [level=3] [ref=e5] + - generic [ref=e8]: + - textbox "账号" [ref=e9] + - img [ref=e11] + - generic [ref=e15]: + - textbox "密码" [ref=e16] + - img [ref=e18] + - generic [ref=e20] [cursor=pointer]: + - generic [ref=e21]: + - checkbox "记住密码" + - generic [ref=e23]: 记住密码 + - button "登 录" [ref=e26] [cursor=pointer]: + - generic [ref=e27]: 登 录 + - generic [ref=e28]: Copyright © 2018-2026 RuoYi. All Rights Reserved. + - text:  \ No newline at end of file diff --git a/.playwright-cli/page-2026-04-10T06-55-43-705Z.yml b/.playwright-cli/page-2026-04-10T06-55-43-705Z.yml new file mode 100644 index 0000000..711875f --- /dev/null +++ b/.playwright-cli/page-2026-04-10T06-55-43-705Z.yml @@ -0,0 +1,18 @@ +- generic [ref=e2]: + - generic [ref=e3]: + - generic [ref=e4]: + - heading "上虞利率定价系统" [level=3] [ref=e5] + - generic [ref=e8]: + - textbox "账号" [ref=e9] + - img [ref=e11] + - generic [ref=e15]: + - textbox "密码" [ref=e16] + - img [ref=e18] + - generic [ref=e20] [cursor=pointer]: + - generic [ref=e21]: + - checkbox "记住密码" + - generic [ref=e23]: 记住密码 + - button "登 录" [ref=e26] [cursor=pointer]: + - generic [ref=e27]: 登 录 + - generic [ref=e28]: Copyright © 2018-2026 RuoYi. All Rights Reserved. + - text:  \ No newline at end of file diff --git a/bin/.DS_Store b/bin/.DS_Store index c597d9d..f0c5f42 100644 Binary files a/bin/.DS_Store and b/bin/.DS_Store differ diff --git a/bin/prod/restart_java.sh b/bin/prod/restart_java.sh index cc4fd9e..d5cd3c1 100755 --- a/bin/prod/restart_java.sh +++ b/bin/prod/restart_java.sh @@ -71,7 +71,16 @@ collect_backend_pids() { fi fi - marker_pids=$(pgrep -f "$BACKEND_MARKER" 2>/dev/null || true) + marker_pids=$(ps -ef | awk -v marker="$BACKEND_MARKER" -v jar="$BACKEND_JAR" ' + index($0, "") == 0 && index($0, marker) > 0 { + for (i = 1; i < NF; i++) { + if ($i == "-jar" && $(i + 1) == jar) { + print $2 + break + } + } + } + ' | xargs 2>/dev/null || true) if [ -n "${marker_pids:-}" ]; then for pid in $marker_pids; do if is_managed_backend_pid "$pid"; then diff --git a/bin/prod/restart_java_test.sh b/bin/prod/restart_java_test.sh index 4e1a119..7ac4fd5 100644 --- a/bin/prod/restart_java_test.sh +++ b/bin/prod/restart_java_test.sh @@ -44,8 +44,8 @@ EOF prepare_script_env() { work_dir="$1" - mkdir -p "$work_dir/env/java/bin" "$work_dir/loan-pricing/backend" "$work_dir/loan-pricing/logs" "$work_dir/loan-pricing/run" - create_fake_java "$work_dir/env/java/bin/java" + mkdir -p "$work_dir/env/jdk/bin" "$work_dir/loan-pricing/backend" "$work_dir/loan-pricing/logs" "$work_dir/loan-pricing/run" + create_fake_java "$work_dir/env/jdk/bin/java" printf 'fake-jar\n' > "$work_dir/loan-pricing/backend/ruoyi-admin.jar" cp "$SCRIPT_UNDER_TEST" "$work_dir/restart_java.sh" perl -0pi -e "s#WEBAPP_ROOT=\"/home/webapp\"#WEBAPP_ROOT=\"$work_dir\"#" "$work_dir/restart_java.sh" @@ -67,8 +67,10 @@ cleanup_work_dir() { } test_script_contract() { - assert_grep 'JAVA_HOME="\$ENV_ROOT/java"' "$SCRIPT_UNDER_TEST" + assert_grep 'JAVA_HOME="\$ENV_ROOT/jdk"' "$SCRIPT_UNDER_TEST" assert_grep '--spring\.profiles\.active=pro' "$SCRIPT_UNDER_TEST" + assert_grep 'ps -ef' "$SCRIPT_UNDER_TEST" + assert_not_grep 'pgrep' "$SCRIPT_UNDER_TEST" assert_not_grep 'mvn' "$SCRIPT_UNDER_TEST" assert_not_grep 'require_root' "$SCRIPT_UNDER_TEST" assert_not_grep '\b(ss|lsof|netstat)\b' "$SCRIPT_UNDER_TEST" diff --git a/bin/restart_java_backend.sh b/bin/restart_java_backend.sh index f45ef14..3bcba2c 100755 --- a/bin/restart_java_backend.sh +++ b/bin/restart_java_backend.sh @@ -83,7 +83,16 @@ collect_pids() { fi fi - marker_pids=$(pgrep -f "$APP_MARKER" 2>/dev/null || true) + marker_pids=$(ps -ef | awk -v marker="$APP_MARKER" -v jar="$JAR_NAME" ' + index($0, "") == 0 && index($0, marker) > 0 { + for (i = 1; i < NF; i++) { + if ($i == "-jar" && $(i + 1) == jar) { + print $2 + break + } + } + } + ' | xargs 2>/dev/null || true) if [ -n "${marker_pids:-}" ]; then for pid in $marker_pids; do if is_managed_backend_pid "$pid"; then @@ -225,7 +234,6 @@ restart_action() { main() { ensure_command mvn ensure_command lsof - ensure_command pgrep ensure_command ps ensure_command tail diff --git a/bin/restart_java_backend_test.sh b/bin/restart_java_backend_test.sh new file mode 100644 index 0000000..eb13eac --- /dev/null +++ b/bin/restart_java_backend_test.sh @@ -0,0 +1,42 @@ +#!/bin/sh + +set -eu + +ROOT_DIR=$(CDPATH= cd -- "$(dirname "$0")/.." && pwd) +SCRIPT_UNDER_TEST="$ROOT_DIR/bin/restart_java_backend.sh" + +fail() { + printf 'FAIL: %s\n' "$1" >&2 + exit 1 +} + +assert_grep() { + pattern="$1" + target="$2" + if ! grep -Eq -- "$pattern" "$target"; then + fail "expected pattern [$pattern] in $target" + fi +} + +assert_not_grep() { + pattern="$1" + target="$2" + if grep -Eq -- "$pattern" "$target"; then + fail "did not expect pattern [$pattern] in $target" + fi +} + +test_script_contract() { + assert_grep 'ps -ef' "$SCRIPT_UNDER_TEST" + assert_not_grep 'pgrep' "$SCRIPT_UNDER_TEST" + assert_grep 'APP_MARKER=' "$SCRIPT_UNDER_TEST" + assert_grep 'status_backend\(\)' "$SCRIPT_UNDER_TEST" +} + +main() { + [ -f "$SCRIPT_UNDER_TEST" ] || fail "script under test not found: $SCRIPT_UNDER_TEST" + test_script_contract + printf 'PASS: restart_java_backend tests\n' +} + +main "$@" diff --git a/doc/2026-04-09-shangyu-retail-input-params-design.md b/doc/2026-04-09-shangyu-retail-input-params-design.md index c92ec67..7ff3fc3 100644 --- a/doc/2026-04-09-shangyu-retail-input-params-design.md +++ b/doc/2026-04-09-shangyu-retail-input-params-design.md @@ -181,7 +181,7 @@ - 下拉选项:`consumer`、`business` - `loanTerm` - 固定年限下拉 - - 选项按 Excel 的 `1/2/3/4/5/6……` 组织 + - 选项固定为 `1/2/3/4/5/6` 同时修正: @@ -307,12 +307,12 @@ - 提交流程后详情页能回显 `loanPurpose`、`loanTerm` - 验证完成后停止本次启动的前后端进程 -## 9. 待确认项 +## 9. 已确认项 -- 当前代码中的 `orgCode` 默认值为 `892000` -- `ModelInvokeDTO` 注释中写的是固定值 `931000` - -本次设计不擅自调整该默认值,保持现有运行逻辑,待业务另行确认后再处理。 +- `orgCode` 统一为 `892000` +- `ModelInvokeDTO` 注释已统一为 `892000` +- 数据库 `loan_pricing_workflow.org_code` 默认值已统一为 `892000` +- 存量 `loan_pricing_workflow.org_code` 数据已通过迁移脚本统一为 `892000` ## 10. 非目标 diff --git a/doc/2026-04-09-shangyu-retail-input-params-frontend-plan.md b/doc/2026-04-09-shangyu-retail-input-params-frontend-plan.md index 541abba..ab0377d 100644 --- a/doc/2026-04-09-shangyu-retail-input-params-frontend-plan.md +++ b/doc/2026-04-09-shangyu-retail-input-params-frontend-plan.md @@ -92,7 +92,7 @@ git commit -m "新增个人测算输入参数前端断言" ```vue - + ``` diff --git a/doc/api/loan-pricing-workflow-api.md b/doc/api/loan-pricing-workflow-api.md index b437778..1cd38c7 100644 --- a/doc/api/loan-pricing-workflow-api.md +++ b/doc/api/loan-pricing-workflow-api.md @@ -32,10 +32,12 @@ | idNum | String | 否 | 证件号码 | | guarType | String | 是 | 担保方式,可选值: 信用/保证/抵押/质押 | | applyAmt | String | 是 | 申请金额,单位: 元 | -| bizProof | String | 否 | 是否有经营佐证,值: true/false | -| loanLoop | String | 否 | 循环功能,值: true/false | -| collType | String | 否 | 抵质押类型,可选值: 一线/一类/二类 | -| collThirdParty | String | 否 | 抵质押物是否三方所有,值: true/false | +| loanPurpose | String | 是 | 贷款用途,可选值: consumer/business | +| loanTerm | String | 是 | 借款期限(年),固定下拉选项按模型文档配置 | +| bizProof | String | 否 | 是否有经营佐证,值: 0/1 | +| loanLoop | String | 否 | 循环功能,值: 0/1 | +| collType | String | 否 | 抵质押类型,可选值: 一类/二类/三类 | +| collThirdParty | String | 否 | 抵质押物是否三方所有,值: 0/1 | **请求示例:** @@ -47,10 +49,12 @@ "idNum": "110101199001011234", "guarType": "抵押", "applyAmt": "500000", - "bizProof": "true", - "loanLoop": "false", + "loanPurpose": "business", + "loanTerm": "3", + "bizProof": "1", + "loanLoop": "0", "collType": "一类", - "collThirdParty": "false" + "collThirdParty": "0" } ``` @@ -64,12 +68,14 @@ "id": 1, "modelOutputId": 100, "serialNum": "20250119143025123", - "orgCode": "931000", + "orgCode": "892000", "runType": "1", "custIsn": "CUST001", "custType": "个人", "guarType": "抵押", "applyAmt": "500000", + "loanPurpose": "business", + "loanTerm": "3", "custName": "张三", "idType": "身份证", "createTime": "2025-01-19 14:30:25", @@ -136,7 +142,7 @@ "id": 2, "modelOutputId": 101, "serialNum": "20250119143125456", - "orgCode": "931000", + "orgCode": "892000", "runType": "1", "custIsn": "CORP001", "custType": "企业", @@ -189,7 +195,7 @@ GET /loanPricing/workflow/list?pageNum=1&pageSize=10&custName=科技 "id": 1, "modelOutputId": 100, "serialNum": "20250119143025123", - "orgCode": "931000", + "orgCode": "892000", "custIsn": "CUST001", "custType": "企业", "guarType": "抵押", @@ -240,7 +246,7 @@ GET /loanPricing/workflow/20250119143025123 "id": 1, "modelOutputId": 100, "serialNum": "20250119143025123", - "orgCode": "931000", + "orgCode": "892000", "runType": "1", "custIsn": "CUST001", "custType": "企业", diff --git a/doc/implementation-report-2026-04-09-default-node25-for-playwright.md b/doc/implementation-report-2026-04-09-default-node25-for-playwright.md new file mode 100644 index 0000000..cf2e4ee --- /dev/null +++ b/doc/implementation-report-2026-04-09-default-node25-for-playwright.md @@ -0,0 +1,30 @@ +# 2026-04-09 默认切换 Node 25 以支持 Playwright 实施记录 + +## 变更内容 +- 将 `nvm` 默认别名从 `14` 调整为 `25` +- 清理了本次验证过程中残留的 Playwright 浏览器安装进程 + +## 执行命令 +- `zsh -lic 'nvm alias default 25'` + +## 变更结果 +- 新开的交互式 `zsh` 默认 Node 版本变为 `v25.9.0` +- 默认 npm/npx 版本变为 `11.12.1` + +## 验证结果 +- `zsh -lic 'node -v && npm -v && npx -v && nvm current && nvm alias default'` + - `node v25.9.0` + - `npm 11.12.1` + - `npx 11.12.1` + - `nvm current = v25.9.0` + - `default -> 25 (-> v25.9.0 *)` +- `zsh -lic '... playwright_cli.sh --help'` + - Playwright CLI 帮助输出正常 +- `zsh -lic '... playwright_cli.sh --session verify-default-25 open https://example.com && snapshot && close && list'` + - 页面成功打开 + - 页面标题为 `Example Domain` + - 快照成功输出 + - 浏览器关闭后 `list` 返回 `(no browsers)` + +## 结论 +- 默认 shell 环境下已可直接使用 Playwright,无需再先手动执行 `nvm use 25` diff --git a/doc/implementation-report-2026-04-09-install-node25-and-node14.md b/doc/implementation-report-2026-04-09-install-node25-and-node14.md new file mode 100644 index 0000000..ac279e2 --- /dev/null +++ b/doc/implementation-report-2026-04-09-install-node25-and-node14.md @@ -0,0 +1,30 @@ +# 2026-04-09 安装 Node 25 与 Node 14 实施记录 + +## 变更内容 +- 使用 `nvm` 安装 `node v25.9.0` +- 使用 `nvm` 安装 `node v14.21.3` +- 调整 `/Users/wkc/.npmrc`,删除与 `nvm` 冲突的 `prefix=~/.npm-global` +- 保留 npm 镜像配置:`registry=https://registry.npmmirror.com` + +## 处理过程 +- `node 25` 通过 `nvm` 正常安装成功 +- `node 14` 在 Apple Silicon 原生环境下无法直接下载 `darwin-arm64` 安装包 +- 原生源码编译 `node 14` 失败,错误来自当前 macOS Command Line Tools/SDK 与旧版 `node 14` 源码不兼容 +- 改为通过 Rosetta 以 `x64` 方式安装 `node 14`,安装成功 + +## 验证结果 +- `zsh -lic 'nvm use 25 && node -v && npm -v'` 验证结果: + - `node v25.9.0` + - `npm 11.12.1` +- `zsh -lic 'nvm use 14 && node -v && npm -v'` 验证结果: + - `node v14.21.3` + - `npm 6.14.18` +- `arch -x86_64 /bin/zsh -lic 'nvm use 14 && node -v && npm -v'` 验证结果: + - `node v14.21.3` + - `npm 6.14.18` +- 新开的交互式 `zsh` 默认版本: + - `node v14.21.3` + - `npm 6.14.18` + +## 备注 +- `nvm` 当前默认别名已指向 `14` diff --git a/doc/implementation-report-2026-04-09-node-uninstall-and-nvm-install.md b/doc/implementation-report-2026-04-09-node-uninstall-and-nvm-install.md new file mode 100644 index 0000000..4a41c48 --- /dev/null +++ b/doc/implementation-report-2026-04-09-node-uninstall-and-nvm-install.md @@ -0,0 +1,25 @@ +# 2026-04-09 Node 卸载与 nvm 安装实施记录 + +## 变更内容 +- 卸载了 Homebrew 安装的 `node 25.8.1_1` +- 删除了本地目录 `/Users/wkc/.local/node-v14.21.3-darwin-x64` +- 更新了 `/Users/wkc/.zshrc` +- 安装了 `nvm 0.40.4` + +## shell 配置调整 +- 删除旧配置:`export PATH="/Users/wkc/.local/node-v14.21.3-darwin-x64/bin:$PATH"` +- 新增 `nvm` 初始化配置: + +```sh +export NVM_DIR="$HOME/.nvm" +[ -s "/opt/homebrew/opt/nvm/nvm.sh" ] && \. "/opt/homebrew/opt/nvm/nvm.sh" +[ -s "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" ] && \. "/opt/homebrew/opt/nvm/etc/bash_completion.d/nvm" +``` + +## 验证结果 +- 交互式 `zsh` 下 `nvm --version` 返回 `0.40.4` +- `node` 命令已不存在,说明当前环境中已无非 `nvm` 管理的 Node 版本 +- `nvm ls` 返回 `N/A`,说明尚未通过 `nvm` 安装任何 Node 版本 + +## 备注 +- `brew uninstall node` 过程中触发了 Homebrew 自动移除若干仅供该版本 Node 使用的依赖库 diff --git a/doc/implementation-report-2026-04-09-remove-id-number-validation.md b/doc/implementation-report-2026-04-09-remove-id-number-validation.md new file mode 100644 index 0000000..0d3d3ef --- /dev/null +++ b/doc/implementation-report-2026-04-09-remove-id-number-validation.md @@ -0,0 +1,25 @@ +# 证件输入校验移除实施记录 + +## 实施时间 +- 2026-04-09 + +## 修改内容 +- 移除个人新增弹窗中的证件号码格式校验 +- 移除企业新增弹窗中的证件号码格式校验 +- 两个新增弹窗的证件号码规则统一保留为必填校验 +- 新增前端源码断言,约束后续不再恢复证件号码格式校验 + +## 修改文件 +- `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue` +- `ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue` +- `ruoyi-ui/tests/id-number-validation-removal.test.js` +- `ruoyi-ui/package.json` +- `doc/implementation-report-2026-04-09-remove-id-number-validation.md` + +## 验证方式 +1. `npm --prefix ruoyi-ui run test:id-number-validation-removal` +2. `npm --prefix ruoyi-ui run build:prod` + +## 说明 +- 本次移除的是前端证件号码格式校验,不影响证件号码必填约束 +- 后端本次未新增或调整证件号码格式校验逻辑 diff --git a/doc/implementation-report-2026-04-09-shangyu-retail-input-params.md b/doc/implementation-report-2026-04-09-shangyu-retail-input-params.md new file mode 100644 index 0000000..a5845f8 --- /dev/null +++ b/doc/implementation-report-2026-04-09-shangyu-retail-input-params.md @@ -0,0 +1,93 @@ +# 上虞个人利率测算输入参数对齐实施记录 + +## 实施时间 +- 2026-04-09 + +## 修改内容 +- 个人新增弹窗补齐 `loanPurpose`、`loanTerm` +- 个人新增弹窗 `loanTerm` 固定为 `1-6` 年 +- 个人新增弹窗 `collType` 选项统一为 `一类/二类/三类` +- 个人新增弹窗开关字段提交值由 `true/false` 调整为 `1/0` +- 个人详情页补齐 `贷款用途` 展示 +- 个人与企业详情、模型输出布尔展示兼容 `1/0` +- 后端个人创建 DTO 补齐 `loanPurpose`、`loanTerm` +- 后端个人 DTO 到流程实体映射补齐 `loanPurpose`、`loanTerm` +- 后端模型调用 DTO 补齐 `loanTerm`、`loanLoop` +- 后端个人模型调用前统一将 `bizProof`、`loanLoop`、`collThirdParty` 规范为 `0/1` +- `orgCode` 统一为 `892000` +- `ModelInvokeDTO` 注释、接口文档、SQL 基线和迁移脚本同步统一为 `892000` +- 新增前端源码断言与后端单元测试 + +## 修改文件 +- `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue` +- `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue` +- `ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue` +- `ruoyi-ui/src/views/loanPricing/workflow/components/CorporateWorkflowDetail.vue` +- `ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue` +- `ruoyi-ui/tests/personal-create-input-params.test.js` +- `ruoyi-ui/package.json` +- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java` +- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java` +- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java` +- `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java` +- `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java` +- `doc/api/loan-pricing-workflow-api.md` +- `sql/loan_pricing_workflow.sql` +- `sql/loan_pricing_schema_20260328.sql` +- `sql/loan_pricing_prod_init_20260331.sql` +- `sql/fix_comments.sql` +- `sql/fix_all_comments.sql` +- `sql/update_org_code_default_20260409.sql` +- `doc/2026-04-09-shangyu-retail-input-params-design.md` +- `doc/2026-04-09-shangyu-retail-input-params-frontend-plan.md` +- `doc/implementation-report-2026-04-09-shangyu-retail-input-params.md` + +## 数据库处理 +1. 执行 `sql/update_org_code_default_20260409.sql` +2. 将 `loan_pricing_workflow.org_code` 默认值修改为 `892000` +3. 将存量 `loan_pricing_workflow.org_code` 非 `892000` 的记录统一更新为 `892000` + +## 验证方式 +1. 前端源码断言: + - `npm --prefix ruoyi-ui run test:personal-create-input-params` + - `npm --prefix ruoyi-ui run test:retail-display-fields` +2. 后端单元测试: + - `mvn -pl ruoyi-loan-pricing -Dtest=LoanPricingModelServiceTest,LoanPricingModelServicePersonalParamsTest test` +3. 前端构建: + - `npm --prefix ruoyi-ui run build:prod` +4. 数据库验证: + - 查询 `loan_pricing_workflow.org_code` 字段默认值 + - 查询存量数据中是否仍存在非 `892000` 记录 +5. 接口验证: + - `/login/test` 获取 token + - `POST /loanPricing/workflow/create/personal` 正常场景 + - `POST /loanPricing/workflow/create/personal` 缺少 `loanPurpose` 场景 + - `POST /loanPricing/workflow/create/personal` 分支值场景 + - `GET /loanPricing/workflow/{serialNum}` 验证回显 +6. 页面验证: + - 启动前端 dev server + - 使用浏览器打开流程列表页 + - 校验新增弹窗下拉选项 + - 页面创建个人流程并打开详情页确认回显 + +## 验证结果 +- `npm --prefix ruoyi-ui run test:personal-create-input-params` 通过 +- `npm --prefix ruoyi-ui run test:retail-display-fields` 通过 +- `mvn -pl ruoyi-loan-pricing -Dtest=LoanPricingModelServiceTest,LoanPricingModelServicePersonalParamsTest test` 通过 +- `npm --prefix ruoyi-ui run build:prod` 通过,输出 `Build complete.` +- 数据库验证结果: + - `loan_pricing_workflow.org_code` 默认值为 `892000` + - 存量非 `892000` 记录数为 `0` +- 接口验证结果: + - 正常场景创建成功,返回 `orgCode=892000`,并持久化 `loanPurpose`、`loanTerm` + - 缺少 `loanPurpose` 时返回 `贷款用途不能为空` + - 分支场景详情回显 `bizProof=0`、`loanLoop=1`、`collThirdParty=0` +- 页面验证结果: + - 新增弹窗显示 `贷款用途` + - 借款期限下拉仅包含 `1-6` + - 抵质押类型下拉为 `一类/二类/三类` + - 页面创建流程成功后,详情页展示 `贷款用途=经营`、`借款期限=6` + +## 说明 +- 浏览器验证使用系统 `Google Chrome.app` +- 本次验证期间启动的后端、前端和浏览器进程已在任务结束前关闭 diff --git a/doc/implementation-report-2026-04-09-start-script-ps-ef.md b/doc/implementation-report-2026-04-09-start-script-ps-ef.md new file mode 100644 index 0000000..cd63f29 --- /dev/null +++ b/doc/implementation-report-2026-04-09-start-script-ps-ef.md @@ -0,0 +1,26 @@ +# 启动脚本进程判断改为 ps -ef 实施记录 + +## 修改内容 + +- 将 `bin/prod/restart_java.sh` 中的后端进程收集逻辑由 `pgrep -f` 改为 `ps -ef | awk` +- 将 `bin/restart_java_backend.sh` 中的后端进程收集逻辑由 `pgrep -f` 改为 `ps -ef | awk` +- 删除 `bin/restart_java_backend.sh` 中对 `pgrep` 命令的依赖校验 +- 更新 `bin/prod/restart_java_test.sh`,补充 `ps -ef` / `pgrep` 约束校验,并修正测试夹具中的 JDK 目录 +- 新增 `bin/restart_java_backend_test.sh`,校验本地后端重启脚本已改用 `ps -ef` + +## 实现说明 + +- 两份脚本都只在 `ps -ef` 结果中匹配同时满足“包含脚本标记参数”和“`-jar` 指向目标 jar”这两个条件的 Java 进程 +- 进程筛选时继续忽略 `` 记录,避免误判僵尸进程 +- 现有 PID 文件校验逻辑保持不变,本次只收敛“扫描当前是否已有进程”的实现方式 + +## 路径检查 + +- 已确认本次实施记录保存路径为 `doc/implementation-report-2026-04-09-start-script-ps-ef.md` + +## 验证结果 + +- 已执行 `sh bin/prod/restart_java_test.sh` +- 已执行 `sh bin/restart_java_backend_test.sh` +- 已执行 `sh -n bin/prod/restart_java.sh && sh -n bin/restart_java_backend.sh` +- 已确认测试中拉起的假 Java 进程在脚本收尾阶段自动停止并清理 diff --git a/doc/implementation-report-2026-04-10-login-shell-default-node25.md b/doc/implementation-report-2026-04-10-login-shell-default-node25.md new file mode 100644 index 0000000..b4837fb --- /dev/null +++ b/doc/implementation-report-2026-04-10-login-shell-default-node25.md @@ -0,0 +1,37 @@ +# 2026-04-10 登录 Shell 默认使用 Node 25 实施记录 + +## 变更内容 +- 保持 `nvm` 默认别名为 `25` +- 在 `~/.zprofile` 中补充 `nvm` 初始化,并在登录 shell 启动时自动执行 `nvm use default` + +## 根因分析 +- `nvm alias default 25` 已经存在,但仅在交互式 shell 中可用 +- `zsh -lc` 启动的是登录非交互 shell,不会读取 `~/.zshrc` +- 因此这类场景下 `node`、`npm`、`npx` 未进入 PATH,表现为 `npx` 启动失败 + +## 修改文件 +- `~/.zprofile` +- `doc/implementation-report-2026-04-10-login-shell-default-node25.md` + +## 验证项 +- 验证登录 shell 在不手动执行 `nvm use` 的情况下可直接识别 `node` +- 验证登录 shell 在不手动执行 `nvm use` 的情况下可直接识别 `npx` +- 验证 `nvm` 默认别名仍然指向 `25` + +## 执行命令 +- `zsh -lc 'nvm alias default 25'` +- `zsh -lc 'echo NODE=$(node -v); echo NPM=$(npm -v); echo NPX=$(npx -v); echo NODE_PATH=$(command -v node); echo NPX_PATH=$(command -v npx); echo NVM_CURRENT=$(nvm current); echo NVM_ALIAS=$(nvm alias default | tail -n 1)'` + +## 验证结果 +- `nvm` 默认别名输出为 `default -> 25 (-> v25.9.0 *)` +- 登录 shell 输出 `NODE=v25.9.0` +- 登录 shell 输出 `NPM=11.12.1` +- 登录 shell 输出 `NPX=11.12.1` +- 登录 shell 输出 `NODE_PATH=/Users/wkc/.nvm/versions/node/v25.9.0/bin/node` +- 登录 shell 输出 `NPX_PATH=/Users/wkc/.nvm/versions/node/v25.9.0/bin/npx` +- 登录 shell 输出 `NVM_CURRENT=v25.9.0` +- 登录 shell 输出 `NVM_ALIAS=default -> 25 (-> v25.9.0 *)` + +## 结论 +- `zsh -lc` 场景下已默认切换到 Node `25.9.0` +- `npx` 在登录 shell 中已可直接使用,无需先手动执行 `nvm use 25` diff --git a/doc/~$上虞利率测算接口文档.xlsx b/doc/~$上虞利率测算接口文档.xlsx new file mode 100644 index 0000000..3c3a794 Binary files /dev/null and b/doc/~$上虞利率测算接口文档.xlsx differ diff --git a/doc/上虞利率测算接口文档.xlsx b/doc/上虞利率测算接口文档.xlsx new file mode 100644 index 0000000..ebc5268 Binary files /dev/null and b/doc/上虞利率测算接口文档.xlsx differ diff --git a/docs/superpowers/plans/2026-04-10-personal-pricing-collateral-optional-frontend-plan.md b/docs/superpowers/plans/2026-04-10-personal-pricing-collateral-optional-frontend-plan.md new file mode 100644 index 0000000..38c565b --- /dev/null +++ b/docs/superpowers/plans/2026-04-10-personal-pricing-collateral-optional-frontend-plan.md @@ -0,0 +1,110 @@ +# Personal Pricing Collateral Optional Frontend Implementation Plan + +> **For agentic workers:** REQUIRED: Use superpowers:executing-plans to implement this plan in this repository. Do not use subagents. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** 让个人利率定价新增流程中的“抵质押类型”改为非必填,企业流程保持不变。 + +**Architecture:** 维持现有个人新增弹窗的数据结构、字段名称和提交报文不变,只移除前端表单对 `collType` 的必填限制。通过前端源码断言测试和页面联调共同验证“字段可空提交”的行为,避免扩大到企业流程或后端接口。 + +**Tech Stack:** Vue 2、Element UI、npm、Node.js 断言脚本 + +--- + +### Task 1: 固化个人新增流程“抵质押类型非必填”的前端测试 + +**Files:** +- Modify: `ruoyi-ui/tests/personal-create-input-params.test.js` +- Inspect: `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue` + +- [ ] **Step 1: 核对当前个人新增弹窗中的 `collType` 规则** + +Run: `sed -n '150,220p' ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue` +Expected: 能看到 `rules.collType` 里仍有 `required: true`。 + +- [ ] **Step 2: 先写失败断言** + +在 `ruoyi-ui/tests/personal-create-input-params.test.js` 中增加断言,明确个人新增弹窗不应再包含: + +```js +required: true, message: "请选择抵质押类型" +``` + +- [ ] **Step 3: 运行测试确认红灯** + +Run: `npm --prefix ruoyi-ui run test:personal-create-input-params` +Expected: FAIL,提示个人新增弹窗仍将抵质押类型设为必填。 + +### Task 2: 移除个人新增弹窗中 `collType` 的必填限制 + +**Files:** +- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue` + +- [ ] **Step 1: 删除 `collType` 的必填校验** + +把: + +```js +collType: [ + {required: true, message: "请选择抵质押类型", trigger: "change"} +] +``` + +移除,使个人表单规则中不再声明 `collType` 为必填。 + +- [ ] **Step 2: 保持其它字段与提交逻辑不变** + +确认以下内容不改动: + +```js +collType: undefined, +collThirdParty: false +``` + +以及提交时的: + +```js +collThirdParty: this.form.collThirdParty ? '1' : '0' +``` + +- [ ] **Step 3: 重新运行测试确认转绿** + +Run: `npm --prefix ruoyi-ui run test:personal-create-input-params` +Expected: PASS,输出包含 `personal create input params assertions passed`。 + +### Task 3: 页面联调并补实施记录 + +**Files:** +- Create: `docs/implementation-reports/2026-04-10-personal-pricing-collateral-optional-frontend.md` + +- [ ] **Step 1: 启动前端页面** + +Run: `npm --prefix ruoyi-ui run dev` +Expected: 前端本地开发服务启动成功,可访问新增个人利率定价流程页面。 + +- [ ] **Step 2: 浏览器确认页面行为** + +联调确认: +- 个人新增弹窗“抵质押类型”字段可为空 +- 不选择“抵质押类型”时其余必填项填完整仍可点击提交 +- 企业新增弹窗规则不受影响 + +- [ ] **Step 3: 停止本次测试启动的前端进程** + +Run: `ps -ef | rg 'vue-cli-service serve|npm --prefix ruoyi-ui run dev'` +Expected: 仅停止本次联调启动的前端进程。 + +- [ ] **Step 4: 编写实施记录** + +实施记录至少包含: + +```markdown +- 本次仅调整个人利率定价新增流程 +- 个人新增弹窗已移除抵质押类型必填校验 +- 企业新增流程未改动 +- 已执行前端断言测试与页面联调验证 +``` + +- [ ] **Step 5: 核对实施记录保存路径** + +Run: `ls docs/implementation-reports/2026-04-10-personal-pricing-collateral-optional-frontend.md` +Expected: 文件位于仓库 `docs/implementation-reports` 目录。 diff --git a/ruoyi-admin/.DS_Store b/ruoyi-admin/.DS_Store new file mode 100644 index 0000000..5f70785 Binary files /dev/null and b/ruoyi-admin/.DS_Store differ diff --git a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java index 37fdb51..d65bbfb 100644 --- a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java +++ b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java @@ -16,7 +16,7 @@ public class ModelInvokeDTO { /** * 机构编码(必填) - * 固定值:931000 + * 固定值:892000 */ private String orgCode; @@ -73,6 +73,12 @@ public class ModelInvokeDTO { */ private String applyAmt; + /** + * 贷款期限(必填) + * 单位:年 + */ + private String loanTerm; + /** * 净身企业(非必填) * 可选值:true/false @@ -119,19 +125,25 @@ public class ModelInvokeDTO { /** * 是否有经营佐证(非必填) - * 可选值:true/false + * 可选值:0/1 */ private String bizProof; + /** + * 循环功能(非必填) + * 可选值:0/1 + */ + private String loanLoop; + /** * 抵质押类型(非必填) - * 可选值:抵押/质押 + * 可选值:一类/二类/三类 */ private String collType; /** * 抵质押物是否三方所有(非必填) - * 可选值:true/false + * 可选值:0/1 */ private String collThirdParty; diff --git a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java index fe7c1cc..12504cc 100644 --- a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java +++ b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java @@ -41,15 +41,24 @@ public class PersonalLoanPricingCreateDTO implements Serializable { @NotBlank(message = "申请金额不能为空") private String applyAmt; - @Schema(description = "是否有经营佐证", example = "true") + @Schema(description = "贷款用途", requiredMode = Schema.RequiredMode.REQUIRED, example = "business", allowableValues = {"consumer", "business"}) + @NotBlank(message = "贷款用途不能为空") + @Pattern(regexp = "^(consumer|business)$", message = "贷款用途必须是:consumer、business之一") + private String loanPurpose; + + @Schema(description = "借款期限(年)", requiredMode = Schema.RequiredMode.REQUIRED, example = "3") + @NotBlank(message = "借款期限不能为空") + private String loanTerm; + + @Schema(description = "是否有经营佐证", example = "1") private String bizProof; - @Schema(description = "循环功能", example = "false") + @Schema(description = "循环功能", example = "0") private String loanLoop; @Schema(description = "抵质押类型", example = "一类") private String collType; - @Schema(description = "抵质押物是否三方所有", example = "false") + @Schema(description = "抵质押物是否三方所有", example = "0") private String collThirdParty; } diff --git a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java index 6b5edce..05517d9 100644 --- a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java +++ b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingModelService.java @@ -60,6 +60,10 @@ public class LoanPricingModelService { } ModelInvokeDTO modelInvokeDTO = new ModelInvokeDTO(); BeanUtils.copyProperties(loanPricingWorkflow, modelInvokeDTO); + if ("个人".equals(loanPricingWorkflow.getCustType())) + { + normalizePersonalModelInvokeDTO(modelInvokeDTO); + } JSONObject response = modelService.invokeModel(modelInvokeDTO); if (loanPricingWorkflow.getCustType().equals("个人")){ // 个人模型 @@ -83,4 +87,24 @@ public class LoanPricingModelService { log.info("更新流程信息成功"); } } + + private void normalizePersonalModelInvokeDTO(ModelInvokeDTO modelInvokeDTO) + { + modelInvokeDTO.setBizProof(toZeroOne(modelInvokeDTO.getBizProof())); + modelInvokeDTO.setLoanLoop(toZeroOne(modelInvokeDTO.getLoanLoop())); + modelInvokeDTO.setCollThirdParty(toZeroOne(modelInvokeDTO.getCollThirdParty())); + } + + private String toZeroOne(String value) + { + if ("true".equals(value) || "1".equals(value)) + { + return "1"; + } + if ("false".equals(value) || "0".equals(value)) + { + return "0"; + } + return value; + } } diff --git a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java index a59b06a..ca0715e 100644 --- a/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java +++ b/ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java @@ -28,6 +28,8 @@ public class LoanPricingConverter { entity.setIdNum(dto.getIdNum()); entity.setGuarType(dto.getGuarType()); entity.setApplyAmt(dto.getApplyAmt()); + entity.setLoanPurpose(dto.getLoanPurpose()); + entity.setLoanTerm(dto.getLoanTerm()); entity.setCollType(dto.getCollType()); entity.setCollThirdParty(dto.getCollThirdParty()); // 映射个人特有字段 diff --git a/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java b/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java new file mode 100644 index 0000000..f777773 --- /dev/null +++ b/ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java @@ -0,0 +1,125 @@ +package com.ruoyi.loanpricing.service; + +import com.alibaba.fastjson2.JSONObject; +import com.ruoyi.loanpricing.domain.dto.ModelInvokeDTO; +import com.ruoyi.loanpricing.domain.dto.PersonalLoanPricingCreateDTO; +import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow; +import com.ruoyi.loanpricing.mapper.LoanPricingWorkflowMapper; +import com.ruoyi.loanpricing.mapper.ModelCorpOutputFieldsMapper; +import com.ruoyi.loanpricing.mapper.ModelRetailOutputFieldsMapper; +import com.ruoyi.loanpricing.util.LoanPricingConverter; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +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.assertThrows; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.argThat; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +@ExtendWith(MockitoExtension.class) +class LoanPricingModelServicePersonalParamsTest { + + @Mock + private ModelService modelService; + + @Mock + private LoanPricingWorkflowMapper loanPricingWorkflowMapper; + + @Mock + private ModelRetailOutputFieldsMapper modelRetailOutputFieldsMapper; + + @Mock + private ModelCorpOutputFieldsMapper modelCorpOutputFieldsMapper; + + @Mock + private SensitiveFieldCryptoService sensitiveFieldCryptoService; + + @InjectMocks + private LoanPricingModelService loanPricingModelService; + + @Test + void shouldContainLoanPurposeAndLoanTermInPersonalCreateDto() throws NoSuchFieldException { + assertNotNull(PersonalLoanPricingCreateDTO.class.getDeclaredField("loanPurpose")); + assertNotNull(PersonalLoanPricingCreateDTO.class.getDeclaredField("loanTerm")); + } + + @Test + void shouldMapLoanPurposeAndLoanTermFromPersonalDto() { + PersonalLoanPricingCreateDTO dto = new PersonalLoanPricingCreateDTO(); + dto.setCustIsn("CUST001"); + dto.setCustName("张三"); + dto.setGuarType("信用"); + dto.setApplyAmt("100000"); + dto.setLoanPurpose("business"); + dto.setLoanTerm("3"); + + LoanPricingWorkflow workflow = LoanPricingConverter.toEntity(dto); + + assertEquals("business", workflow.getLoanPurpose()); + assertEquals("3", workflow.getLoanTerm()); + } + + @Test + void shouldContainLoanTermAndLoanLoopInModelInvokeDto() throws NoSuchFieldException { + assertNotNull(ModelInvokeDTO.class.getDeclaredField("loanTerm")); + assertNotNull(ModelInvokeDTO.class.getDeclaredField("loanLoop")); + } + + @Test + void shouldInvokePersonalModelWithExpectedParams() { + LoanPricingWorkflow workflow = new LoanPricingWorkflow(); + workflow.setId(1L); + workflow.setSerialNum("202604090001"); + workflow.setOrgCode("892000"); + workflow.setRunType("1"); + workflow.setCustIsn("CUST001"); + workflow.setCustType("个人"); + workflow.setCustName("cipher-name"); + workflow.setIdType("身份证"); + workflow.setIdNum("cipher-id"); + workflow.setGuarType("信用"); + workflow.setApplyAmt("100000"); + workflow.setLoanPurpose("business"); + workflow.setLoanTerm("3"); + workflow.setBizProof("true"); + workflow.setLoanLoop("false"); + workflow.setCollThirdParty("true"); + workflow.setCollType("一类"); + + JSONObject response = new JSONObject(); + response.put("calculateRate", "6.15"); + + when(loanPricingWorkflowMapper.selectById(1L)).thenReturn(workflow); + when(sensitiveFieldCryptoService.decrypt("cipher-name")).thenReturn("张三"); + when(sensitiveFieldCryptoService.decrypt("cipher-id")).thenReturn("110101199001011234"); + when(modelService.invokeModel(any())).thenReturn(response); + + loanPricingModelService.invokeModelAsync(1L); + + verify(modelService).invokeModel(argThat((ModelInvokeDTO dto) -> + Objects.equals("202604090001", dto.getSerialNum()) + && Objects.equals("892000", dto.getOrgCode()) + && Objects.equals("1", dto.getRunType()) + && Objects.equals("CUST001", dto.getCustIsn()) + && Objects.equals("个人", dto.getCustType()) + && Objects.equals("张三", dto.getCustName()) + && Objects.equals("身份证", dto.getIdType()) + && Objects.equals("110101199001011234", dto.getIdNum()) + && Objects.equals("信用", dto.getGuarType()) + && Objects.equals("100000", dto.getApplyAmt()) + && Objects.equals("business", 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()))); + } +} diff --git a/ruoyi-ui/dist.zip b/ruoyi-ui/dist.zip index 23353f6..20132d9 100644 Binary files a/ruoyi-ui/dist.zip and b/ruoyi-ui/dist.zip differ diff --git a/ruoyi-ui/package.json b/ruoyi-ui/package.json index 0897d9c..5fbcc8a 100644 --- a/ruoyi-ui/package.json +++ b/ruoyi-ui/package.json @@ -10,7 +10,9 @@ "build:stage": "vue-cli-service build --mode staging", "preview": "node build/index.js --preview", "test:password-transfer": "node tests/password-transfer-api.test.js", - "test:retail-display-fields": "node tests/retail-display-fields.test.js" + "test:retail-display-fields": "node tests/retail-display-fields.test.js", + "test:personal-create-input-params": "node tests/personal-create-input-params.test.js", + "test:id-number-validation-removal": "node tests/id-number-validation-removal.test.js" }, "keywords": [ "vue", diff --git a/ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue b/ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue index 27f0224..44f906c 100644 --- a/ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue +++ b/ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue @@ -124,22 +124,6 @@ export default { } }, data() { - // 统一社会信用代码验证 - const validateIdNum = (rule, value, callback) => { - if (!value) { - callback(new Error('请输入证件号码')) - } else if (this.form.idType === '统一社会信用代码') { - const reg = /^[0-9A-HJ-NPQRTUWXY]{2}\d{6}[0-9A-HJ-NPQRTUWXY]{10}$/ - if (!reg.test(value)) { - callback(new Error('请输入正确的统一社会信用代码')) - } else { - callback() - } - } else { - callback() - } - } - // 金额验证 const validateApplyAmt = (rule, value, callback) => { if (!value) { @@ -175,7 +159,7 @@ export default { return { submitting: false, form: { - orgCode: '', + orgCode: '892000', runType: '1', custIsn: undefined, custName: undefined, @@ -204,7 +188,7 @@ export default { {required: true, message: "请选择证件类型", trigger: "change"} ], idNum: [ - {required: true, validator: validateIdNum, trigger: "blur"} + {required: true, message: "证件号码不能为空", trigger: "blur"} ], guarType: [ {required: true, message: "请选择担保方式", trigger: "change"} @@ -242,7 +226,7 @@ export default { /** 表单重置 */ reset() { this.form = { - orgCode: '', + orgCode: '892000', runType: '1', custIsn: undefined, custName: undefined, diff --git a/ruoyi-ui/src/views/loanPricing/workflow/components/CorporateWorkflowDetail.vue b/ruoyi-ui/src/views/loanPricing/workflow/components/CorporateWorkflowDetail.vue index 2270879..9af1f30 100644 --- a/ruoyi-ui/src/views/loanPricing/workflow/components/CorporateWorkflowDetail.vue +++ b/ruoyi-ui/src/views/loanPricing/workflow/components/CorporateWorkflowDetail.vue @@ -150,8 +150,8 @@ export default { methods: { /** 格式化布尔值为中文 */ formatBoolean(value) { - if (value === 'true' || value === true) return '是' - if (value === 'false' || value === false) return '否' + if (value === 'true' || value === true || value === '1' || value === 1) return '是' + if (value === 'false' || value === false || value === '0' || value === 0) return '否' return value || '-' }, /** 获取基准利率 */ diff --git a/ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue b/ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue index cf1b660..4aa649d 100644 --- a/ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue +++ b/ruoyi-ui/src/views/loanPricing/workflow/components/ModelOutputDisplay.vue @@ -242,8 +242,8 @@ export default { methods: { /** 格式化布尔值为中文 */ formatBoolean(value) { - if (value === 'true' || value === true) return '是' - if (value === 'false' || value === false) return '否' + if (value === 'true' || value === true || value === '1' || value === 1) return '是' + if (value === 'false' || value === false || value === '0' || value === 0) return '否' return value || '-' }, /** 格式化贷款用途 */ diff --git a/ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue b/ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue index 6351e7e..910cb95 100644 --- a/ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue +++ b/ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue @@ -51,6 +51,23 @@ + + + + + + + + + + + + + + + + + @@ -70,9 +87,9 @@ - + @@ -102,22 +119,6 @@ export default { } }, data() { - // 身份证验证 - const validateIdNum = (rule, value, callback) => { - if (!value) { - callback(new Error('请输入证件号码')) - } else if (this.form.idType === '身份证') { - const reg = /(^\d{15}$)|(^\d{18}$)|(^\d{17}(\d|X|x)$)/ - if (!reg.test(value)) { - callback(new Error('请输入正确的身份证号码')) - } else { - callback() - } - } else { - callback() - } - } - // 金额验证 const validateApplyAmt = (rule, value, callback) => { if (!value) { @@ -135,9 +136,12 @@ export default { } return { + loanTermOptions: [ + '1', '2', '3', '4', '5', '6' + ], submitting: false, form: { - orgCode: '', + orgCode: '892000', runType: '1', custIsn: undefined, custName: undefined, @@ -145,6 +149,8 @@ export default { idNum: undefined, guarType: undefined, applyAmt: undefined, + loanPurpose: undefined, + loanTerm: undefined, bizProof: false, loanLoop: false, collType: undefined, @@ -163,7 +169,7 @@ export default { {required: true, message: "请选择证件类型", trigger: "change"} ], idNum: [ - {required: true, validator: validateIdNum, trigger: "blur"} + {required: true, message: "证件号码不能为空", trigger: "blur"} ], guarType: [ {required: true, message: "请选择担保方式", trigger: "change"} @@ -171,8 +177,11 @@ export default { applyAmt: [ {required: true, validator: validateApplyAmt, trigger: "blur"} ], - collType: [ - {required: true, message: "请选择抵质押类型", trigger: "change"} + loanPurpose: [ + {required: true, message: "请选择贷款用途", trigger: "change"} + ], + loanTerm: [ + {required: true, message: "请选择借款期限", trigger: "change"} ] } } @@ -198,7 +207,7 @@ export default { /** 表单重置 */ reset() { this.form = { - orgCode: '', + orgCode: '892000', runType: '1', custIsn: undefined, custName: undefined, @@ -206,6 +215,8 @@ export default { idNum: undefined, guarType: undefined, applyAmt: undefined, + loanPurpose: undefined, + loanTerm: undefined, bizProof: false, loanLoop: false, collType: undefined, @@ -231,9 +242,9 @@ export default { // 转换开关值为字符串 const data = { ...this.form, - bizProof: this.form.bizProof ? 'true' : 'false', - loanLoop: this.form.loanLoop ? 'true' : 'false', - collThirdParty: this.form.collThirdParty ? 'true' : 'false' + bizProof: this.form.bizProof ? '1' : '0', + loanLoop: this.form.loanLoop ? '1' : '0', + collThirdParty: this.form.collThirdParty ? '1' : '0' } createPersonalWorkflow(data).then(response => { diff --git a/ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue b/ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue index 55145d3..d2c3a43 100644 --- a/ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue +++ b/ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue @@ -72,6 +72,7 @@ {{ detailData.guarType }} {{ detailData.applyAmt }} 元 + {{ formatLoanPurpose(detailData.loanPurpose) }} {{ detailData.loanTerm || '-' }} {{ formatBoolean(detailData.bizProof) @@ -149,8 +150,14 @@ export default { methods: { /** 格式化布尔值为中文 */ formatBoolean(value) { - if (value === 'true' || value === true) return '是' - if (value === 'false' || value === false) return '否' + if (value === 'true' || value === true || value === '1' || value === 1) return '是' + if (value === 'false' || value === false || value === '0' || value === 0) return '否' + return value || '-' + }, + /** 格式化贷款用途 */ + formatLoanPurpose(value) { + if (value === 'consumer') return '消费' + if (value === 'business') return '经营' return value || '-' }, /** 获取基准利率 */ diff --git a/ruoyi-ui/tests/id-number-validation-removal.test.js b/ruoyi-ui/tests/id-number-validation-removal.test.js new file mode 100644 index 0000000..ba9e505 --- /dev/null +++ b/ruoyi-ui/tests/id-number-validation-removal.test.js @@ -0,0 +1,34 @@ +const fs = require('fs') +const path = require('path') +const assert = require('assert') + +function read(relativePath) { + return fs.readFileSync(path.join(__dirname, '..', relativePath), 'utf8') +} + +const personalCreateDialog = read('src/views/loanPricing/workflow/components/PersonalCreateDialog.vue') +const corporateCreateDialog = read('src/views/loanPricing/workflow/components/CorporateCreateDialog.vue') + +assert( + !personalCreateDialog.includes('const validateIdNum ='), + '个人新增弹窗仍包含证件号码格式校验函数' +) + +assert( + !corporateCreateDialog.includes('const validateIdNum ='), + '企业新增弹窗仍包含证件号码格式校验函数' +) + +assert( + personalCreateDialog.includes("idNum: [") && + personalCreateDialog.includes('{required: true, message: "证件号码不能为空", trigger: "blur"}'), + '个人新增弹窗证件号码规则应仅保留必填' +) + +assert( + corporateCreateDialog.includes("idNum: [") && + corporateCreateDialog.includes('{required: true, message: "证件号码不能为空", trigger: "blur"}'), + '企业新增弹窗证件号码规则应仅保留必填' +) + +console.log('id number validation removal assertions passed') diff --git a/ruoyi-ui/tests/personal-create-input-params.test.js b/ruoyi-ui/tests/personal-create-input-params.test.js new file mode 100644 index 0000000..88ac252 --- /dev/null +++ b/ruoyi-ui/tests/personal-create-input-params.test.js @@ -0,0 +1,65 @@ +const fs = require('fs') +const path = require('path') +const assert = require('assert') + +function read(relativePath) { + return fs.readFileSync(path.join(__dirname, '..', relativePath), 'utf8') +} + +const personalCreateDialog = read('src/views/loanPricing/workflow/components/PersonalCreateDialog.vue') +const personalDetail = read('src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue') + +assert( + personalCreateDialog.includes('label="贷款用途"') && personalCreateDialog.includes('prop="loanPurpose"'), + '个人新增弹窗缺少贷款用途字段' +) + +assert( + personalCreateDialog.includes('label="借款期限(年)"') && personalCreateDialog.includes('prop="loanTerm"'), + '个人新增弹窗缺少借款期限字段' +) + +assert( + personalCreateDialog.includes("value=\"consumer\"") && personalCreateDialog.includes("value=\"business\""), + '个人新增弹窗缺少贷款用途选项' +) + +assert( + personalCreateDialog.includes('loanTermOptions') && + personalCreateDialog.includes("'1'") && + personalCreateDialog.includes("'6'") && + !personalCreateDialog.includes("'7'"), + '个人新增弹窗借款期限选项应限制为 1-6 年' +) + +assert( + personalCreateDialog.includes('label="一类"') && + personalCreateDialog.includes('label="二类"') && + personalCreateDialog.includes('label="三类"') && + !personalCreateDialog.includes('label="一线"'), + '个人新增弹窗抵质押类型选项未按 Excel 对齐' +) + +assert( + !personalCreateDialog.includes('{required: true, message: "请选择抵质押类型", trigger: "change"}'), + '个人新增弹窗仍将抵质押类型设为必填' +) + +assert( + personalCreateDialog.includes("bizProof: this.form.bizProof ? '1' : '0'") && + personalCreateDialog.includes("loanLoop: this.form.loanLoop ? '1' : '0'") && + personalCreateDialog.includes("collThirdParty: this.form.collThirdParty ? '1' : '0'"), + '个人新增弹窗开关字段未按 1/0 提交' +) + +assert( + personalDetail.includes('label="贷款用途"') && personalDetail.includes('detailData.loanPurpose'), + '个人详情页缺少贷款用途展示' +) + +assert( + personalDetail.includes("value === '1'") && personalDetail.includes("value === '0'"), + '个人详情页布尔格式化未兼容 1/0' +) + +console.log('personal create input params assertions passed') diff --git a/sql/fix_all_comments.sql b/sql/fix_all_comments.sql index ccb4ecc..4952d32 100644 --- a/sql/fix_all_comments.sql +++ b/sql/fix_all_comments.sql @@ -8,7 +8,7 @@ ALTER TABLE loan_pricing_workflow MODIFY COLUMN `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', MODIFY COLUMN `serial_num` varchar(50) NOT NULL COMMENT '业务方流水号', MODIFY COLUMN `model_output_id` bigint(20) DEFAULT NULL COMMENT '模型输出ID', - MODIFY COLUMN `org_code` varchar(20) NOT NULL DEFAULT '' COMMENT '机构编码', + MODIFY COLUMN `org_code` varchar(20) NOT NULL DEFAULT '892000' COMMENT '机构编码(统一值892000)', MODIFY COLUMN `run_type` varchar(10) NOT NULL DEFAULT '1' COMMENT '运行模式: 1-同步', MODIFY COLUMN `cust_isn` varchar(50) NOT NULL COMMENT '客户内码', MODIFY COLUMN `cust_type` varchar(20) NOT NULL COMMENT '客户类型: 个人/企业', diff --git a/sql/fix_comments.sql b/sql/fix_comments.sql index 5103088..c6e5bc1 100644 --- a/sql/fix_comments.sql +++ b/sql/fix_comments.sql @@ -14,7 +14,7 @@ ALTER TABLE loan_pricing_workflow MODIFY COLUMN `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', MODIFY COLUMN `serial_num` varchar(50) NOT NULL COMMENT '业务方流水号', MODIFY COLUMN `model_output_id` bigint(20) DEFAULT NULL COMMENT '模型输出ID', - MODIFY COLUMN `org_code` varchar(20) NOT NULL DEFAULT '' COMMENT '机构编码', + MODIFY COLUMN `org_code` varchar(20) NOT NULL DEFAULT '892000' COMMENT '机构编码(统一值892000)', MODIFY COLUMN `run_type` varchar(10) NOT NULL DEFAULT '1' COMMENT '运行模式: 1-同步', MODIFY COLUMN `cust_isn` varchar(50) NOT NULL COMMENT '客户内码', MODIFY COLUMN `cust_type` varchar(20) NOT NULL COMMENT '客户类型: 个人/企业', diff --git a/sql/loan_pricing_prod_init_20260331.sql b/sql/loan_pricing_prod_init_20260331.sql index e091b48..7468d21 100644 --- a/sql/loan_pricing_prod_init_20260331.sql +++ b/sql/loan_pricing_prod_init_20260331.sql @@ -741,7 +741,7 @@ CREATE TABLE `loan_pricing_workflow` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `serial_num` varchar(50) NOT NULL COMMENT '业务方流水号', `model_output_id` bigint(20) DEFAULT NULL COMMENT '模型输出ID', - `org_code` varchar(20) NOT NULL DEFAULT '' COMMENT '机构编码', + `org_code` varchar(20) NOT NULL DEFAULT '892000' COMMENT '机构编码(统一值892000)', `run_type` varchar(10) NOT NULL DEFAULT '1' COMMENT '运行模式: 1-同步', `cust_isn` varchar(50) NOT NULL COMMENT '客户内码', `cust_type` varchar(20) NOT NULL COMMENT '客户类型: 个人/企业', diff --git a/sql/loan_pricing_schema_20260328.sql b/sql/loan_pricing_schema_20260328.sql index 7366339..f02f608 100644 --- a/sql/loan_pricing_schema_20260328.sql +++ b/sql/loan_pricing_schema_20260328.sql @@ -323,7 +323,7 @@ CREATE TABLE `loan_pricing_workflow` ( `id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '主键ID', `serial_num` varchar(50) NOT NULL COMMENT '业务方流水号', `model_output_id` bigint(20) DEFAULT NULL COMMENT '模型输出ID', - `org_code` varchar(20) NOT NULL DEFAULT '' COMMENT '机构编码', + `org_code` varchar(20) NOT NULL DEFAULT '892000' COMMENT '机构编码(统一值892000)', `run_type` varchar(10) NOT NULL DEFAULT '1' COMMENT '运行模式: 1-同步', `cust_isn` varchar(50) NOT NULL COMMENT '客户内码', `cust_type` varchar(20) NOT NULL COMMENT '客户类型: 个人/企业', diff --git a/sql/loan_pricing_workflow.sql b/sql/loan_pricing_workflow.sql index 484560b..b81fa25 100644 --- a/sql/loan_pricing_workflow.sql +++ b/sql/loan_pricing_workflow.sql @@ -4,7 +4,7 @@ CREATE TABLE `loan_pricing_workflow` ( `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', `serial_num` varchar(50) NOT NULL COMMENT '业务方流水号', `model_output_id` bigint(20) NULL COMMENT '模型输出id', - `org_code` varchar(20) NOT NULL DEFAULT '' COMMENT '机构编码', + `org_code` varchar(20) NOT NULL DEFAULT '892000' COMMENT '机构编码(统一值892000)', `run_type` varchar(10) NOT NULL DEFAULT '1' COMMENT '运行模式: 1-同步', `cust_isn` varchar(50) NOT NULL COMMENT '客户内码', `cust_type` varchar(20) NOT NULL COMMENT '客户类型: 个人/企业', diff --git a/sql/update_org_code_default_20260409.sql b/sql/update_org_code_default_20260409.sql new file mode 100644 index 0000000..efa6699 --- /dev/null +++ b/sql/update_org_code_default_20260409.sql @@ -0,0 +1,10 @@ +-- 统一 loan_pricing_workflow 表 org_code 默认值与存量数据 + +ALTER TABLE `loan_pricing_workflow` + MODIFY COLUMN `org_code` varchar(20) NOT NULL DEFAULT '892000' COMMENT '机构编码(统一值892000)'; + +UPDATE `loan_pricing_workflow` +SET `org_code` = '892000' +WHERE `org_code` IS NULL + OR `org_code` = '' + OR `org_code` <> '892000';