diff --git a/doc/implementation-report-2026-05-11-shangyu-pricing-field-adjustment-plans.md b/doc/implementation-report-2026-05-11-shangyu-pricing-field-adjustment-plans.md
new file mode 100644
index 0000000..05f996b
--- /dev/null
+++ b/doc/implementation-report-2026-05-11-shangyu-pricing-field-adjustment-plans.md
@@ -0,0 +1,19 @@
+# 2026-05-11 上虞利率定价字段口径调整实施计划记录
+
+## 修改内容
+
+- 新增后端实施计划:`docs/superpowers/plans/2026-05-11-shangyu-pricing-field-adjustment-backend-plan.md`。
+- 新增前端实施计划:`docs/superpowers/plans/2026-05-11-shangyu-pricing-field-adjustment-frontend-plan.md`。
+- 后端计划覆盖创建 DTO、流程实体、模型入参、转换器、服务层校验、SQL schema 和后端测试。
+- 前端计划覆盖个人/企业新增弹窗、业务种类选项、抵质押类型选项、`couponRate` 条件必填、静态断言、构建和 Playwright 真实页面验证。
+
+## 范围说明
+
+- 计划依据:`docs/superpowers/specs/2026-05-11-shangyu-pricing-field-adjustment-design.md`。
+- 对公 `businessType` 上传模型这一条已按用户确认从本次实施范围排除;计划只覆盖 `couponRate` 的模型入参新增。
+- 本次仅产出实施计划,未进入业务代码实现。
+
+## 待验证
+
+- 计划需通过计划审查后再进入实施。
+- 后续实现完成后需要补充 `doc/implementation-report-2026-05-11-shangyu-pricing-field-adjustment.md`,记录真实代码改动、测试命令和页面验证结果。
diff --git a/docs/superpowers/plans/2026-05-11-shangyu-pricing-field-adjustment-backend-plan.md b/docs/superpowers/plans/2026-05-11-shangyu-pricing-field-adjustment-backend-plan.md
new file mode 100644
index 0000000..60ccc8b
--- /dev/null
+++ b/docs/superpowers/plans/2026-05-11-shangyu-pricing-field-adjustment-backend-plan.md
@@ -0,0 +1,647 @@
+# Shangyu Pricing Field Adjustment Backend 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 backend create-flow contract so `businessType`, `couponRate`, personal removed fields, and customer-type-specific collateral options match the approved Shangyu pricing spec.
+
+**Architecture:** Keep the existing workflow creation API and entity flow. Add `couponRate` to the DTO/entity/model path, tighten service-layer validation in `LoanPricingWorkflowServiceImpl`, remove personal create dependencies on `loanPurpose` and `bizProof`, and update SQL schema files in place.
+
+**Tech Stack:** Spring Boot, RuoYi, MyBatis Plus, Lombok, Jakarta Validation, JUnit 5, Mockito, MySQL SQL files.
+
+---
+
+## File Structure
+
+- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java`
+ - Change `businessType` values to `新增/存量新增/存量转贷`.
+ - Remove personal create DTO dependency on `loanPurpose` and `bizProof`.
+ - Add `couponRate`.
+- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/CorporateLoanPricingCreateDTO.java`
+ - Change `businessType` values to `新增/存量新增/存量转贷`.
+ - Expand `collType` values for corporate mortgage and pledge paths.
+ - Add `couponRate`.
+- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/entity/LoanPricingWorkflow.java`
+ - Update `businessType` comment and add persisted `couponRate`.
+- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java`
+ - Add `couponRate` only. Do not add `businessType`; the user excluded that model-input branch from this scope.
+- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java`
+ - Map `couponRate`.
+ - Stop mapping removed personal fields from the personal create DTO.
+- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImpl.java`
+ - Update business type validation.
+ - Add collateral-option validation by `custType + guarType`.
+ - Add required `couponRate` validation for `质押 + 存单质押`.
+- Modify Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImplTest.java`
+ - Add validation coverage and update existing create tests to set valid `businessType`.
+- Modify Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java`
+ - Remove old personal `loanPurpose` DTO expectations so Maven test compilation remains valid after DTO removal.
+ - Keep `loanTerm/loanLoop` model assertions.
+- Create Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/util/LoanPricingConverterTest.java`
+ - Confirm personal removed fields no longer drive conversion and `couponRate` maps.
+- Modify Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServiceTest.java`
+ - Confirm `couponRate` is copied into `ModelInvokeDTO` via the model invocation path.
+- Create: `sql/add_coupon_rate_20260511.sql`
+ - Migration for existing databases.
+- Modify: `sql/loan_pricing_workflow.sql`
+ - Add `coupon_rate`.
+- Modify: `sql/loan_pricing_schema_20260328.sql`
+ - Add `coupon_rate` to `loan_pricing_workflow`.
+- Modify: `sql/loan_pricing_prod_init_20260331.sql`
+ - Add `coupon_rate` to production init schema.
+
+## Task 1: Add Failing Backend Contract Tests
+
+**Files:**
+- Modify Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImplTest.java`
+- Modify Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java`
+- Create Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/util/LoanPricingConverterTest.java`
+- Modify Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServiceTest.java`
+
+- [ ] **Step 1: Update existing create tests with valid business type**
+
+In `LoanPricingWorkflowServiceImplTest`, every test that calls `createLoanPricing` must set:
+
+```java
+workflow.setBusinessType("新增");
+```
+
+For tests using mortgage or pledge, also set an allowed `collType` for the matching `custType`.
+
+- [ ] **Step 2: Update old personal parameter tests**
+
+In `LoanPricingModelServicePersonalParamsTest`, replace the old DTO field test:
+
+Add the static import if it is not present:
+
+```java
+import static org.junit.jupiter.api.Assertions.assertNull;
+```
+
+```java
+@Test
+void shouldRemoveLoanPurposeAndKeepLoanTermInPersonalCreateDto() throws NoSuchFieldException {
+ assertThrows(NoSuchFieldException.class,
+ () -> PersonalLoanPricingCreateDTO.class.getDeclaredField("loanPurpose"));
+ assertNotNull(PersonalLoanPricingCreateDTO.class.getDeclaredField("loanTerm"));
+}
+```
+
+Replace the old converter test that called `dto.setLoanPurpose(...)`:
+
+```java
+@Test
+void shouldMapLoanTermWithoutLoanPurposeFromPersonalDto() {
+ PersonalLoanPricingCreateDTO dto = new PersonalLoanPricingCreateDTO();
+ dto.setCustIsn("CUST001");
+ dto.setCustName("张三");
+ dto.setGuarType("信用");
+ dto.setApplyAmt("100000");
+ dto.setBusinessType("新增");
+ dto.setLoanTerm("3");
+
+ LoanPricingWorkflow workflow = LoanPricingConverter.toEntity(dto);
+
+ assertNull(workflow.getLoanPurpose());
+ assertNull(workflow.getBizProof());
+ assertEquals("3", workflow.getLoanTerm());
+}
+```
+
+In `shouldInvokePersonalModelWithExpectedParams`, remove:
+
+```java
+workflow.setLoanPurpose("business");
+workflow.setBizProof("true");
+```
+
+and remove these argument matcher clauses:
+
+```java
+&& Objects.equals("business", dto.getLoanPurpose())
+&& Objects.equals("1", dto.getBizProof())
+```
+
+Keep the existing `loanTerm`, `loanLoop`, `collThirdParty`, and `collType` assertions.
+
+- [ ] **Step 3: Add failing business type and collateral validation tests**
+
+Add tests like:
+
+```java
+@Test
+void shouldRejectOldBusinessType() {
+ LoanPricingWorkflow workflow = validPersonalWorkflow();
+ workflow.setBusinessType("新客");
+
+ ServiceException ex = assertThrows(ServiceException.class,
+ () -> loanPricingWorkflowService.createLoanPricing(workflow));
+
+ assertEquals("业务种类必须是:新增、存量新增、存量转贷之一", ex.getMessage());
+}
+
+@Test
+void shouldRequireHistoryRateForStockTransfer() {
+ LoanPricingWorkflow workflow = validCorporateWorkflow();
+ workflow.setBusinessType("存量转贷");
+ workflow.setLoanRateHistory(null);
+
+ ServiceException ex = assertThrows(ServiceException.class,
+ () -> loanPricingWorkflowService.createLoanPricing(workflow));
+
+ assertEquals("请选择历史贷款合同", ex.getMessage());
+}
+
+@Test
+void shouldRequireCouponRateForCertificatePledge() {
+ LoanPricingWorkflow workflow = validPersonalWorkflow();
+ workflow.setGuarType("质押");
+ workflow.setCollType("存单质押");
+ workflow.setCouponRate(null);
+
+ ServiceException ex = assertThrows(ServiceException.class,
+ () -> loanPricingWorkflowService.createLoanPricing(workflow));
+
+ assertEquals("存单票面利率不能为空", ex.getMessage());
+}
+```
+
+Add helper methods:
+
+```java
+private LoanPricingWorkflow validPersonalWorkflow() {
+ LoanPricingWorkflow workflow = new LoanPricingWorkflow();
+ workflow.setCustType("个人");
+ workflow.setCustIsn("P001");
+ workflow.setCustName("张三");
+ workflow.setIdNum("330102199001011234");
+ workflow.setGuarType("信用");
+ workflow.setApplyAmt("100000");
+ workflow.setBusinessType("新增");
+ return workflow;
+}
+
+private LoanPricingWorkflow validCorporateWorkflow() {
+ LoanPricingWorkflow workflow = new LoanPricingWorkflow();
+ workflow.setCustType("企业");
+ workflow.setCustIsn("C001");
+ workflow.setCustName("测试企业");
+ workflow.setIdNum("91330100MA0000000X");
+ workflow.setGuarType("信用");
+ workflow.setApplyAmt("1000000");
+ workflow.setBusinessType("新增");
+ return workflow;
+}
+```
+
+- [ ] **Step 4: Add failing allowed-option tests**
+
+Add focused tests:
+
+```java
+@Test
+void shouldAllowPersonalMortgageLineType() {
+ LoanPricingWorkflow workflow = validPersonalWorkflow();
+ workflow.setGuarType("抵押");
+ workflow.setCollType("一线");
+
+ when(sensitiveFieldCryptoService.encrypt(any())).thenAnswer(invocation -> invocation.getArgument(0));
+
+ loanPricingWorkflowService.createLoanPricing(workflow);
+
+ verify(loanPricingWorkflowMapper).insert(any());
+}
+
+@Test
+void shouldRejectCorporateMortgageTypeForPersonalMortgage() {
+ LoanPricingWorkflow workflow = validPersonalWorkflow();
+ workflow.setGuarType("抵押");
+ workflow.setCollType("排污权抵押");
+
+ ServiceException ex = assertThrows(ServiceException.class,
+ () -> loanPricingWorkflowService.createLoanPricing(workflow));
+
+ assertEquals("个人抵押抵质押类型必须是:一线、一类、二类、三类之一", ex.getMessage());
+}
+
+@Test
+void shouldAllowCorporatePledgeEquityType() {
+ LoanPricingWorkflow workflow = validCorporateWorkflow();
+ workflow.setGuarType("质押");
+ workflow.setCollType("股权质押");
+
+ when(sensitiveFieldCryptoService.encrypt(any())).thenAnswer(invocation -> invocation.getArgument(0));
+
+ loanPricingWorkflowService.createLoanPricing(workflow);
+
+ verify(loanPricingWorkflowMapper).insert(any());
+}
+```
+
+- [ ] **Step 5: Add failing converter test**
+
+Create `LoanPricingConverterTest`:
+
+```java
+package com.ruoyi.loanpricing.util;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+import static org.junit.jupiter.api.Assertions.assertNull;
+
+import com.ruoyi.loanpricing.domain.dto.CorporateLoanPricingCreateDTO;
+import com.ruoyi.loanpricing.domain.dto.PersonalLoanPricingCreateDTO;
+import com.ruoyi.loanpricing.domain.entity.LoanPricingWorkflow;
+import org.junit.jupiter.api.Test;
+
+class LoanPricingConverterTest {
+ @Test
+ void shouldMapCouponRateForPersonalWorkflow() {
+ PersonalLoanPricingCreateDTO dto = new PersonalLoanPricingCreateDTO();
+ dto.setCustIsn("P001");
+ dto.setGuarType("质押");
+ dto.setApplyAmt("100000");
+ dto.setBusinessType("新增");
+ dto.setCouponRate("2.15");
+
+ LoanPricingWorkflow workflow = LoanPricingConverter.toEntity(dto);
+
+ assertEquals("2.15", workflow.getCouponRate());
+ assertNull(workflow.getLoanPurpose());
+ assertNull(workflow.getBizProof());
+ }
+
+ @Test
+ void shouldMapCouponRateForCorporateWorkflow() {
+ CorporateLoanPricingCreateDTO dto = new CorporateLoanPricingCreateDTO();
+ dto.setCustIsn("C001");
+ dto.setGuarType("质押");
+ dto.setApplyAmt("1000000");
+ dto.setBusinessType("新增");
+ dto.setCouponRate("2.35");
+
+ LoanPricingWorkflow workflow = LoanPricingConverter.toEntity(dto);
+
+ assertEquals("2.35", workflow.getCouponRate());
+ }
+}
+```
+
+- [ ] **Step 6: Add failing model DTO test**
+
+In `LoanPricingModelServiceTest`, add a test or extend the existing argument-captor test to assert:
+
+```java
+assertEquals("2.15", capturedModelInvokeDto.getCouponRate());
+```
+
+The workflow used by that test must set:
+
+```java
+loanPricingWorkflow.setCouponRate("2.15");
+loanPricingWorkflow.setBusinessType("新增");
+```
+
+- [ ] **Step 7: Run backend tests and confirm failure**
+
+Run:
+
+```bash
+mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowServiceImplTest,LoanPricingModelServicePersonalParamsTest,LoanPricingConverterTest,LoanPricingModelServiceTest -Dsurefire.failIfNoSpecifiedTests=false test
+```
+
+Expected: FAIL because `couponRate` and new validation are not implemented yet.
+
+## Task 2: Implement DTO, Entity, Converter, and Model DTO Fields
+
+**Files:**
+- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java`
+- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/CorporateLoanPricingCreateDTO.java`
+- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/entity/LoanPricingWorkflow.java`
+- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java`
+- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java`
+- Modify Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java`
+
+- [ ] **Step 1: Update personal create DTO**
+
+In `PersonalLoanPricingCreateDTO`:
+
+- Remove the `loanPurpose` field and its validation annotations.
+- Remove the `bizProof` field.
+- Change `businessType` annotations to:
+
+```java
+@Schema(description = "业务种类", requiredMode = Schema.RequiredMode.REQUIRED, example = "新增", allowableValues = {"新增", "存量新增", "存量转贷"})
+@NotBlank(message = "业务种类不能为空")
+@Pattern(regexp = "^(新增|存量新增|存量转贷)$", message = "业务种类必须是:新增、存量新增、存量转贷之一")
+private String businessType;
+```
+
+- Add:
+
+```java
+@Schema(description = "存单票面利率", example = "2.15")
+private String couponRate;
+```
+
+- [ ] **Step 2: Update corporate create DTO**
+
+In `CorporateLoanPricingCreateDTO`:
+
+```java
+@Schema(description = "业务种类", requiredMode = Schema.RequiredMode.REQUIRED, example = "新增", allowableValues = {"新增", "存量新增", "存量转贷"})
+@NotBlank(message = "业务种类不能为空")
+@Pattern(regexp = "^(新增|存量新增|存量转贷)$", message = "业务种类必须是:新增、存量新增、存量转贷之一")
+private String businessType;
+```
+
+Change `collType` annotation to include the combined corporate option set:
+
+```java
+@Schema(description = "抵质押类型", example = "一类", allowableValues = {"一类", "二类", "三类", "四类", "排污权抵押", "设备等其他不动产抵押", "存单质押", "股权质押", "其他质押"})
+@Pattern(regexp = "^(一类|二类|三类|四类|排污权抵押|设备等其他不动产抵押|存单质押|股权质押|其他质押)$", message = "抵质押类型不符合当前客户类型和担保方式")
+private String collType;
+```
+
+Add:
+
+```java
+@Schema(description = "存单票面利率", example = "2.35")
+private String couponRate;
+```
+
+- [ ] **Step 3: Update workflow entity**
+
+In `LoanPricingWorkflow`:
+
+```java
+/** 业务种类: 新增/存量新增/存量转贷 */
+private String businessType;
+
+/** 存单票面利率 */
+private String couponRate;
+```
+
+Keep `loanPurpose` and `bizProof` on the entity for historical rows and existing schema compatibility; they are no longer populated by personal create DTO conversion.
+
+- [ ] **Step 4: Update model DTO**
+
+In `ModelInvokeDTO`, add:
+
+```java
+/**
+ * 存单票面利率
+ */
+private String couponRate;
+```
+
+Do not add `businessType` in this task.
+
+- [ ] **Step 5: Update converter**
+
+In personal conversion, remove:
+
+```java
+entity.setLoanPurpose(dto.getLoanPurpose());
+entity.setBizProof(dto.getBizProof());
+```
+
+Add for both personal and corporate conversion:
+
+```java
+entity.setCouponRate(dto.getCouponRate());
+```
+
+- [ ] **Step 6: Run targeted converter and model tests**
+
+Run:
+
+```bash
+mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingModelServicePersonalParamsTest,LoanPricingConverterTest,LoanPricingModelServiceTest -Dsurefire.failIfNoSpecifiedTests=false test
+```
+
+Expected: personal parameter, converter, and model field tests PASS; service validation tests may still fail until Task 3.
+
+- [ ] **Step 7: Commit field and converter changes**
+
+```bash
+git add ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java \
+ ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/CorporateLoanPricingCreateDTO.java \
+ ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/entity/LoanPricingWorkflow.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/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java \
+ ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/util/LoanPricingConverterTest.java \
+ ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServiceTest.java
+git commit -m "调整上虞利率定价后端字段"
+```
+
+## Task 3: Implement Service-Layer Validation
+
+**Files:**
+- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImpl.java`
+- Modify Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImplTest.java`
+
+- [ ] **Step 1: Replace business type validation**
+
+Update `validateBusinessTypeAndHistoryRate`:
+
+```java
+private void validateBusinessTypeAndHistoryRate(LoanPricingWorkflow workflow) {
+ if (!StringUtils.hasText(workflow.getBusinessType())) {
+ throw new ServiceException("业务种类不能为空");
+ }
+ if (!isOneOf(workflow.getBusinessType(), "新增", "存量新增", "存量转贷")) {
+ throw new ServiceException("业务种类必须是:新增、存量新增、存量转贷之一");
+ }
+ if ("存量转贷".equals(workflow.getBusinessType())
+ && !StringUtils.hasText(workflow.getLoanRateHistory())) {
+ throw new ServiceException("请选择历史贷款合同");
+ }
+}
+```
+
+- [ ] **Step 2: Add collateral and coupon validators**
+
+Add calls before insert:
+
+```java
+validateBusinessTypeAndHistoryRate(loanPricingWorkflow);
+validateCollateralType(loanPricingWorkflow);
+validateCouponRate(loanPricingWorkflow);
+```
+
+Add helper methods:
+
+```java
+private void validateCouponRate(LoanPricingWorkflow workflow) {
+ if ("质押".equals(workflow.getGuarType())
+ && "存单质押".equals(workflow.getCollType())
+ && !StringUtils.hasText(workflow.getCouponRate())) {
+ throw new ServiceException("存单票面利率不能为空");
+ }
+}
+
+private void validateCollateralType(LoanPricingWorkflow workflow) {
+ if (!"抵押".equals(workflow.getGuarType()) && !"质押".equals(workflow.getGuarType())) {
+ return;
+ }
+ if (!StringUtils.hasText(workflow.getCollType())) {
+ throw new ServiceException("请选择抵质押类型");
+ }
+ if ("个人".equals(workflow.getCustType()) && "抵押".equals(workflow.getGuarType())
+ && !isOneOf(workflow.getCollType(), "一线", "一类", "二类", "三类")) {
+ throw new ServiceException("个人抵押抵质押类型必须是:一线、一类、二类、三类之一");
+ }
+ if ("个人".equals(workflow.getCustType()) && "质押".equals(workflow.getGuarType())
+ && !isOneOf(workflow.getCollType(), "存单质押", "其他质押")) {
+ throw new ServiceException("个人质押抵质押类型必须是:存单质押、其他质押之一");
+ }
+ if ("企业".equals(workflow.getCustType()) && "抵押".equals(workflow.getGuarType())
+ && !isOneOf(workflow.getCollType(), "一类", "二类", "三类", "四类", "排污权抵押", "设备等其他不动产抵押")) {
+ throw new ServiceException("企业抵押抵质押类型必须是:一类、二类、三类、四类、排污权抵押、设备等其他不动产抵押之一");
+ }
+ if ("企业".equals(workflow.getCustType()) && "质押".equals(workflow.getGuarType())
+ && !isOneOf(workflow.getCollType(), "存单质押", "股权质押", "其他质押")) {
+ throw new ServiceException("企业质押抵质押类型必须是:存单质押、股权质押、其他质押之一");
+ }
+}
+
+private boolean isOneOf(String value, String... allowedValues) {
+ for (String allowedValue : allowedValues) {
+ if (allowedValue.equals(value)) {
+ return true;
+ }
+ }
+ return false;
+}
+```
+
+- [ ] **Step 3: Run service tests**
+
+Run:
+
+```bash
+mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test
+```
+
+Expected: PASS.
+
+- [ ] **Step 4: Run backend targeted suite**
+
+Run:
+
+```bash
+mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowServiceImplTest,LoanPricingModelServicePersonalParamsTest,LoanPricingConverterTest,LoanPricingModelServiceTest -Dsurefire.failIfNoSpecifiedTests=false test
+```
+
+Expected: PASS.
+
+- [ ] **Step 5: Commit validation changes**
+
+```bash
+git add ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImpl.java \
+ ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImplTest.java
+git commit -m "增加上虞利率定价创建校验"
+```
+
+## Task 4: Update SQL Schema Files
+
+**Files:**
+- Create: `sql/add_coupon_rate_20260511.sql`
+- Modify: `sql/loan_pricing_workflow.sql`
+- Modify: `sql/loan_pricing_schema_20260328.sql`
+- Modify: `sql/loan_pricing_prod_init_20260331.sql`
+
+- [ ] **Step 1: Create migration SQL**
+
+Create `sql/add_coupon_rate_20260511.sql`:
+
+```sql
+-- 上虞利率定价存单票面利率字段
+ALTER TABLE `loan_pricing_workflow`
+ ADD COLUMN `coupon_rate` varchar(100) DEFAULT NULL COMMENT '存单票面利率' AFTER `loan_rate_history`;
+```
+
+- [ ] **Step 2: Update standalone workflow schema**
+
+In `sql/loan_pricing_workflow.sql`, add after `loan_rate_history`:
+
+```sql
+ `coupon_rate` varchar(100) DEFAULT NULL COMMENT '存单票面利率',
+```
+
+- [ ] **Step 3: Update bundled schema files**
+
+Apply the same column to the `loan_pricing_workflow` table definitions in:
+
+- `sql/loan_pricing_schema_20260328.sql`
+- `sql/loan_pricing_prod_init_20260331.sql`
+
+- [ ] **Step 4: Verify SQL references**
+
+Run:
+
+```bash
+rg -n "coupon_rate|存单票面利率" sql
+```
+
+Expected: `coupon_rate` appears in the migration and all three schema/init files.
+
+- [ ] **Step 5: Commit SQL changes**
+
+```bash
+git add sql/add_coupon_rate_20260511.sql \
+ sql/loan_pricing_workflow.sql \
+ sql/loan_pricing_schema_20260328.sql \
+ sql/loan_pricing_prod_init_20260331.sql
+git commit -m "新增存单票面利率数据库字段"
+```
+
+## Task 5: Backend Verification and Record
+
+**Files:**
+- Create or Modify: `doc/implementation-report-2026-05-11-shangyu-pricing-field-adjustment.md`
+
+- [ ] **Step 1: Run backend verification**
+
+Run:
+
+```bash
+mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowServiceImplTest,LoanPricingModelServicePersonalParamsTest,LoanPricingConverterTest,LoanPricingModelServiceTest -Dsurefire.failIfNoSpecifiedTests=false test
+```
+
+Expected: PASS.
+
+- [ ] **Step 2: Check staged-independent status**
+
+Run:
+
+```bash
+git status --short
+```
+
+Expected: only intended backend and SQL files are modified or staged for this implementation slice. Existing unrelated files such as `AGENTS.md`, `.DS_Store`, and operation-manual docs must stay out of these commits.
+
+- [ ] **Step 3: Update implementation record**
+
+Add backend notes to `doc/implementation-report-2026-05-11-shangyu-pricing-field-adjustment.md`:
+
+```markdown
+## 后端实现
+
+- 调整个人/企业创建 DTO 的业务种类口径为 `新增/存量新增/存量转贷`。
+- 新增 `couponRate` 存单票面利率字段,并贯通流程保存和模型入参。
+- 取消对私创建链路对 `loanPurpose`、`bizProof` 的依赖。
+- 增加服务层校验,覆盖业务种类、历史贷款利率、抵质押类型和存单票面利率。
+- 新增 `coupon_rate` 数据库字段及 schema/init SQL。
+
+## 后端验证
+
+- `mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowServiceImplTest,LoanPricingModelServicePersonalParamsTest,LoanPricingConverterTest,LoanPricingModelServiceTest -Dsurefire.failIfNoSpecifiedTests=false test`
+```
+
+- [ ] **Step 4: Commit backend record**
+
+```bash
+git add doc/implementation-report-2026-05-11-shangyu-pricing-field-adjustment.md
+git commit -m "记录上虞字段调整后端实现"
+```
diff --git a/docs/superpowers/plans/2026-05-11-shangyu-pricing-field-adjustment-frontend-plan.md b/docs/superpowers/plans/2026-05-11-shangyu-pricing-field-adjustment-frontend-plan.md
new file mode 100644
index 0000000..53c7f9f
--- /dev/null
+++ b/docs/superpowers/plans/2026-05-11-shangyu-pricing-field-adjustment-frontend-plan.md
@@ -0,0 +1,485 @@
+# 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
+
+
+
+```
+
+- [ ] **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
+
+
+
+
+
+```
+
+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
+
+
+
+```
+
+- [ ] **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
+
+
+
+
+
+```
+
+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 "记录上虞字段调整前端验证"
+```