# Shangyu Pricing Field Adjustment Frontend Implementation Plan

> **For agentic workers:** Follow this repository's `AGENTS.md`: do not invoke `using-superpowers` or subagents during implementation unless the user explicitly requests them. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Update the personal and corporate workflow create dialogs so the displayed fields, dynamic options, required `couponRate`, and submitted payload match the approved Shangyu pricing spec.

**Architecture:** Keep the existing Vue 2 create-dialog components and static Node assertion tests. Update each dialog in place, using computed properties for customer-type-specific collateral options and the `质押 + 存单质押` coupon-rate condition; verify with static tests, production build, and a real Playwright browser check.

**Tech Stack:** Vue 2, Element UI, RuoYi request wrapper, Node static tests, npm scripts, nvm-controlled frontend runtime, Playwright real browser verification.

---

## File Structure

- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue`
  - Change `businessType` options.
  - Remove `loanPurpose` and `bizProof` UI, form fields, rules, reset values, and submit payload handling.
  - Change personal `collateralTypeOptions`.
  - Add `couponRate` conditional field and submit cleanup.
- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue`
  - Change `businessType` options.
  - Change corporate `collateralTypeOptions`.
  - Add `couponRate` conditional field and submit cleanup.
- Modify: `ruoyi-ui/tests/business-type-history-rate.test.js`
  - Update business-type assertions from `新客` to `新增`.
  - Add `couponRate` assertions.
- Modify: `ruoyi-ui/tests/personal-create-input-params.test.js`
  - Assert personal removed fields are absent.
  - Assert personal collateral options match the new spec.
- Modify: `ruoyi-ui/tests/corporate-create-input-params.test.js`
  - Assert corporate collateral options match the new spec.
- Modify: `doc/implementation-report-2026-05-11-shangyu-pricing-field-adjustment.md`
  - Add frontend implementation and Playwright verification notes after execution.

## Task 1: Update Frontend Static Assertions First

**Files:**
- Modify: `ruoyi-ui/tests/business-type-history-rate.test.js`
- Modify: `ruoyi-ui/tests/personal-create-input-params.test.js`
- Modify: `ruoyi-ui/tests/corporate-create-input-params.test.js`

- [ ] **Step 1: Update business-type assertions**

In `business-type-history-rate.test.js`, replace old assertions for `新客` with:

```js
;[
  ['个人新增弹窗', personalCreate],
  ['企业新增弹窗', corporateCreate]
].forEach(([name, source]) => {
  assert(source.includes('label="业务种类"'), `${name} 缺少业务种类`)
  assert(source.includes('value="新增"'), `${name} 缺少新增选项`)
  assert(source.includes('value="存量新增"'), `${name} 缺少存量新增选项`)
  assert(source.includes('value="存量转贷"'), `${name} 缺少存量转贷选项`)
  assert(!source.includes('value="新客"'), `${name} 仍保留旧业务种类 新客`)
  assert(source.includes("if (value === '存量转贷')"), `${name} 未保持仅存量转贷查询历史合同`)
})
```

- [ ] **Step 2: Add coupon-rate assertions**

In the same test file, add:

```js
;[
  ['个人新增弹窗', personalCreate],
  ['企业新增弹窗', corporateCreate]
].forEach(([name, source]) => {
  assert(source.includes('label="存单票面利率"'), `${name} 缺少存单票面利率字段`)
  assert(source.includes('prop="couponRate"'), `${name} 缺少 couponRate prop`)
  assert(source.includes('isCertificatePledge'), `${name} 缺少存单质押条件计算`)
  assert(source.includes("this.form.guarType === '质押'") && source.includes("this.form.collType === '存单质押'"), `${name} couponRate 条件不正确`)
  assert(source.includes('delete data.couponRate'), `${name} 未在非适用条件删除 couponRate`)
  assert(source.includes('存单票面利率不能为空'), `${name} 缺少 couponRate 必填提示`)
})
```

This shared `business-type-history-rate.test.js` is expected to remain failing until both personal and corporate dialogs are updated. Do not use it as a passing checkpoint after only the personal dialog is changed.

- [ ] **Step 3: Update personal-field assertions**

In `personal-create-input-params.test.js`, replace old required assertions for `loanPurpose` and `bizProof` with absence checks:

```js
assert(
  !personalCreateDialog.includes('label="贷款用途"') &&
    !personalCreateDialog.includes('prop="loanPurpose"') &&
    !personalCreateDialog.includes('loanPurpose:'),
  '个人新增弹窗不应继续保留贷款用途字段'
)

assert(
  !personalCreateDialog.includes('label="是否有经营佐证"') &&
    !personalCreateDialog.includes('prop="bizProof"') &&
    !personalCreateDialog.includes('bizProof:'),
  '个人新增弹窗不应继续保留是否有经营佐证字段'
)
```

Update personal collateral assertion:

```js
assert(
  personalCreateDialog.includes("return ['一线', '一类', '二类', '三类']") &&
    personalCreateDialog.includes("return ['存单质押', '其他质押']"),
  '个人新增弹窗抵质押类型选项未按新口径动态切换'
)
```

- [ ] **Step 4: Update corporate collateral assertions**

In `corporate-create-input-params.test.js`, replace old collateral assertion with:

```js
assert(
  corporateCreateDialog.includes("return ['一类', '二类', '三类', '四类', '排污权抵押', '设备等其他不动产抵押']") &&
    corporateCreateDialog.includes("return ['存单质押', '股权质押', '其他质押']"),
  '企业新增弹窗抵质押类型选项未按新口径动态切换'
)
```

- [ ] **Step 5: Run frontend static tests and confirm failure**

Run:

```bash
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:business-type-history-rate && npm --prefix ruoyi-ui run test:personal-create-input-params && npm --prefix ruoyi-ui run test:corporate-create-input-params'
```

Expected: FAIL because the UI is not updated yet.

## Task 2: Update Personal Create Dialog

**Files:**
- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue`

- [ ] **Step 1: Change business type options**

Replace the first option:

```vue
<el-option label="新增" value="新增"/>
<el-option label="存量新增" value="存量新增"/>
<el-option label="存量转贷" value="存量转贷"/>
```

- [ ] **Step 2: Remove personal fields from template**

Delete the `贷款用途` form item and its surrounding column. Delete the `是否有经营佐证` form item. Keep `借款期限(年)` and `循环功能`.

If a row becomes single-column, leave it as a normal `el-row` with one `el-col :span="12"`; do not restructure the whole dialog.

- [ ] **Step 3: Remove personal fields from data, rules, and submit payload**

Remove these from `form` initial state and `reset()`:

```js
loanPurpose: undefined,
bizProof: false,
```

Remove `loanPurpose` from `rules`.

Remove this submit conversion:

```js
bizProof: this.form.bizProof ? '1' : '0',
```

Keep:

```js
loanLoop: this.form.loanLoop ? '1' : '0'
```

- [ ] **Step 4: Update personal collateral options**

Change `collateralTypeOptions()` to:

```js
collateralTypeOptions() {
  if (this.form.guarType === '抵押') {
    return ['一线', '一类', '二类', '三类']
  }
  if (this.form.guarType === '质押') {
    return ['存单质押', '其他质押']
  }
  return []
}
```

- [ ] **Step 5: Add coupon-rate field**

Add under the collateral row:

```vue
<el-col :span="12" v-if="isCertificatePledge">
  <el-form-item label="存单票面利率" prop="couponRate">
    <el-input v-model="form.couponRate" placeholder="请输入存单票面利率"/>
  </el-form-item>
</el-col>
```

If the row already has two columns, put this field in the same `抵质押信息` area and keep the existing `900px` dialog.

- [ ] **Step 6: Add computed condition and validator**

Add:

```js
isCertificatePledge() {
  return this.form.guarType === '质押' && this.form.collType === '存单质押'
}
```

Add a validator in `data()`:

```js
const validateCouponRate = (rule, value, callback) => {
  if (this.isCertificatePledge && !value) {
    callback(new Error('存单票面利率不能为空'))
    return
  }
  callback()
}
```

Add rule:

```js
couponRate: [
  {validator: validateCouponRate, trigger: "blur"}
]
```

- [ ] **Step 7: Add coupon-rate state cleanup**

Add `couponRate: undefined` to `form` initial state and `reset()`.

Add watcher:

```js
'form.collType'() {
  this.resetCouponRateIfNotCertificatePledge()
}
```

Add method:

```js
resetCouponRateIfNotCertificatePledge() {
  if (!this.isCertificatePledge) {
    this.form.couponRate = undefined
    this.$nextTick(() => {
      if (this.$refs.form) {
        this.$refs.form.clearValidate(['couponRate'])
      }
    })
  }
}
```

Call it at the end of `resetCollateralFields()`.

- [ ] **Step 8: Add submit cleanup and front-end guard**

In `submitForm`, before `this.submitting = true`, add:

```js
if (this.isCertificatePledge && !this.form.couponRate) {
  this.$modal.msgWarning("存单票面利率不能为空")
  return
}
```

After collateral handling:

```js
if (!this.isCertificatePledge) {
  delete data.couponRate
}
```

- [ ] **Step 9: Run personal static tests**

Run:

```bash
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:personal-create-input-params'
```

Expected: PASS. The shared `test:business-type-history-rate` is intentionally not run here because it also asserts corporate changes that are implemented in Task 3.

- [ ] **Step 10: Commit personal frontend changes**

```bash
git add ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue \
  ruoyi-ui/tests/personal-create-input-params.test.js
git commit -m "调整上虞对私新增字段口径"
```

## Task 3: Update Corporate Create Dialog

**Files:**
- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue`
- Modify: `ruoyi-ui/tests/corporate-create-input-params.test.js`
- Modify: `ruoyi-ui/tests/business-type-history-rate.test.js`

- [ ] **Step 1: Change business type options**

Use:

```vue
<el-option label="新增" value="新增"/>
<el-option label="存量新增" value="存量新增"/>
<el-option label="存量转贷" value="存量转贷"/>
```

- [ ] **Step 2: Update corporate collateral options**

Change `collateralTypeOptions()` to:

```js
collateralTypeOptions() {
  if (this.form.guarType === '抵押') {
    return ['一类', '二类', '三类', '四类', '排污权抵押', '设备等其他不动产抵押']
  }
  if (this.form.guarType === '质押') {
    return ['存单质押', '股权质押', '其他质押']
  }
  return []
}
```

- [ ] **Step 3: Add coupon-rate field and state**

Mirror the personal dialog implementation:

```vue
<el-col :span="12" v-if="isCertificatePledge">
  <el-form-item label="存单票面利率" prop="couponRate">
    <el-input v-model="form.couponRate" placeholder="请输入存单票面利率"/>
  </el-form-item>
</el-col>
```

Add:

```js
couponRate: undefined
```

to initial `form` and `reset()`.

- [ ] **Step 4: Add computed condition, validator, watcher, and submit cleanup**

Use the same names as personal dialog:

```js
isCertificatePledge() {
  return this.form.guarType === '质押' && this.form.collType === '存单质押'
}
```

Use the same `validateCouponRate`, `couponRate` rule, `form.collType` watcher, `resetCouponRateIfNotCertificatePledge`, front-end guard, and:

```js
if (!this.isCertificatePledge) {
  delete data.couponRate
}
```

- [ ] **Step 5: Run corporate static tests**

Run:

```bash
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:corporate-create-input-params && npm --prefix ruoyi-ui run test:business-type-history-rate'
```

Expected: PASS.

- [ ] **Step 6: Commit corporate frontend changes**

```bash
git add ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue \
  ruoyi-ui/tests/corporate-create-input-params.test.js \
  ruoyi-ui/tests/business-type-history-rate.test.js
git commit -m "调整上虞对公新增字段口径"
```

## Task 4: Frontend Build and Real Page Verification

**Files:**
- Modify: `doc/implementation-report-2026-05-11-shangyu-pricing-field-adjustment.md`

- [ ] **Step 1: Run all related static tests**

Run:

```bash
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:business-type-history-rate && npm --prefix ruoyi-ui run test:personal-create-input-params && npm --prefix ruoyi-ui run test:corporate-create-input-params'
```

Expected: PASS.

- [ ] **Step 2: Run production build**

Run:

```bash
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run build:prod'
```

Expected: PASS and `ruoyi-ui/dist` generated.

- [ ] **Step 3: Start the local app stack**

Use the repository's existing startup scripts if available. If a frontend dev server is needed, run it with Node controlled by `nvm`:

```bash
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run dev'
```

Record the PID and URL. If the backend also needs restart, use the existing repository backend restart script rather than inventing a new start path.

- [ ] **Step 4: Use Playwright on the real page**

Open the actual local workflow page, not a prototype. Verify:

- Personal create dialog:
  - `业务种类` shows `新增/存量新增/存量转贷`.
  - `新客` is absent.
  - `贷款用途` is absent.
  - `是否有经营佐证` is absent.
  - `抵押` shows `一线/一类/二类/三类`.
  - `质押 + 存单质押` shows `存单票面利率`.
  - Leaving `存单票面利率` empty blocks submit.
- Corporate create dialog:
  - `业务种类` shows `新增/存量新增/存量转贷`.
  - `抵押` shows `一类/二类/三类/四类/排污权抵押/设备等其他不动产抵押`.
  - `质押` shows `存单质押/股权质押/其他质押`.
  - `质押 + 存单质押` shows `存单票面利率`.
  - Leaving `存单票面利率` empty blocks submit.
- History-rate behavior:
  - `存量转贷` triggers historical-contract query.
  - `新增` and `存量新增` do not trigger historical-contract query.

- [ ] **Step 5: Stop test processes**

Stop any frontend/backend processes started in this task. Verify with:

```bash
ps -ef | rg 'vue-cli-service|ruoyi-admin|RuoYiApplication'
```

Expected: no leftover processes from this test run.

- [ ] **Step 6: Update implementation record**

Append:

```markdown
## 前端实现

- 调整个人/企业新增弹窗业务种类选项为 `新增/存量新增/存量转贷`。
- 个人新增弹窗剔除 `loanPurpose`、`bizProof`。
- 按客户类型和担保方式调整抵质押类型选项。
- 新增 `质押 + 存单质押` 下的 `couponRate` 存单票面利率字段、必填校验和提交清理。

## 前端验证

- `npm --prefix ruoyi-ui run test:business-type-history-rate`
- `npm --prefix ruoyi-ui run test:personal-create-input-params`
- `npm --prefix ruoyi-ui run test:corporate-create-input-params`
- `npm --prefix ruoyi-ui run build:prod`
- Playwright 真实页面验证：通过
```

- [ ] **Step 7: Commit frontend verification record**

```bash
git add doc/implementation-report-2026-05-11-shangyu-pricing-field-adjustment.md
git commit -m "记录上虞字段调整前端验证"
```
