新增业务种类历史利率实施计划
This commit is contained in:
@@ -0,0 +1,18 @@
|
||||
# 2026-04-29 业务种类与历史贷款利率实施计划记录
|
||||
|
||||
## 修改内容
|
||||
|
||||
- 新增后端实施计划 `docs/superpowers/plans/2026-04-29-business-type-history-rate-backend-plan.md`。
|
||||
- 新增前端实施计划 `docs/superpowers/plans/2026-04-29-business-type-history-rate-frontend-plan.md`。
|
||||
- 后端计划覆盖字段、转换器、服务层校验、历史合同代理接口、mock、SQL 和后端测试。
|
||||
- 前端计划覆盖历史合同查询 API、历史合同单选弹窗、个人/企业新增弹窗、详情展示、静态测试和 browser-use 真实页面验证。
|
||||
- 根据计划审查意见,补充固定客户号 mock 场景 `HISTORY_EMPTY` / `HISTORY_EMPTY_RATE`,确保 browser-use 真实页面测试能稳定覆盖空列表和空历史利率。
|
||||
- 根据计划审查意见,修正前端客户号选择测试命令,避免调用不存在的 npm script。
|
||||
|
||||
## 验证说明
|
||||
|
||||
- 本次仅产出实施计划,未进入代码实现。
|
||||
- 真实页面测试已按用户要求明确使用 `browser-use:browser`,并禁止打开 prototype 页面。
|
||||
- 首轮计划审查发现 4 个执行风险,已按意见补充和修订。
|
||||
- 第二轮计划审查结论为 Approved。
|
||||
- 后续实施完成后需要补充 `doc/implementation-report-2026-04-29-business-type-history-rate.md` 记录代码改动和验证结果。
|
||||
@@ -0,0 +1,742 @@
|
||||
# Business Type History Rate Backend Implementation Plan
|
||||
|
||||
> **For agentic workers:** Follow this repository's `AGENTS.md`: do not enable subagents or `using-superpowers` during implementation unless the user explicitly requests them. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Add backend support for business type persistence, historical loan-rate lookup, and historical loan-rate model input for both personal and corporate workflow creation.
|
||||
|
||||
**Architecture:** Keep the existing workflow creation API shape and extend the request DTOs, entity, converter, service validation, SQL schema, and model DTO. Add one backend proxy service/controller endpoint for historical contracts, following the existing customer-map proxy pattern, plus a mock endpoint for local development and browser-use testing.
|
||||
|
||||
**Tech Stack:** Spring Boot, RuoYi, MyBatis Plus, Lombok, JUnit 5, Mockito, Spring `MockRestServiceServer`, MySQL schema SQL.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/PersonalLoanPricingCreateDTO.java`
|
||||
- Add `businessType` and `loanRateHistory`.
|
||||
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/CorporateLoanPricingCreateDTO.java`
|
||||
- Add the same fields.
|
||||
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/entity/LoanPricingWorkflow.java`
|
||||
- Add persisted fields `businessType` and `loanRateHistory`.
|
||||
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/dto/ModelInvokeDTO.java`
|
||||
- Add `loanRateHistory` only.
|
||||
- Create: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/vo/HistoryLoanContractVO.java`
|
||||
- Represent external historical contract rows with underscore JSON names.
|
||||
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java`
|
||||
- Map `businessType` and `loanRateHistory` for personal and corporate create DTOs.
|
||||
- Create: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanRateHistoryService.java`
|
||||
- Proxy external historical contract lookup.
|
||||
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowController.java`
|
||||
- Add `GET /loanPricing/workflow/history-contract`.
|
||||
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImpl.java`
|
||||
- Add service-layer cross-field validation before insert/model invocation.
|
||||
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockController.java`
|
||||
- Add mock historical contract endpoint with fixed normal, empty, and empty-rate scenarios.
|
||||
- Add fixed customer-map mock customer IDs that return `cust_isn=EMPTY_HISTORY` and `cust_isn=EMPTY_RATE` for browser-use testing.
|
||||
- Modify: `ruoyi-admin/src/main/resources/application-dev.yml`
|
||||
- Add local mock `loan-rate-history.url`.
|
||||
- Modify: `ruoyi-admin/src/main/resources/application-uat.yml`
|
||||
- Add local mock `loan-rate-history.url`.
|
||||
- Modify: `ruoyi-admin/src/main/resources/application-pro.yml`
|
||||
- Add real external `loan-rate-history.url` without `cust_isn=`.
|
||||
- Create: `sql/add_business_type_history_rate_20260429.sql`
|
||||
- Migration for existing databases.
|
||||
- Modify: `sql/loan_pricing_workflow.sql`
|
||||
- Update standalone workflow schema.
|
||||
- Modify: `sql/loan_pricing_schema_20260328.sql`
|
||||
- Update bundled schema.
|
||||
- Modify: `sql/loan_pricing_prod_init_20260331.sql`
|
||||
- Update production init schema.
|
||||
- Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/domain/vo/HistoryLoanContractVOTest.java`
|
||||
- Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanRateHistoryServiceTest.java`
|
||||
- Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowControllerHistoryContractTest.java`
|
||||
- Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockControllerHistoryContractTest.java`
|
||||
- Modify Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockControllerCustomerMapTest.java`
|
||||
- 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`
|
||||
- Modify Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServiceTest.java`
|
||||
|
||||
## Task 1: Add Backend Field Contracts
|
||||
|
||||
**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`
|
||||
- Create: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/vo/HistoryLoanContractVO.java`
|
||||
- Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/domain/vo/HistoryLoanContractVOTest.java`
|
||||
- Modify Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java`
|
||||
|
||||
- [ ] **Step 1: Write failing field tests**
|
||||
|
||||
Add to `LoanPricingModelServicePersonalParamsTest`:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void shouldContainBusinessTypeAndLoanRateHistoryInCreateDtosAndWorkflow() throws NoSuchFieldException {
|
||||
assertNotNull(PersonalLoanPricingCreateDTO.class.getDeclaredField("businessType"));
|
||||
assertNotNull(PersonalLoanPricingCreateDTO.class.getDeclaredField("loanRateHistory"));
|
||||
assertNotNull(CorporateLoanPricingCreateDTO.class.getDeclaredField("businessType"));
|
||||
assertNotNull(CorporateLoanPricingCreateDTO.class.getDeclaredField("loanRateHistory"));
|
||||
assertNotNull(LoanPricingWorkflow.class.getDeclaredField("businessType"));
|
||||
assertNotNull(LoanPricingWorkflow.class.getDeclaredField("loanRateHistory"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldContainLoanRateHistoryButNotBusinessTypeInModelInvokeDto() throws NoSuchFieldException {
|
||||
assertNotNull(ModelInvokeDTO.class.getDeclaredField("loanRateHistory"));
|
||||
assertThrows(NoSuchFieldException.class, () -> ModelInvokeDTO.class.getDeclaredField("businessType"));
|
||||
}
|
||||
```
|
||||
|
||||
Create `HistoryLoanContractVOTest`:
|
||||
|
||||
```java
|
||||
package com.ruoyi.loanpricing.domain.vo;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
class HistoryLoanContractVOTest {
|
||||
@Test
|
||||
void shouldDeserializeUnderscoreFields() throws Exception {
|
||||
String json = "{\"cust_isn\":\"81033011438\",\"loan_contract_history\":\"HT001\",\"guar_type_history\":\"信用\",\"product_code_history\":\"P001\",\"loan_rate_history\":\"3.65\",\"loan_amount_history\":\"100000\",\"loan_sign_date_history\":\"2025-01-01\"}";
|
||||
|
||||
HistoryLoanContractVO vo = new ObjectMapper().readValue(json, HistoryLoanContractVO.class);
|
||||
|
||||
assertNotNull(vo.getCustIsn());
|
||||
assertNotNull(vo.getLoanContractHistory());
|
||||
assertNotNull(vo.getLoanRateHistory());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run tests to verify they fail**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingModelServicePersonalParamsTest,HistoryLoanContractVOTest test
|
||||
```
|
||||
|
||||
Expected: FAIL because the fields and VO do not exist.
|
||||
|
||||
- [ ] **Step 3: Add DTO/entity/model fields**
|
||||
|
||||
Add to both create DTOs:
|
||||
|
||||
```java
|
||||
@Schema(description = "业务种类", requiredMode = Schema.RequiredMode.REQUIRED, example = "存量转贷", allowableValues = {"新客", "存量新增", "存量转贷"})
|
||||
@NotBlank(message = "业务种类不能为空")
|
||||
@Pattern(regexp = "^(新客|存量新增|存量转贷)$", message = "业务种类必须是:新客、存量新增、存量转贷之一")
|
||||
private String businessType;
|
||||
|
||||
@Schema(description = "历史贷款利率", example = "3.65")
|
||||
private String loanRateHistory;
|
||||
```
|
||||
|
||||
Add to `LoanPricingWorkflow`:
|
||||
|
||||
```java
|
||||
/** 业务种类: 新客/存量新增/存量转贷 */
|
||||
private String businessType;
|
||||
|
||||
/** 历史贷款利率 */
|
||||
private String loanRateHistory;
|
||||
```
|
||||
|
||||
Add only this field to `ModelInvokeDTO`:
|
||||
|
||||
```java
|
||||
/**
|
||||
* 历史贷款利率
|
||||
*/
|
||||
private String loanRateHistory;
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Create `HistoryLoanContractVO`**
|
||||
|
||||
```java
|
||||
package com.ruoyi.loanpricing.domain.vo;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.io.Serializable;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class HistoryLoanContractVO implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
@JsonProperty("cust_isn")
|
||||
private String custIsn;
|
||||
|
||||
@JsonProperty("loan_contract_history")
|
||||
private String loanContractHistory;
|
||||
|
||||
@JsonProperty("guar_type_history")
|
||||
private String guarTypeHistory;
|
||||
|
||||
@JsonProperty("product_code_history")
|
||||
private String productCodeHistory;
|
||||
|
||||
@JsonProperty("loan_rate_history")
|
||||
private String loanRateHistory;
|
||||
|
||||
@JsonProperty("loan_amount_history")
|
||||
private String loanAmountHistory;
|
||||
|
||||
@JsonProperty("loan_sign_date_history")
|
||||
private String loanSignDateHistory;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Run field tests**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingModelServicePersonalParamsTest,HistoryLoanContractVOTest test
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```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/domain/vo/HistoryLoanContractVO.java \
|
||||
ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/domain/vo/HistoryLoanContractVOTest.java \
|
||||
ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java
|
||||
git commit -m "新增业务种类与历史利率后端字段"
|
||||
```
|
||||
|
||||
## Task 2: Map and Validate Workflow Creation
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java`
|
||||
- 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/LoanPricingModelServicePersonalParamsTest.java`
|
||||
- Modify Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImplTest.java`
|
||||
|
||||
- [ ] **Step 1: Write failing converter tests**
|
||||
|
||||
Add:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void shouldMapBusinessTypeAndLoanRateHistoryFromPersonalDto() {
|
||||
PersonalLoanPricingCreateDTO dto = new PersonalLoanPricingCreateDTO();
|
||||
dto.setCustIsn("CUST001");
|
||||
dto.setGuarType("信用");
|
||||
dto.setApplyAmt("100000");
|
||||
dto.setBusinessType("存量转贷");
|
||||
dto.setLoanRateHistory("3.65");
|
||||
|
||||
LoanPricingWorkflow workflow = LoanPricingConverter.toEntity(dto);
|
||||
|
||||
assertEquals("存量转贷", workflow.getBusinessType());
|
||||
assertEquals("3.65", workflow.getLoanRateHistory());
|
||||
}
|
||||
```
|
||||
|
||||
Add a similar corporate DTO test in `LoanPricingModelServiceTest` or a new converter-focused test.
|
||||
|
||||
- [ ] **Step 2: Write failing service validation tests**
|
||||
|
||||
Add to `LoanPricingWorkflowServiceImplTest`:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void shouldRejectMissingBusinessTypeBeforeInsert() {
|
||||
LoanPricingWorkflow workflow = validWorkflow();
|
||||
workflow.setBusinessType(null);
|
||||
|
||||
ServiceException ex = assertThrows(ServiceException.class, () -> loanPricingWorkflowService.createLoanPricing(workflow));
|
||||
|
||||
assertEquals("业务种类不能为空", ex.getMessage());
|
||||
verify(loanPricingWorkflowMapper, never()).insert(any());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectInvalidBusinessTypeBeforeInsert() {
|
||||
LoanPricingWorkflow workflow = validWorkflow();
|
||||
workflow.setBusinessType("其他");
|
||||
|
||||
ServiceException ex = assertThrows(ServiceException.class, () -> loanPricingWorkflowService.createLoanPricing(workflow));
|
||||
|
||||
assertEquals("业务种类必须是:新客、存量新增、存量转贷之一", ex.getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectStockTransferWithoutLoanRateHistory() {
|
||||
LoanPricingWorkflow workflow = validWorkflow();
|
||||
workflow.setBusinessType("存量转贷");
|
||||
workflow.setLoanRateHistory(" ");
|
||||
|
||||
ServiceException ex = assertThrows(ServiceException.class, () -> loanPricingWorkflowService.createLoanPricing(workflow));
|
||||
|
||||
assertEquals("请选择历史贷款合同", ex.getMessage());
|
||||
}
|
||||
```
|
||||
|
||||
Add helper:
|
||||
|
||||
```java
|
||||
private LoanPricingWorkflow validWorkflow() {
|
||||
LoanPricingWorkflow workflow = new LoanPricingWorkflow();
|
||||
workflow.setCustIsn("81033011438");
|
||||
workflow.setCustType("个人");
|
||||
workflow.setCustName("张三");
|
||||
workflow.setIdNum("110101199001011234");
|
||||
workflow.setGuarType("信用");
|
||||
workflow.setApplyAmt("100000");
|
||||
workflow.setBusinessType("新客");
|
||||
return workflow;
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run tests to verify they fail**
|
||||
|
||||
```bash
|
||||
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingModelServicePersonalParamsTest,LoanPricingModelServiceTest,LoanPricingWorkflowServiceImplTest test
|
||||
```
|
||||
|
||||
Expected: FAIL because mapping and validation do not exist.
|
||||
|
||||
- [ ] **Step 4: Implement converter mapping**
|
||||
|
||||
Add to both `toEntity(...)` methods:
|
||||
|
||||
```java
|
||||
entity.setBusinessType(dto.getBusinessType());
|
||||
entity.setLoanRateHistory(dto.getLoanRateHistory());
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Implement service validation**
|
||||
|
||||
In `LoanPricingWorkflowServiceImpl`, before defaults/encryption/insert in `createLoanPricing(...)`, call:
|
||||
|
||||
```java
|
||||
validateBusinessTypeAndHistoryRate(loanPricingWorkflow);
|
||||
```
|
||||
|
||||
Add:
|
||||
|
||||
```java
|
||||
private void validateBusinessTypeAndHistoryRate(LoanPricingWorkflow workflow) {
|
||||
if (!StringUtils.hasText(workflow.getBusinessType())) {
|
||||
throw new ServiceException("业务种类不能为空");
|
||||
}
|
||||
if (!"新客".equals(workflow.getBusinessType())
|
||||
&& !"存量新增".equals(workflow.getBusinessType())
|
||||
&& !"存量转贷".equals(workflow.getBusinessType())) {
|
||||
throw new ServiceException("业务种类必须是:新客、存量新增、存量转贷之一");
|
||||
}
|
||||
if ("存量转贷".equals(workflow.getBusinessType())
|
||||
&& !StringUtils.hasText(workflow.getLoanRateHistory())) {
|
||||
throw new ServiceException("请选择历史贷款合同");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Import `com.ruoyi.common.exception.ServiceException`.
|
||||
|
||||
- [ ] **Step 6: Run validation tests**
|
||||
|
||||
```bash
|
||||
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingModelServicePersonalParamsTest,LoanPricingModelServiceTest,LoanPricingWorkflowServiceImplTest test
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 7: Commit**
|
||||
|
||||
```bash
|
||||
git add ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/util/LoanPricingConverter.java \
|
||||
ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImpl.java \
|
||||
ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java \
|
||||
ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServiceTest.java \
|
||||
ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/impl/LoanPricingWorkflowServiceImplTest.java
|
||||
git commit -m "校验并映射业务种类历史利率"
|
||||
```
|
||||
|
||||
## Task 3: Add Historical Contract Proxy and Mock
|
||||
|
||||
**Files:**
|
||||
- Create: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanRateHistoryService.java`
|
||||
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowController.java`
|
||||
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockController.java`
|
||||
- Modify: `ruoyi-admin/src/main/resources/application-dev.yml`
|
||||
- Modify: `ruoyi-admin/src/main/resources/application-uat.yml`
|
||||
- Modify: `ruoyi-admin/src/main/resources/application-pro.yml`
|
||||
- Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanRateHistoryServiceTest.java`
|
||||
- Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockControllerHistoryContractTest.java`
|
||||
- Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowControllerHistoryContractTest.java`
|
||||
- Modify Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockControllerCustomerMapTest.java`
|
||||
|
||||
- [ ] **Step 1: Write failing service tests**
|
||||
|
||||
Create tests following `LoanPricingCustomerMapServiceTest`:
|
||||
|
||||
```java
|
||||
@Test
|
||||
void shouldQueryHistoryContractsWithCustIsnParam() {
|
||||
RestTemplate restTemplate = new RestTemplate();
|
||||
MockRestServiceServer server = MockRestServiceServer.createServer(restTemplate);
|
||||
LoanRateHistoryService service = new LoanRateHistoryService(restTemplate, "http://mock/history?appCode=abc");
|
||||
|
||||
server.expect(requestTo("http://mock/history?appCode=abc&cust_isn=81033011438"))
|
||||
.andRespond(withSuccess("{\"code\":200,\"data\":[{\"cust_isn\":\"81033011438\",\"loan_contract_history\":\"HT001\",\"loan_rate_history\":\"3.65\"}]}", MediaType.APPLICATION_JSON));
|
||||
|
||||
List<HistoryLoanContractVO> result = service.query(" 81033011438 ");
|
||||
|
||||
assertEquals(1, result.size());
|
||||
assertEquals("3.65", result.get(0).getLoanRateHistory());
|
||||
server.verify();
|
||||
}
|
||||
|
||||
@Test
|
||||
void shouldRejectBlankCustIsn() {
|
||||
LoanRateHistoryService service = new LoanRateHistoryService(new RestTemplate(), "http://mock/history");
|
||||
|
||||
ServiceException ex = assertThrows(ServiceException.class, () -> service.query(" "));
|
||||
|
||||
assertEquals("客户内码不能为空", ex.getMessage());
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Write failing controller/mock tests**
|
||||
|
||||
Mock endpoint tests must cover:
|
||||
|
||||
- Normal `cust_isn=81033011438` returns at least one row with `loan_rate_history`.
|
||||
- Fixed empty scenario, for example `cust_isn=EMPTY_HISTORY`, returns `data: []`.
|
||||
- Fixed empty-rate scenario, for example `cust_isn=EMPTY_RATE`, returns a row whose `loan_rate_history` is empty.
|
||||
|
||||
Customer-map mock tests must also cover browser-use fixed customer numbers:
|
||||
|
||||
- `cust_id=HISTORY_EMPTY` returns exactly one customer-map row with `cust_isn=EMPTY_HISTORY`.
|
||||
- `cust_id=HISTORY_EMPTY_RATE` returns exactly one customer-map row with `cust_isn=EMPTY_RATE`.
|
||||
|
||||
Controller test must verify `/loanPricing/workflow/history-contract?custIsn=81033011438` delegates to `LoanRateHistoryService.query("81033011438")`.
|
||||
|
||||
- [ ] **Step 3: Run tests to verify they fail**
|
||||
|
||||
```bash
|
||||
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanRateHistoryServiceTest,LoanRatePricingMockControllerHistoryContractTest,LoanPricingWorkflowControllerHistoryContractTest test
|
||||
```
|
||||
|
||||
Expected: FAIL because service/controller/mock do not exist.
|
||||
|
||||
- [ ] **Step 4: Implement `LoanRateHistoryService`**
|
||||
|
||||
```java
|
||||
@Service
|
||||
public class LoanRateHistoryService {
|
||||
private final RestTemplate restTemplate;
|
||||
|
||||
@Value("${loan-rate-history.url}")
|
||||
private String historyUrl;
|
||||
|
||||
public LoanRateHistoryService() {
|
||||
this(new RestTemplate());
|
||||
}
|
||||
|
||||
LoanRateHistoryService(RestTemplate restTemplate) {
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
LoanRateHistoryService(RestTemplate restTemplate, String historyUrl) {
|
||||
this.restTemplate = restTemplate;
|
||||
this.historyUrl = historyUrl;
|
||||
}
|
||||
|
||||
public List<HistoryLoanContractVO> query(String custIsn) {
|
||||
String normalizedCustIsn = StringUtils.trimWhitespace(custIsn);
|
||||
if (!StringUtils.hasText(normalizedCustIsn)) {
|
||||
throw new ServiceException("客户内码不能为空");
|
||||
}
|
||||
URI uri = UriComponentsBuilder.fromHttpUrl(historyUrl)
|
||||
.queryParam("cust_isn", normalizedCustIsn)
|
||||
.build()
|
||||
.encode()
|
||||
.toUri();
|
||||
HistoryLoanContractResponse response = restTemplate.getForObject(uri, HistoryLoanContractResponse.class);
|
||||
if (response == null) {
|
||||
throw new ServiceException("历史贷款合同接口无返回");
|
||||
}
|
||||
if (response.getCode() != null && response.getCode() != 200) {
|
||||
throw new ServiceException(StringUtils.hasText(response.getMsg()) ? response.getMsg() : "历史贷款合同查询失败");
|
||||
}
|
||||
return response.getData() == null ? Collections.emptyList() : response.getData();
|
||||
}
|
||||
|
||||
@Data
|
||||
@JsonIgnoreProperties(ignoreUnknown = true)
|
||||
static class HistoryLoanContractResponse {
|
||||
private Integer code;
|
||||
private String msg;
|
||||
private List<HistoryLoanContractVO> data;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Add controller endpoint**
|
||||
|
||||
Inject `LoanRateHistoryService` into `LoanPricingWorkflowController` and add:
|
||||
|
||||
```java
|
||||
@Operation(summary = "查询历史贷款合同")
|
||||
@GetMapping("/history-contract")
|
||||
public AjaxResult queryHistoryContract(@RequestParam("custIsn") String custIsn) {
|
||||
return success(loanRateHistoryService.query(custIsn));
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 6: Add mock endpoint**
|
||||
|
||||
Add to `LoanRatePricingMockController`:
|
||||
|
||||
```java
|
||||
@Anonymous
|
||||
@Operation(summary = "模拟历史贷款合同查询")
|
||||
@GetMapping("/history-contract")
|
||||
public AjaxResult queryHistoryContract(@RequestParam("cust_isn") String custIsn) {
|
||||
String normalizedCustIsn = StringUtils.trimWhitespace(custIsn);
|
||||
if (!StringUtils.hasText(normalizedCustIsn)) {
|
||||
throw new ServiceException("客户内码不能为空");
|
||||
}
|
||||
if ("EMPTY_HISTORY".equals(normalizedCustIsn)) {
|
||||
return success(Collections.emptyList());
|
||||
}
|
||||
List<HistoryLoanContractVO> records = new ArrayList<>();
|
||||
HistoryLoanContractVO record = new HistoryLoanContractVO();
|
||||
record.setCustIsn(normalizedCustIsn);
|
||||
record.setLoanContractHistory("HT" + normalizedCustIsn);
|
||||
record.setGuarTypeHistory("信用");
|
||||
record.setProductCodeHistory("P001");
|
||||
record.setLoanRateHistory("EMPTY_RATE".equals(normalizedCustIsn) ? "" : "3.65");
|
||||
record.setLoanAmountHistory("100000");
|
||||
record.setLoanSignDateHistory(LocalDate.now().minusMonths(6).toString());
|
||||
records.add(record);
|
||||
return success(records);
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 7: Add fixed customer-map mock scenarios**
|
||||
|
||||
Before the random-record loop in `randomCustomerMapRecords`, add:
|
||||
|
||||
```java
|
||||
if ("HISTORY_EMPTY".equals(normalizedCustId)) {
|
||||
CustomerMapRecordVO record = new CustomerMapRecordVO();
|
||||
record.setCustId(normalizedCustId);
|
||||
record.setCustIsn("EMPTY_HISTORY");
|
||||
record.setCustName(namePrefix + "空历史合同");
|
||||
record.setFaithDay("0");
|
||||
record.setBalanceAvg("0");
|
||||
record.setLoanCountHis("0");
|
||||
record.setLastLoanDate("");
|
||||
return Collections.singletonList(record);
|
||||
}
|
||||
if ("HISTORY_EMPTY_RATE".equals(normalizedCustId)) {
|
||||
CustomerMapRecordVO record = new CustomerMapRecordVO();
|
||||
record.setCustId(normalizedCustId);
|
||||
record.setCustIsn("EMPTY_RATE");
|
||||
record.setCustName(namePrefix + "空历史利率");
|
||||
record.setFaithDay("30");
|
||||
record.setBalanceAvg("10000");
|
||||
record.setLoanCountHis("1");
|
||||
record.setLastLoanDate(LocalDate.now().minusMonths(3).toString());
|
||||
return Collections.singletonList(record);
|
||||
}
|
||||
```
|
||||
|
||||
Import `java.util.Collections` if needed.
|
||||
|
||||
- [ ] **Step 8: Add profile config**
|
||||
|
||||
Use mock URLs in dev/uat:
|
||||
|
||||
```yaml
|
||||
loan-rate-history:
|
||||
url: http://localhost:63310/rate/pricing/mock/history-contract
|
||||
```
|
||||
|
||||
Use real URL in pro without `cust_isn=`:
|
||||
|
||||
```yaml
|
||||
loan-rate-history:
|
||||
url: http://552f7aff0acd4c09ac3b83dbfee57fa0.apigateway.res.dc-pdt-zj96596.com/shangyu_loan_rate_history?appCode=1a89fa84abda480ba93ed73fd01ffd07
|
||||
```
|
||||
|
||||
- [ ] **Step 9: Run proxy/mock tests**
|
||||
|
||||
```bash
|
||||
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanRateHistoryServiceTest,LoanRatePricingMockControllerHistoryContractTest,LoanPricingWorkflowControllerHistoryContractTest,LoanRatePricingMockControllerCustomerMapTest test
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 10: Commit**
|
||||
|
||||
```bash
|
||||
git add ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanRateHistoryService.java \
|
||||
ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowController.java \
|
||||
ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockController.java \
|
||||
ruoyi-admin/src/main/resources/application-dev.yml \
|
||||
ruoyi-admin/src/main/resources/application-uat.yml \
|
||||
ruoyi-admin/src/main/resources/application-pro.yml \
|
||||
ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanRateHistoryServiceTest.java \
|
||||
ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockControllerHistoryContractTest.java \
|
||||
ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowControllerHistoryContractTest.java \
|
||||
ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockControllerCustomerMapTest.java
|
||||
git commit -m "新增历史贷款合同查询接口"
|
||||
```
|
||||
|
||||
## Task 4: Persist SQL Schema Changes
|
||||
|
||||
**Files:**
|
||||
- Create: `sql/add_business_type_history_rate_20260429.sql`
|
||||
- Modify: `sql/loan_pricing_workflow.sql`
|
||||
- Modify: `sql/loan_pricing_schema_20260328.sql`
|
||||
- Modify: `sql/loan_pricing_prod_init_20260331.sql`
|
||||
|
||||
- [ ] **Step 1: Write migration SQL**
|
||||
|
||||
Create:
|
||||
|
||||
```sql
|
||||
-- 为利率定价流程添加业务种类和历史贷款利率字段
|
||||
ALTER TABLE `loan_pricing_workflow`
|
||||
ADD COLUMN `business_type` varchar(20) DEFAULT NULL COMMENT '业务种类' AFTER `loan_purpose`,
|
||||
ADD COLUMN `loan_rate_history` varchar(100) DEFAULT NULL COMMENT '历史贷款利率' AFTER `business_type`;
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Update schema SQL files**
|
||||
|
||||
In each `loan_pricing_workflow` create-table definition, add:
|
||||
|
||||
```sql
|
||||
`business_type` varchar(20) DEFAULT NULL COMMENT '业务种类',
|
||||
`loan_rate_history` varchar(100) DEFAULT NULL COMMENT '历史贷款利率',
|
||||
```
|
||||
|
||||
Place them after `loan_purpose`.
|
||||
|
||||
- [ ] **Step 3: Verify SQL text**
|
||||
|
||||
Run:
|
||||
|
||||
```bash
|
||||
rg -n "business_type|loan_rate_history" sql/add_business_type_history_rate_20260429.sql sql/loan_pricing_workflow.sql sql/loan_pricing_schema_20260328.sql sql/loan_pricing_prod_init_20260331.sql
|
||||
```
|
||||
|
||||
Expected: each file contains both fields in the workflow table context.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add sql/add_business_type_history_rate_20260429.sql \
|
||||
sql/loan_pricing_workflow.sql \
|
||||
sql/loan_pricing_schema_20260328.sql \
|
||||
sql/loan_pricing_prod_init_20260331.sql
|
||||
git commit -m "新增业务种类历史利率数据库字段"
|
||||
```
|
||||
|
||||
## Task 5: Verify Model Input Chain
|
||||
|
||||
**Files:**
|
||||
- Modify Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java`
|
||||
- Modify Test: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServiceTest.java`
|
||||
|
||||
- [ ] **Step 1: Add personal model assertion**
|
||||
|
||||
In `shouldInvokePersonalModelWithExpectedParams`, set:
|
||||
|
||||
```java
|
||||
workflow.setBusinessType("存量转贷");
|
||||
workflow.setLoanRateHistory("3.65");
|
||||
```
|
||||
|
||||
Add to the `argThat`:
|
||||
|
||||
```java
|
||||
&& Objects.equals("3.65", dto.getLoanRateHistory())
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Add corporate model assertion**
|
||||
|
||||
Add or update a corporate model invocation test in `LoanPricingModelServiceTest`:
|
||||
|
||||
```java
|
||||
workflow.setCustType("企业");
|
||||
workflow.setBusinessType("存量转贷");
|
||||
workflow.setLoanRateHistory("3.75");
|
||||
```
|
||||
|
||||
Verify:
|
||||
|
||||
```java
|
||||
verify(modelService).invokeCorporateModel(argThat((ModelInvokeDTO dto) ->
|
||||
Objects.equals("3.75", dto.getLoanRateHistory())));
|
||||
```
|
||||
|
||||
- [ ] **Step 3: Run model tests**
|
||||
|
||||
```bash
|
||||
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingModelServicePersonalParamsTest,LoanPricingModelServiceTest test
|
||||
```
|
||||
|
||||
Expected: PASS and no `businessType` field exists in `ModelInvokeDTO`.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServicePersonalParamsTest.java \
|
||||
ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingModelServiceTest.java
|
||||
git commit -m "验证历史利率模型入参链路"
|
||||
```
|
||||
|
||||
## Task 6: Backend Final Verification
|
||||
|
||||
**Files:**
|
||||
- No new files unless a test failure requires a focused fix.
|
||||
|
||||
- [ ] **Step 1: Run focused backend test suite**
|
||||
|
||||
```bash
|
||||
mvn -pl ruoyi-loan-pricing -am -Dtest=HistoryLoanContractVOTest,LoanRateHistoryServiceTest,LoanPricingWorkflowControllerHistoryContractTest,LoanRatePricingMockControllerHistoryContractTest,LoanPricingWorkflowServiceImplTest,LoanPricingModelServicePersonalParamsTest,LoanPricingModelServiceTest test
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 2: Run existing related customer-map tests**
|
||||
|
||||
```bash
|
||||
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingCustomerMapServiceTest,LoanPricingWorkflowControllerCustomerMapTest,LoanRatePricingMockControllerCustomerMapTest,CustomerMapRecordVOTest test
|
||||
```
|
||||
|
||||
Expected: PASS, confirming the new proxy pattern did not break customer-map behavior.
|
||||
|
||||
- [ ] **Step 3: Record backend implementation notes**
|
||||
|
||||
Append backend verification notes to a new or existing implementation report, for example:
|
||||
|
||||
```text
|
||||
doc/implementation-report-2026-04-29-business-type-history-rate.md
|
||||
```
|
||||
|
||||
Include commands run and whether browser-use verification remains for the frontend plan.
|
||||
|
||||
- [ ] **Step 4: Commit**
|
||||
|
||||
```bash
|
||||
git add doc/implementation-report-2026-04-29-business-type-history-rate.md
|
||||
git commit -m "记录业务种类历史利率后端验证"
|
||||
```
|
||||
@@ -0,0 +1,732 @@
|
||||
# Business Type History Rate Frontend Implementation Plan
|
||||
|
||||
> **For agentic workers:** Follow this repository's `AGENTS.md`: do not enable subagents or `using-superpowers` during implementation unless the user explicitly requests them. Steps use checkbox (`- [ ]`) syntax for tracking.
|
||||
|
||||
**Goal:** Add business type selection and historical loan-contract single-selection to the personal and corporate workflow creation UI, then verify the real page with browser-use.
|
||||
|
||||
**Architecture:** Reuse the current customer-map driven creation flow. Add one shared `HistoryContractSelector` component, one API function, fields and validation in both create dialogs, and detail display in both detail components. Final browser verification must use `browser-use:browser` with the in-app browser, not Playwright CLI or prototype pages.
|
||||
|
||||
**Tech Stack:** Vue 2, Element UI, RuoYi request wrapper, Node static tests, nvm Node 14.21.3, browser-use `iab` runtime for real page testing.
|
||||
|
||||
---
|
||||
|
||||
## File Structure
|
||||
|
||||
- Modify: `ruoyi-ui/src/api/loanPricing/workflow.js`
|
||||
- Add `queryHistoryContracts(custIsn)`.
|
||||
- Create: `ruoyi-ui/src/views/loanPricing/workflow/components/HistoryContractSelector.vue`
|
||||
- Shared modal for historical contract query results and single selection.
|
||||
- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue`
|
||||
- Add business type, history-rate display, query trigger, selection handling, and submit validation.
|
||||
- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue`
|
||||
- Same behavior for corporate creation.
|
||||
- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue`
|
||||
- Show business type and historical loan rate.
|
||||
- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/CorporateWorkflowDetail.vue`
|
||||
- Show business type and historical loan rate.
|
||||
- Modify: `ruoyi-ui/package.json`
|
||||
- Add `test:business-type-history-rate`.
|
||||
- Create: `ruoyi-ui/tests/business-type-history-rate.test.js`
|
||||
- Static coverage for API, selector, create dialogs, validation, clearing, and detail display.
|
||||
- Modify: `doc/implementation-report-2026-04-29-business-type-history-rate.md`
|
||||
- Add frontend and browser-use verification notes after execution.
|
||||
|
||||
## Task 1: Add Frontend API and Static Test Skeleton
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/api/loanPricing/workflow.js`
|
||||
- Modify: `ruoyi-ui/package.json`
|
||||
- Create: `ruoyi-ui/tests/business-type-history-rate.test.js`
|
||||
|
||||
- [ ] **Step 1: Write failing API assertions**
|
||||
|
||||
Create `ruoyi-ui/tests/business-type-history-rate.test.js`:
|
||||
|
||||
```js
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
const assert = require('assert')
|
||||
|
||||
function read(relativePath) {
|
||||
return fs.readFileSync(path.join(__dirname, '..', relativePath), 'utf8')
|
||||
}
|
||||
|
||||
const workflowApi = read('src/api/loanPricing/workflow.js')
|
||||
|
||||
assert(
|
||||
workflowApi.includes('export function queryHistoryContracts') &&
|
||||
workflowApi.includes("url: '/loanPricing/workflow/history-contract'") &&
|
||||
workflowApi.includes('params: { custIsn: custIsn }'),
|
||||
'缺少历史贷款合同查询 API'
|
||||
)
|
||||
|
||||
console.log('business type history rate assertions passed')
|
||||
```
|
||||
|
||||
Add package script:
|
||||
|
||||
```json
|
||||
"test:business-type-history-rate": "node tests/business-type-history-rate.test.js"
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
```bash
|
||||
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:business-type-history-rate'
|
||||
```
|
||||
|
||||
Expected: FAIL because the API function does not exist.
|
||||
|
||||
- [ ] **Step 3: Implement API function**
|
||||
|
||||
Append to `workflow.js`:
|
||||
|
||||
```js
|
||||
// 查询历史贷款合同
|
||||
export function queryHistoryContracts(custIsn) {
|
||||
return request({
|
||||
url: '/loanPricing/workflow/history-contract',
|
||||
method: 'get',
|
||||
params: { custIsn: custIsn }
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run API test**
|
||||
|
||||
```bash
|
||||
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:business-type-history-rate'
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/api/loanPricing/workflow.js ruoyi-ui/package.json ruoyi-ui/tests/business-type-history-rate.test.js
|
||||
git commit -m "新增历史贷款合同前端接口"
|
||||
```
|
||||
|
||||
## Task 2: Create Historical Contract Selector
|
||||
|
||||
**Files:**
|
||||
- Create: `ruoyi-ui/src/views/loanPricing/workflow/components/HistoryContractSelector.vue`
|
||||
- Modify: `ruoyi-ui/tests/business-type-history-rate.test.js`
|
||||
|
||||
- [ ] **Step 1: Extend failing selector assertions**
|
||||
|
||||
Add:
|
||||
|
||||
```js
|
||||
const historySelector = read('src/views/loanPricing/workflow/components/HistoryContractSelector.vue')
|
||||
|
||||
assert(
|
||||
historySelector.includes('title="历史贷款合同选择"') &&
|
||||
historySelector.includes('width="80%"') &&
|
||||
historySelector.includes(':data="contracts"'),
|
||||
'历史贷款合同选择弹窗缺少标题、宽度或表格数据'
|
||||
)
|
||||
|
||||
;[
|
||||
'cust_isn',
|
||||
'loan_contract_history',
|
||||
'guar_type_history',
|
||||
'product_code_history',
|
||||
'loan_rate_history',
|
||||
'loan_amount_history',
|
||||
'loan_sign_date_history'
|
||||
].forEach((field) => {
|
||||
assert(historySelector.includes(`prop="${field}"`) || historySelector.includes(`row.${field}`), `历史合同弹窗缺少字段 ${field}`)
|
||||
})
|
||||
|
||||
assert(
|
||||
historySelector.includes('type="radio"') &&
|
||||
historySelector.includes('selectedContract') &&
|
||||
historySelector.includes("this.$emit('select', this.selectedContract)") &&
|
||||
historySelector.includes('请选择历史贷款合同'),
|
||||
'历史合同弹窗缺少单选、确定选择或未选提示'
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
```bash
|
||||
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:business-type-history-rate'
|
||||
```
|
||||
|
||||
Expected: FAIL because the component does not exist.
|
||||
|
||||
- [ ] **Step 3: Implement `HistoryContractSelector.vue`**
|
||||
|
||||
Use a small presentational component. It receives already-loaded `contracts` and does not call the API itself.
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<el-dialog title="历史贷款合同选择" :visible.sync="dialogVisible" width="80%" append-to-body
|
||||
:close-on-click-modal="false" @close="handleClose">
|
||||
<el-table :data="contracts" v-loading="loading" @row-click="handleRowClick">
|
||||
<el-table-column label="选择" align="center" width="70">
|
||||
<template slot-scope="scope">
|
||||
<el-radio v-model="selectedContract" :label="scope.row"> </el-radio>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="客户内码" prop="cust_isn" align="center" :show-overflow-tooltip="true"/>
|
||||
<el-table-column label="历史贷款合同号" prop="loan_contract_history" align="center" :show-overflow-tooltip="true"/>
|
||||
<el-table-column label="历史贷款担保方式" prop="guar_type_history" align="center"/>
|
||||
<el-table-column label="历史贷款产品代码" prop="product_code_history" align="center"/>
|
||||
<el-table-column label="历史贷款利率" prop="loan_rate_history" align="center"/>
|
||||
<el-table-column label="历史贷款金额" prop="loan_amount_history" align="center"/>
|
||||
<el-table-column label="历史贷款签订时间" prop="loan_sign_date_history" align="center" width="150"/>
|
||||
</el-table>
|
||||
<div slot="footer" class="dialog-footer">
|
||||
<el-button type="primary" @click="confirmSelect">确 定</el-button>
|
||||
<el-button @click="dialogVisible = false">取 消</el-button>
|
||||
</div>
|
||||
</el-dialog>
|
||||
</template>
|
||||
```
|
||||
|
||||
Script:
|
||||
|
||||
```js
|
||||
export default {
|
||||
name: "HistoryContractSelector",
|
||||
props: {
|
||||
visible: { type: Boolean, default: false },
|
||||
contracts: { type: Array, default: () => [] },
|
||||
loading: { type: Boolean, default: false }
|
||||
},
|
||||
data() {
|
||||
return { selectedContract: null }
|
||||
},
|
||||
computed: {
|
||||
dialogVisible: {
|
||||
get() { return this.visible },
|
||||
set(val) { this.$emit('update:visible', val) }
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleRowClick(row) {
|
||||
this.selectedContract = row
|
||||
},
|
||||
confirmSelect() {
|
||||
if (!this.selectedContract) {
|
||||
this.$modal.msgWarning("请选择历史贷款合同")
|
||||
return
|
||||
}
|
||||
if (!this.selectedContract.loan_rate_history) {
|
||||
this.$modal.msgWarning("历史贷款利率不能为空")
|
||||
return
|
||||
}
|
||||
this.$emit('select', this.selectedContract)
|
||||
this.dialogVisible = false
|
||||
},
|
||||
handleClose() {
|
||||
this.selectedContract = null
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Run selector test**
|
||||
|
||||
```bash
|
||||
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:business-type-history-rate'
|
||||
```
|
||||
|
||||
Expected: PASS for selector assertions.
|
||||
|
||||
- [ ] **Step 5: Commit**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/loanPricing/workflow/components/HistoryContractSelector.vue \
|
||||
ruoyi-ui/tests/business-type-history-rate.test.js
|
||||
git commit -m "新增历史贷款合同选择弹窗"
|
||||
```
|
||||
|
||||
## Task 3: Add Personal Create Dialog Behavior
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue`
|
||||
- Modify: `ruoyi-ui/tests/business-type-history-rate.test.js`
|
||||
|
||||
- [ ] **Step 1: Extend failing personal dialog assertions**
|
||||
|
||||
Add:
|
||||
|
||||
```js
|
||||
const personalCreate = read('src/views/loanPricing/workflow/components/PersonalCreateDialog.vue')
|
||||
|
||||
assert(
|
||||
personalCreate.includes('label="业务种类"') &&
|
||||
personalCreate.includes('v-model="form.businessType"') &&
|
||||
personalCreate.includes('新客') &&
|
||||
personalCreate.includes('存量新增') &&
|
||||
personalCreate.includes('存量转贷'),
|
||||
'个人新增弹窗缺少业务种类选择'
|
||||
)
|
||||
|
||||
assert(
|
||||
personalCreate.includes('label="历史贷款利率"') &&
|
||||
personalCreate.includes('v-model="form.loanRateHistory"') &&
|
||||
personalCreate.includes(':readonly="true"'),
|
||||
'个人新增弹窗缺少只读历史贷款利率'
|
||||
)
|
||||
|
||||
assert(
|
||||
personalCreate.includes('queryHistoryContracts') &&
|
||||
personalCreate.includes('HistoryContractSelector') &&
|
||||
personalCreate.includes('handleBusinessTypeChange') &&
|
||||
personalCreate.includes('handleHistoryContractSelect'),
|
||||
'个人新增弹窗缺少历史合同查询和选择逻辑'
|
||||
)
|
||||
|
||||
assert(
|
||||
personalCreate.includes('请选择历史贷款合同') &&
|
||||
personalCreate.includes('未查询到历史贷款合同') &&
|
||||
personalCreate.includes('delete data.loanRateHistory'),
|
||||
'个人新增弹窗缺少存量转贷拦截、空列表提示或非存量转贷清理'
|
||||
)
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
```bash
|
||||
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:business-type-history-rate'
|
||||
```
|
||||
|
||||
Expected: FAIL.
|
||||
|
||||
- [ ] **Step 3: Implement personal form fields**
|
||||
|
||||
In the loan information area, add:
|
||||
|
||||
```vue
|
||||
<el-row>
|
||||
<el-col :span="12">
|
||||
<el-form-item label="业务种类" prop="businessType">
|
||||
<el-select v-model="form.businessType" placeholder="请选择业务种类" style="width: 100%" @change="handleBusinessTypeChange">
|
||||
<el-option label="新客" value="新客"/>
|
||||
<el-option label="存量新增" value="存量新增"/>
|
||||
<el-option label="存量转贷" value="存量转贷"/>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12" v-if="isStockTransfer">
|
||||
<el-form-item label="历史贷款利率" prop="loanRateHistory">
|
||||
<el-input v-model="form.loanRateHistory" placeholder="请选择历史贷款合同" :readonly="true"/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
```
|
||||
|
||||
Add selector under the form:
|
||||
|
||||
```vue
|
||||
<history-contract-selector
|
||||
:visible.sync="showHistorySelector"
|
||||
:contracts="historyContracts"
|
||||
:loading="historyLoading"
|
||||
@select="handleHistoryContractSelect"
|
||||
/>
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Implement personal script**
|
||||
|
||||
Import:
|
||||
|
||||
```js
|
||||
import {createPersonalWorkflow, queryHistoryContracts} from "@/api/loanPricing/workflow"
|
||||
import HistoryContractSelector from "./HistoryContractSelector"
|
||||
```
|
||||
|
||||
Register component, then add state:
|
||||
|
||||
```js
|
||||
businessTypeOptions: ['新客', '存量新增', '存量转贷'],
|
||||
showHistorySelector: false,
|
||||
historyLoading: false,
|
||||
historyContracts: [],
|
||||
selectedHistoryContract: null,
|
||||
```
|
||||
|
||||
Add form fields in initial data and `reset()`:
|
||||
|
||||
```js
|
||||
businessType: undefined,
|
||||
loanRateHistory: undefined,
|
||||
```
|
||||
|
||||
Add rules:
|
||||
|
||||
```js
|
||||
businessType: [
|
||||
{required: true, message: "请选择业务种类", trigger: "change"}
|
||||
],
|
||||
loanRateHistory: [
|
||||
{required: true, message: "请选择历史贷款合同", trigger: "change"}
|
||||
]
|
||||
```
|
||||
|
||||
Add computed:
|
||||
|
||||
```js
|
||||
isStockTransfer() {
|
||||
return this.form.businessType === '存量转贷'
|
||||
}
|
||||
```
|
||||
|
||||
Add methods:
|
||||
|
||||
```js
|
||||
handleBusinessTypeChange(value) {
|
||||
this.clearHistoryContract()
|
||||
if (value === '存量转贷') {
|
||||
this.queryHistoryContracts()
|
||||
}
|
||||
},
|
||||
queryHistoryContracts() {
|
||||
if (!this.form.custIsn) {
|
||||
this.$modal.msgWarning("客户内码不能为空")
|
||||
return
|
||||
}
|
||||
this.historyLoading = true
|
||||
queryHistoryContracts(this.form.custIsn).then(response => {
|
||||
this.historyContracts = response.data || []
|
||||
if (this.historyContracts.length === 0) {
|
||||
this.$modal.msgWarning("未查询到历史贷款合同")
|
||||
return
|
||||
}
|
||||
this.showHistorySelector = true
|
||||
}).finally(() => {
|
||||
this.historyLoading = false
|
||||
})
|
||||
},
|
||||
handleHistoryContractSelect(row) {
|
||||
if (!row.loan_rate_history) {
|
||||
this.$modal.msgWarning("历史贷款利率不能为空")
|
||||
return
|
||||
}
|
||||
this.selectedHistoryContract = row
|
||||
this.form.loanRateHistory = row.loan_rate_history
|
||||
},
|
||||
clearHistoryContract() {
|
||||
this.selectedHistoryContract = null
|
||||
this.form.loanRateHistory = undefined
|
||||
this.historyContracts = []
|
||||
}
|
||||
```
|
||||
|
||||
In `submitForm`, before setting `submitting`:
|
||||
|
||||
```js
|
||||
if (this.isStockTransfer && !this.form.loanRateHistory) {
|
||||
this.$modal.msgWarning("请选择历史贷款合同")
|
||||
return
|
||||
}
|
||||
```
|
||||
|
||||
When building `data`, remove historical rate for non-stock-transfer:
|
||||
|
||||
```js
|
||||
if (!this.isStockTransfer) {
|
||||
delete data.loanRateHistory
|
||||
}
|
||||
```
|
||||
|
||||
- [ ] **Step 5: Run personal static test**
|
||||
|
||||
```bash
|
||||
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:business-type-history-rate'
|
||||
```
|
||||
|
||||
Expected: PASS for personal assertions.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/loanPricing/workflow/components/PersonalCreateDialog.vue \
|
||||
ruoyi-ui/tests/business-type-history-rate.test.js
|
||||
git commit -m "个人新增支持业务种类和历史利率"
|
||||
```
|
||||
|
||||
## Task 4: Add Corporate Create Dialog Behavior
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue`
|
||||
- Modify: `ruoyi-ui/tests/business-type-history-rate.test.js`
|
||||
|
||||
- [ ] **Step 1: Extend failing corporate assertions**
|
||||
|
||||
Mirror the personal assertions for `CorporateCreateDialog.vue`, replacing assertion messages with “企业新增弹窗...”.
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
```bash
|
||||
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:business-type-history-rate'
|
||||
```
|
||||
|
||||
Expected: FAIL.
|
||||
|
||||
- [ ] **Step 3: Implement corporate fields and selector**
|
||||
|
||||
Apply the same form, selector, state, rules, computed, and methods from Task 3, but keep existing corporate-specific fields and submission conversion intact.
|
||||
|
||||
- [ ] **Step 4: Keep collateral and green/trade behavior untouched**
|
||||
|
||||
When editing submit payload, keep:
|
||||
|
||||
```js
|
||||
isGreenLoan: this.form.isGreenLoan ? '1' : '0',
|
||||
isTradeBuildEnt: this.form.isTradeBuildEnt ? '1' : '0'
|
||||
```
|
||||
|
||||
Do not reintroduce `repayMethod` UI.
|
||||
|
||||
- [ ] **Step 5: Run corporate static test**
|
||||
|
||||
```bash
|
||||
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:business-type-history-rate'
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/loanPricing/workflow/components/CorporateCreateDialog.vue \
|
||||
ruoyi-ui/tests/business-type-history-rate.test.js
|
||||
git commit -m "企业新增支持业务种类和历史利率"
|
||||
```
|
||||
|
||||
## Task 5: Add Detail Display
|
||||
|
||||
**Files:**
|
||||
- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue`
|
||||
- Modify: `ruoyi-ui/src/views/loanPricing/workflow/components/CorporateWorkflowDetail.vue`
|
||||
- Modify: `ruoyi-ui/tests/business-type-history-rate.test.js`
|
||||
|
||||
- [ ] **Step 1: Add failing detail assertions**
|
||||
|
||||
```js
|
||||
const personalDetail = read('src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue')
|
||||
const corporateDetail = read('src/views/loanPricing/workflow/components/CorporateWorkflowDetail.vue')
|
||||
|
||||
;[
|
||||
['个人详情', personalDetail],
|
||||
['企业详情', corporateDetail]
|
||||
].forEach(([name, source]) => {
|
||||
assert(source.includes('label="业务种类"') && source.includes('detailData.businessType'), `${name} 缺少业务种类展示`)
|
||||
assert(source.includes('label="历史贷款利率"') && source.includes('detailData.loanRateHistory'), `${name} 缺少历史贷款利率展示`)
|
||||
})
|
||||
```
|
||||
|
||||
- [ ] **Step 2: Run test to verify it fails**
|
||||
|
||||
```bash
|
||||
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:business-type-history-rate'
|
||||
```
|
||||
|
||||
Expected: FAIL.
|
||||
|
||||
- [ ] **Step 3: Add personal detail fields**
|
||||
|
||||
In personal “业务信息” descriptions, add:
|
||||
|
||||
```vue
|
||||
<el-descriptions-item label="业务种类">{{ detailData.businessType || '-' }}</el-descriptions-item>
|
||||
<el-descriptions-item label="历史贷款利率">{{ detailData.loanRateHistory || '-' }}</el-descriptions-item>
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Add corporate detail fields**
|
||||
|
||||
Add the same two fields to corporate “业务信息” descriptions.
|
||||
|
||||
- [ ] **Step 5: Run detail test**
|
||||
|
||||
```bash
|
||||
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:business-type-history-rate'
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 6: Commit**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ui/src/views/loanPricing/workflow/components/PersonalWorkflowDetail.vue \
|
||||
ruoyi-ui/src/views/loanPricing/workflow/components/CorporateWorkflowDetail.vue \
|
||||
ruoyi-ui/tests/business-type-history-rate.test.js
|
||||
git commit -m "详情展示业务种类和历史利率"
|
||||
```
|
||||
|
||||
## Task 6: Frontend Static Verification
|
||||
|
||||
**Files:**
|
||||
- No new files unless tests expose a focused defect.
|
||||
|
||||
- [ ] **Step 1: Run new focused test**
|
||||
|
||||
```bash
|
||||
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run test:business-type-history-rate'
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 2: Run related existing frontend tests**
|
||||
|
||||
```bash
|
||||
zsh -lic 'nvm use 14.21.3 >/dev/null && node ruoyi-ui/tests/customer-map-selection.test.js && npm --prefix ruoyi-ui run test:personal-create-input-params && npm --prefix ruoyi-ui run test:corporate-create-input-params'
|
||||
```
|
||||
|
||||
Expected: PASS.
|
||||
|
||||
- [ ] **Step 3: Build frontend**
|
||||
|
||||
```bash
|
||||
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run build:prod'
|
||||
```
|
||||
|
||||
Expected: build succeeds.
|
||||
|
||||
- [ ] **Step 4: Commit only if fixes were required**
|
||||
|
||||
```bash
|
||||
git status --short
|
||||
```
|
||||
|
||||
Expected: no unexpected frontend changes beyond this plan.
|
||||
|
||||
## Task 7: Real Page Verification With browser-use
|
||||
|
||||
**Files:**
|
||||
- Modify: `doc/implementation-report-2026-04-29-business-type-history-rate.md`
|
||||
|
||||
- [ ] **Step 1: Start backend with latest code**
|
||||
|
||||
Start or restart the backend on port `63310` using the repo's existing scripts or Maven command. Confirm the backend has loaded the latest backend code before browser testing.
|
||||
|
||||
Example:
|
||||
|
||||
```bash
|
||||
bin/restart_java_backend_test.sh
|
||||
```
|
||||
|
||||
Expected: backend listens on `63310`.
|
||||
|
||||
- [ ] **Step 2: Start frontend with Node 14**
|
||||
|
||||
Use nvm-managed Node:
|
||||
|
||||
```bash
|
||||
zsh -lic 'nvm use 14.21.3 >/dev/null && npm --prefix ruoyi-ui run dev -- --port 8080'
|
||||
```
|
||||
|
||||
Expected: frontend dev server is available at `http://localhost:8080`.
|
||||
|
||||
- [ ] **Step 3: Initialize browser-use**
|
||||
|
||||
Use the Node REPL `js` tool and the in-app browser backend. First browser cell:
|
||||
|
||||
```js
|
||||
if (!globalThis.agent) {
|
||||
const { setupAtlasRuntime } = await import("/Users/wkc/.codex/plugins/cache/openai-bundled/browser-use/0.1.0-alpha1/scripts/browser-client.mjs");
|
||||
const backend = "iab";
|
||||
await setupAtlasRuntime({ globals: globalThis, backend });
|
||||
}
|
||||
await agent.browser.nameSession("🔎 利率定价历史利率测试");
|
||||
if (typeof tab === "undefined") {
|
||||
globalThis.tab = await agent.browser.tabs.new();
|
||||
}
|
||||
await tab.goto("http://localhost:8080");
|
||||
await tab.playwright.waitForLoadState({ state: "domcontentloaded", timeoutMs: 10000 });
|
||||
console.log(await tab.title());
|
||||
```
|
||||
|
||||
- [ ] **Step 4: Log in on the real page**
|
||||
|
||||
Use the real login page, not a prototype. If the default test login path is available, use the project-supported login route. Otherwise log in through the visible login form with the configured test account.
|
||||
|
||||
After login, verify that the actual workflow list is visible.
|
||||
|
||||
- [ ] **Step 5: Verify personal new customer path**
|
||||
|
||||
In the browser:
|
||||
|
||||
1. Open 利率定价流程 list.
|
||||
2. Click 新增.
|
||||
3. Select 个人客户.
|
||||
4. Query a customer number and choose a customer internal code.
|
||||
5. In the personal create dialog, choose 业务种类 = 新客.
|
||||
6. Confirm no historical contract selector appears.
|
||||
7. Fill required fields and submit.
|
||||
8. Open detail and verify 业务种类 displays 新客 and 历史贷款利率 is empty/`-`.
|
||||
|
||||
- [ ] **Step 6: Verify personal existing-new path**
|
||||
|
||||
Repeat personal creation with 业务种类 = 存量新增. Confirm no historical contract selector appears, submit succeeds, and detail displays 存量新增.
|
||||
|
||||
- [ ] **Step 7: Verify personal stock-transfer path**
|
||||
|
||||
Repeat personal creation with 业务种类 = 存量转贷. Confirm:
|
||||
|
||||
1. Historical contract selector opens.
|
||||
2. All 7 columns are visible.
|
||||
3. A single radio selection is possible.
|
||||
4. Selecting a row fills 历史贷款利率.
|
||||
5. Submit succeeds.
|
||||
6. Detail displays 业务种类 = 存量转贷 and the chosen 历史贷款利率.
|
||||
|
||||
- [ ] **Step 8: Verify corporate three paths**
|
||||
|
||||
Repeat Steps 5-7 for 企业客户: 新客、存量新增、存量转贷.
|
||||
|
||||
- [ ] **Step 9: Verify blocked stock-transfer submission**
|
||||
|
||||
Use a stock-transfer flow and attempt to submit without selecting a historical contract. Confirm the page blocks submission and shows “请选择历史贷款合同”.
|
||||
|
||||
- [ ] **Step 10: Verify empty history scenario**
|
||||
|
||||
Use the fixed customer-map mock customer number `HISTORY_EMPTY`. In the customer number selector, query `HISTORY_EMPTY`, select the returned row with `cust_isn=EMPTY_HISTORY`, then choose 业务种类 = 存量转贷. Confirm “未查询到历史贷款合同” appears and submission remains blocked.
|
||||
|
||||
- [ ] **Step 11: Verify empty historical-rate scenario**
|
||||
|
||||
Use the fixed customer-map mock customer number `HISTORY_EMPTY_RATE`. In the customer number selector, query `HISTORY_EMPTY_RATE`, select the returned row with `cust_isn=EMPTY_RATE`, then choose 业务种类 = 存量转贷. Confirm the history selector opens with a row whose `loan_rate_history` is empty; selecting it and clicking 确定 must show “历史贷款利率不能为空”, must not fill the create form's historical loan-rate field, and submission must remain blocked.
|
||||
|
||||
- [ ] **Step 12: Capture evidence**
|
||||
|
||||
Capture screenshots or DOM snapshots for:
|
||||
|
||||
- Personal stock-transfer selector with all 7 columns.
|
||||
- Corporate stock-transfer selector with all 7 columns.
|
||||
- Detail page showing 业务种类 and 历史贷款利率.
|
||||
- Blocked submit warning.
|
||||
- Empty-history warning from `HISTORY_EMPTY`.
|
||||
- Empty-rate warning from `HISTORY_EMPTY_RATE`.
|
||||
|
||||
Use browser-use screenshots through `await display(await tab.playwright.screenshot({ fullPage: false }))` when visual confirmation matters.
|
||||
|
||||
- [ ] **Step 13: Stop test processes**
|
||||
|
||||
Stop only the backend/frontend processes started for this test. Do not kill unrelated user processes.
|
||||
|
||||
- [ ] **Step 14: Record verification**
|
||||
|
||||
Update:
|
||||
|
||||
```text
|
||||
doc/implementation-report-2026-04-29-business-type-history-rate.md
|
||||
```
|
||||
|
||||
Include:
|
||||
|
||||
- Static test commands and results.
|
||||
- Backend/frontend server commands.
|
||||
- browser-use URL and scenarios covered.
|
||||
- Confirmation that test processes were stopped.
|
||||
|
||||
- [ ] **Step 15: Commit verification notes**
|
||||
|
||||
```bash
|
||||
git add doc/implementation-report-2026-04-29-business-type-history-rate.md
|
||||
git commit -m "记录业务种类历史利率页面验证"
|
||||
```
|
||||
Reference in New Issue
Block a user