# Customer Map Selection Backend Implementation Plan

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

**Goal:** Add backend customer-id-to-customer-internal-code query APIs for personal and corporate workflow creation, backed by local mock interfaces and profile configuration.

**Architecture:** Keep workflow creation APIs unchanged. Add a small customer-map service that reads separate personal/corporate URLs from configuration, calls them with GET `cust_id`, and returns mapping records with underscore JSON field names through the existing RuoYi `AjaxResult` response convention. Add mock endpoints under the existing mock controller so all active profiles can point to local mock URLs first.

**Tech Stack:** Spring Boot, RuoYi `AjaxResult`, Spring `RestTemplate`, Jackson `@JsonProperty`, JUnit 5, Mockito, Maven.

---

## File Structure

- Create: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/vo/CustomerMapRecordVO.java`
  - Holds one customer mapping record.
  - Uses Java camelCase fields internally and `@JsonProperty` to serialize/deserialize underscore field names.
- Create: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapService.java`
  - Owns config URL selection, GET forwarding, parameter validation, and response parsing.
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowController.java`
  - Adds authenticated business endpoints used by the frontend.
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockController.java`
  - Adds anonymous local mock endpoints.
- Modify: `ruoyi-admin/src/main/resources/application-dev.yml`
  - Adds `customer-map` mock URL config.
- Modify: `ruoyi-admin/src/main/resources/application-uat.yml`
  - Adds `customer-map` mock URL config.
- Modify: `ruoyi-admin/src/main/resources/application-pro.yml`
  - Adds `customer-map` mock URL config for the production profile, currently pointing to local mock as requested.
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/domain/vo/CustomerMapRecordVOTest.java`
  - Verifies JSON field names stay underscored.
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapServiceTest.java`
  - Verifies personal/corporate URL routing, `cust_id` forwarding, response parsing, and missing customer-id errors.
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockControllerCustomerMapTest.java`
  - Verifies mock endpoints return one or more underscore-field records.
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowControllerCustomerMapTest.java`
  - Verifies business controller methods delegate to the service and preserve the result records.

## Task 1: Add Customer Map Record VO

**Files:**
- Create: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/vo/CustomerMapRecordVO.java`
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/domain/vo/CustomerMapRecordVOTest.java`

- [ ] **Step 1: Write the failing serialization test**

Create `CustomerMapRecordVOTest`:

```java
package com.ruoyi.loanpricing.domain.vo;

import static org.junit.jupiter.api.Assertions.assertTrue;

import com.fasterxml.jackson.databind.ObjectMapper;
import org.junit.jupiter.api.Test;

class CustomerMapRecordVOTest
{
    @Test
    void shouldSerializeCustomerMapFieldsWithUnderscoreNames() throws Exception
    {
        CustomerMapRecordVO record = new CustomerMapRecordVO();
        record.setCustId("101330419198206033217");
        record.setCustIsn("81033011438");
        record.setCustName("张三");
        record.setFaithDay("20");
        record.setBalanceAvg("300000");
        record.setLoanCountHis("2");
        record.setLastLoanDate("2025-12-01");

        String json = new ObjectMapper().writeValueAsString(record);

        assertTrue(json.contains("\"cust_id\""));
        assertTrue(json.contains("\"cust_isn\""));
        assertTrue(json.contains("\"cust_name\""));
        assertTrue(json.contains("\"faith_day\""));
        assertTrue(json.contains("\"balance_avg\""));
        assertTrue(json.contains("\"loan_count_his\""));
        assertTrue(json.contains("\"last_loan_date\""));
    }
}
```

- [ ] **Step 2: Run the test to verify it fails**

Run:

```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=CustomerMapRecordVOTest -Dsurefire.failIfNoSpecifiedTests=false test
```

Expected: FAIL because `CustomerMapRecordVO` does not exist.

- [ ] **Step 3: Implement the VO**

Create `CustomerMapRecordVO.java`:

```java
package com.ruoyi.loanpricing.domain.vo;

import com.fasterxml.jackson.annotation.JsonProperty;
import java.io.Serializable;
import lombok.Data;

@Data
public class CustomerMapRecordVO implements Serializable
{
    private static final long serialVersionUID = 1L;

    @JsonProperty("cust_id")
    private String custId;

    @JsonProperty("cust_isn")
    private String custIsn;

    @JsonProperty("cust_name")
    private String custName;

    @JsonProperty("faith_day")
    private String faithDay;

    @JsonProperty("balance_avg")
    private String balanceAvg;

    @JsonProperty("loan_count_his")
    private String loanCountHis;

    @JsonProperty("last_loan_date")
    private String lastLoanDate;
}
```

- [ ] **Step 4: Run the test to verify it passes**

Run:

```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=CustomerMapRecordVOTest -Dsurefire.failIfNoSpecifiedTests=false test
```

Expected: PASS.

## Task 2: Add Customer Map Service

**Files:**
- Create: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapService.java`
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapServiceTest.java`

- [ ] **Step 1: Write service tests first**

Create tests that use `MockRestServiceServer` and the package-private test constructor:

```java
package com.ruoyi.loanpricing.service;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.springframework.test.web.client.match.MockRestRequestMatchers.requestTo;
import static org.springframework.test.web.client.response.MockRestResponseCreators.withSuccess;

import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.loanpricing.domain.vo.CustomerMapRecordVO;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.springframework.http.MediaType;
import org.springframework.web.client.RestTemplate;
import org.springframework.test.web.client.MockRestServiceServer;

class LoanPricingCustomerMapServiceTest
{
    @Test
    void shouldQueryPersonalCustomerMapWithCustIdParam()
    {
        RestTemplate restTemplate = new RestTemplate();
        MockRestServiceServer server = MockRestServiceServer.createServer(restTemplate);
        LoanPricingCustomerMapService service = new LoanPricingCustomerMapService(
                restTemplate,
                "http://mock/personal",
                "http://mock/corporate");

        server.expect(requestTo("http://mock/personal?cust_id=P001"))
                .andRespond(withSuccess("{\"code\":200,\"msg\":\"操作成功\",\"data\":[{\"cust_id\":\"P001\",\"cust_isn\":\"81033011438\",\"cust_name\":\"张三\"}]}",
                        MediaType.APPLICATION_JSON));

        List<CustomerMapRecordVO> result = service.queryPersonal("P001");

        assertEquals(1, result.size());
        assertEquals("81033011438", result.get(0).getCustIsn());
        assertEquals("张三", result.get(0).getCustName());
        server.verify();
    }

    @Test
    void shouldQueryCorporateCustomerMapWithCustIdParam()
    {
        RestTemplate restTemplate = new RestTemplate();
        MockRestServiceServer server = MockRestServiceServer.createServer(restTemplate);
        LoanPricingCustomerMapService service = new LoanPricingCustomerMapService(
                restTemplate,
                "http://mock/personal",
                "http://mock/corporate");

        server.expect(requestTo("http://mock/corporate?cust_id=C001"))
                .andRespond(withSuccess("{\"code\":200,\"data\":[{\"cust_id\":\"C001\",\"cust_isn\":\"82002469287\",\"cust_name\":\"测试企业\"}]}",
                        MediaType.APPLICATION_JSON));

        List<CustomerMapRecordVO> result = service.queryCorporate("C001");

        assertEquals("82002469287", result.get(0).getCustIsn());
        assertEquals("测试企业", result.get(0).getCustName());
        server.verify();
    }

    @Test
    void shouldRejectBlankCustId()
    {
        LoanPricingCustomerMapService service = new LoanPricingCustomerMapService(
                new RestTemplate(),
                "http://mock/personal",
                "http://mock/corporate");

        ServiceException ex = assertThrows(ServiceException.class, () -> service.queryPersonal(" "));

        assertEquals("客户号不能为空", ex.getMessage());
    }
}
```

- [ ] **Step 2: Run the tests to verify they fail**

Run:

```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingCustomerMapServiceTest -Dsurefire.failIfNoSpecifiedTests=false test
```

Expected: FAIL because `LoanPricingCustomerMapService` does not exist.

- [ ] **Step 3: Implement the service**

Create the service with a default Spring constructor and a package-private test constructor:

```java
package com.ruoyi.loanpricing.service;

import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.loanpricing.domain.vo.CustomerMapRecordVO;
import java.net.URI;
import java.util.Collections;
import java.util.List;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.util.UriComponentsBuilder;

@Service
public class LoanPricingCustomerMapService
{
    private final RestTemplate restTemplate;

    @Value("${customer-map.personal-url}")
    private String personalUrl;

    @Value("${customer-map.corporate-url}")
    private String corporateUrl;

    public LoanPricingCustomerMapService()
    {
        this(new RestTemplate());
    }

    LoanPricingCustomerMapService(RestTemplate restTemplate)
    {
        this.restTemplate = restTemplate;
    }

    LoanPricingCustomerMapService(RestTemplate restTemplate, String personalUrl, String corporateUrl)
    {
        this.restTemplate = restTemplate;
        this.personalUrl = personalUrl;
        this.corporateUrl = corporateUrl;
    }

    public List<CustomerMapRecordVO> queryPersonal(String custId)
    {
        return query(personalUrl, custId);
    }

    public List<CustomerMapRecordVO> queryCorporate(String custId)
    {
        return query(corporateUrl, custId);
    }

    private List<CustomerMapRecordVO> query(String url, String custId)
    {
        if (!StringUtils.hasText(custId))
        {
            throw new ServiceException("客户号不能为空");
        }
        URI uri = UriComponentsBuilder.fromHttpUrl(url)
                .queryParam("cust_id", custId)
                .build()
                .toUri();
        CustomerMapResponse response = restTemplate.getForObject(uri, CustomerMapResponse.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 CustomerMapResponse
    {
        private Integer code;
        private String msg;
        private List<CustomerMapRecordVO> data;
    }
}
```

- [ ] **Step 4: Run service tests**

Run:

```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingCustomerMapServiceTest -Dsurefire.failIfNoSpecifiedTests=false test
```

Expected: PASS.

## Task 3: Add Mock Endpoints

**Files:**
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockController.java`
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockControllerCustomerMapTest.java`

- [ ] **Step 1: Write controller tests for mock endpoints**

```java
package com.ruoyi.loanpricing.controller;

import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.core.domain.AjaxResult;
import java.util.List;
import org.junit.jupiter.api.Test;

class LoanRatePricingMockControllerCustomerMapTest
{
    @Test
    void shouldReturnPersonalCustomerMapRecords() throws Exception
    {
        LoanRatePricingMockController controller = new LoanRatePricingMockController();

        AjaxResult result = controller.queryPersonalCustomerMap("P001");

        List<?> rows = (List<?>) result.get("data");
        assertFalse(rows.isEmpty());
        String json = new ObjectMapper().writeValueAsString(rows.get(0));
        assertTrue(json.contains("\"cust_id\""));
        assertTrue(json.contains("\"cust_isn\""));
        assertTrue(json.contains("\"cust_name\""));
    }

    @Test
    void shouldReturnCorporateCustomerMapRecords() throws Exception
    {
        LoanRatePricingMockController controller = new LoanRatePricingMockController();

        AjaxResult result = controller.queryCorporateCustomerMap("C001");

        List<?> rows = (List<?>) result.get("data");
        assertFalse(rows.isEmpty());
        String json = new ObjectMapper().writeValueAsString(rows.get(0));
        assertTrue(json.contains("\"cust_id\""));
        assertTrue(json.contains("\"loan_count_his\""));
        assertTrue(json.contains("\"last_loan_date\""));
    }
}
```

- [ ] **Step 2: Run the tests to verify they fail**

Run:

```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanRatePricingMockControllerCustomerMapTest -Dsurefire.failIfNoSpecifiedTests=false test
```

Expected: FAIL because the mock methods do not exist.

- [ ] **Step 3: Implement mock methods**

Modify `LoanRatePricingMockController`:

```java
@Anonymous
@Operation(summary = "模拟个人客户号映射查询")
@GetMapping("/customer-map/personal")
public AjaxResult queryPersonalCustomerMap(@RequestParam("cust_id") String custId)
{
    return success(randomCustomerMapRecords(custId, "个人客户"));
}

@Anonymous
@Operation(summary = "模拟企业客户号映射查询")
@GetMapping("/customer-map/corporate")
public AjaxResult queryCorporateCustomerMap(@RequestParam("cust_id") String custId)
{
    return success(randomCustomerMapRecords(custId, "企业客户"));
}
```

Add a private helper in the same controller:

```java
private List<CustomerMapRecordVO> randomCustomerMapRecords(String custId, String namePrefix)
{
    if (!StringUtils.hasText(custId))
    {
        throw new ServiceException("客户号不能为空");
    }
    int count = ThreadLocalRandom.current().nextInt(1, 4);
    List<CustomerMapRecordVO> records = new ArrayList<>();
    for (int i = 1; i <= count; i++)
    {
        CustomerMapRecordVO record = new CustomerMapRecordVO();
        record.setCustId(custId);
        record.setCustIsn(String.valueOf(81000000000L + ThreadLocalRandom.current().nextInt(1000000)));
        record.setCustName(namePrefix + i);
        record.setFaithDay(String.valueOf(ThreadLocalRandom.current().nextInt(1, 365)));
        record.setBalanceAvg(String.valueOf(ThreadLocalRandom.current().nextInt(10000, 900000)));
        record.setLoanCountHis(String.valueOf(ThreadLocalRandom.current().nextInt(0, 10)));
        record.setLastLoanDate(LocalDate.now().minusDays(ThreadLocalRandom.current().nextInt(1, 800)).toString());
        records.add(record);
    }
    return records;
}
```

Required imports:

```java
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.loanpricing.domain.vo.CustomerMapRecordVO;
import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
```

- [ ] **Step 4: Run mock controller tests**

Run:

```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanRatePricingMockControllerCustomerMapTest -Dsurefire.failIfNoSpecifiedTests=false test
```

Expected: PASS.

## Task 4: Add Business Endpoints

**Files:**
- Modify: `ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowController.java`
- Create: `ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowControllerCustomerMapTest.java`

- [ ] **Step 1: Write controller delegation tests**

```java
package com.ruoyi.loanpricing.controller;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.mockito.Mockito.when;

import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.loanpricing.domain.vo.CustomerMapRecordVO;
import com.ruoyi.loanpricing.service.LoanPricingCustomerMapService;
import java.lang.reflect.Field;
import java.util.List;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;

class LoanPricingWorkflowControllerCustomerMapTest
{
    @Test
    void shouldReturnPersonalCustomerMapFromService() throws Exception
    {
        LoanPricingCustomerMapService service = Mockito.mock(LoanPricingCustomerMapService.class);
        CustomerMapRecordVO row = new CustomerMapRecordVO();
        row.setCustIsn("81033011438");
        when(service.queryPersonal("P001")).thenReturn(List.of(row));

        LoanPricingWorkflowController controller = new LoanPricingWorkflowController();
        setField(controller, "customerMapService", service);

        AjaxResult result = controller.queryPersonalCustomerMap("P001");

        List<?> rows = (List<?>) result.get("data");
        assertEquals(1, rows.size());
    }

    @Test
    void shouldReturnCorporateCustomerMapFromService() throws Exception
    {
        LoanPricingCustomerMapService service = Mockito.mock(LoanPricingCustomerMapService.class);
        CustomerMapRecordVO row = new CustomerMapRecordVO();
        row.setCustIsn("82002469287");
        when(service.queryCorporate("C001")).thenReturn(List.of(row));

        LoanPricingWorkflowController controller = new LoanPricingWorkflowController();
        setField(controller, "customerMapService", service);

        AjaxResult result = controller.queryCorporateCustomerMap("C001");

        List<?> rows = (List<?>) result.get("data");
        assertEquals(1, rows.size());
    }

    private static void setField(Object target, String fieldName, Object value) throws Exception
    {
        Field field = target.getClass().getDeclaredField(fieldName);
        field.setAccessible(true);
        field.set(target, value);
    }
}
```

- [ ] **Step 2: Run the tests to verify they fail**

Run:

```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowControllerCustomerMapTest -Dsurefire.failIfNoSpecifiedTests=false test
```

Expected: FAIL because controller methods and field do not exist.

- [ ] **Step 3: Implement controller methods**

In `LoanPricingWorkflowController`, add:

```java
@Autowired
private LoanPricingCustomerMapService customerMapService;

@Operation(summary = "查询个人客户号映射")
@GetMapping("/customer-map/personal")
public AjaxResult queryPersonalCustomerMap(@RequestParam("custId") String custId)
{
    return success(customerMapService.queryPersonal(custId));
}

@Operation(summary = "查询企业客户号映射")
@GetMapping("/customer-map/corporate")
public AjaxResult queryCorporateCustomerMap(@RequestParam("custId") String custId)
{
    return success(customerMapService.queryCorporate(custId));
}
```

Required import:

```java
import com.ruoyi.loanpricing.service.LoanPricingCustomerMapService;
```

- [ ] **Step 4: Run controller tests**

Run:

```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingWorkflowControllerCustomerMapTest -Dsurefire.failIfNoSpecifiedTests=false test
```

Expected: PASS.

## Task 5: Add Profile Configuration

**Files:**
- 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`

- [ ] **Step 1: Add `customer-map` to every active profile**

Place this block next to the existing `model:` block in each profile file:

```yaml
customer-map:
    personal-url: http://localhost:63310/rate/pricing/mock/customer-map/personal
    corporate-url: http://localhost:63310/rate/pricing/mock/customer-map/corporate
```

Keep the existing `model:` URLs unchanged.

- [ ] **Step 2: Verify config keys exist**

Run:

```bash
rg -n "customer-map:|personal-url: http://localhost:63310/rate/pricing/mock/customer-map/personal|corporate-url: http://localhost:63310/rate/pricing/mock/customer-map/corporate" ruoyi-admin/src/main/resources/application-dev.yml ruoyi-admin/src/main/resources/application-uat.yml ruoyi-admin/src/main/resources/application-pro.yml
```

Expected: each profile contains one `customer-map` block with both mock URLs.

## Task 6: Backend Verification

**Files:**
- Verify only, no new files.

- [ ] **Step 1: Run focused backend tests**

Run:

```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=CustomerMapRecordVOTest,LoanPricingCustomerMapServiceTest,LoanRatePricingMockControllerCustomerMapTest,LoanPricingWorkflowControllerCustomerMapTest -Dsurefire.failIfNoSpecifiedTests=false test
```

Expected: PASS.

- [ ] **Step 2: Run affected existing model/workflow tests**

Run:

```bash
mvn -pl ruoyi-loan-pricing -am -Dtest=LoanPricingModelServiceTest,LoanPricingWorkflowServiceImplTest -Dsurefire.failIfNoSpecifiedTests=false test
```

Expected: PASS.

- [ ] **Step 3: Manual API verification after backend restart**

Restart backend so config and routes are active, then call:

```bash
curl -sS 'http://localhost:63310/rate/pricing/mock/customer-map/personal?cust_id=P001'
curl -sS 'http://localhost:63310/rate/pricing/mock/customer-map/corporate?cust_id=C001'
TOKEN=$(curl -sS -H 'Content-Type: application/json' -d '{"username":"admin","password":"admin123"}' 'http://localhost:63310/login/test' | sed -n 's/.*"token":"\([^"]*\)".*/\1/p')
curl -sS -H "Authorization: Bearer ${TOKEN}" 'http://localhost:63310/loanPricing/workflow/customer-map/personal?custId=P001'
curl -sS -H "Authorization: Bearer ${TOKEN}" 'http://localhost:63310/loanPricing/workflow/customer-map/corporate?custId=C001'
```

Expected: each successful response uses the RuoYi response wrapper and `data` contains one or more records with underscore fields. If the local admin password differs, replace `admin/admin123` with a valid local test account before calling the authenticated workflow endpoints.

- [ ] **Step 4: Commit backend work**

Use a Chinese commit message and do not include unrelated dirty files:

```bash
git status --short
git add ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/domain/vo/CustomerMapRecordVO.java \
  ruoyi-loan-pricing/src/main/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapService.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-loan-pricing/src/test/java/com/ruoyi/loanpricing/domain/vo/CustomerMapRecordVOTest.java \
  ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/service/LoanPricingCustomerMapServiceTest.java \
  ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanRatePricingMockControllerCustomerMapTest.java \
  ruoyi-loan-pricing/src/test/java/com/ruoyi/loanpricing/controller/LoanPricingWorkflowControllerCustomerMapTest.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
git commit -m "新增客户号映射后端接口"
```

Do not commit temporary curl output, screenshots, or generated test data.
