From 02df6417d400aabb559d095e5f7cd63c63b86fb1 Mon Sep 17 00:00:00 2001
From: yeg <1347747024@qq.com>
Date: 星期四, 17 九月 2026 15:57:20 +0800
Subject: [PATCH] fix(merchant): 开户行按银行名与支行名分别匹配,城市降级为本地优先排序
---
rencaika_merchant/pages/bank-branch-select/bank-branch-select.uvue | 24 +++
talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantSettlementService.java | 56 ++++++--
talent-card-server/src/test/java/com/talentcard/server/business/merchant/BankBranchKeywordTests.java | 89 ++++++++++++++
talent-card-server/src/test/java/com/talentcard/server/business/merchant/BankBranchSearchTests.java | 97 ++++++++++++++++
talent-card-server/src/main/java/com/talentcard/server/business/merchant/BankBranchKeyword.java | 78 +++++++++++++
5 files changed, 322 insertions(+), 22 deletions(-)
diff --git a/rencaika_merchant/pages/bank-branch-select/bank-branch-select.uvue b/rencaika_merchant/pages/bank-branch-select/bank-branch-select.uvue
index 6357dad..37abaad 100644
--- a/rencaika_merchant/pages/bank-branch-select/bank-branch-select.uvue
+++ b/rencaika_merchant/pages/bank-branch-select/bank-branch-select.uvue
@@ -6,8 +6,8 @@
</view>
<view class="search-section">
- <view class="search-box"><input class="search-input" v-model="searchKeyword" maxlength="30" placeholder="搜索银行或支行名称" placeholder-class="search-placeholder" /><image class="search-icon" src="/static/icons/lucide/search.svg" mode="aspectFit"></image></view>
- <text class="city-tip">当前开户城市:{{cityName}}</text>
+ <view class="search-box"><input class="search-input" v-model="searchKeyword" maxlength="30" placeholder="输入支行名称关键字或联行号" placeholder-class="search-placeholder" /><image class="search-icon" src="/static/icons/lucide/search.svg" mode="aspectFit"></image></view>
+ <text class="city-tip">开户城市:{{cityName}}({{cityName}}的网点优先,开户行不限城市)</text>
</view>
<scroll-view class="branch-scroll" scroll-y="true" :show-scrollbar="false" :enhanced="false">
@@ -20,7 +20,8 @@
</view>
<view v-if="loading" class="empty-state"><text class="empty-title">正在加载开户行</text></view>
<view v-else-if="loadError" class="empty-state" @tap="loadBranches"><text class="empty-title">开户行加载失败</text><text class="empty-tip">点击重新加载</text></view>
- <view v-else-if="branches.length == 0" class="empty-state"><text class="empty-title">未找到匹配的开户行</text><text class="empty-tip">请尝试输入银行简称或支行名称</text></view>
+ <view v-else-if="!searched" class="empty-state"><text class="empty-title">输入支行名称或联行号搜索</text><text class="empty-tip">开户行不限开户城市;也可输入 12 位联行号</text></view>
+ <view v-else-if="branches.length == 0" class="empty-state" @tap="loadBranches"><text class="empty-title">未找到匹配的开户行</text><text class="empty-tip">可换用支行名称中的关键字(如"邻水丰禾"),或输入联行号;点击重试</text></view>
<view class="safe-space"></view>
</scroll-view>
</view>
@@ -36,21 +37,35 @@
const branches = ref<any[]>([])
const loading = ref(false)
const loadError = ref(false)
+ const searched = ref(false)
let searchTimer : number | null = null
const goBack = () => {
uni.navigateBack()
}
+ // 开户城市只作为「本地网点优先」的排序提示传给后端,不是过滤条件:后端按城市名子串匹配支行名,
+ // 会把「邻水丰禾支行」这类以县区命名的网点整批筛掉(支行名里只有县区名,没有地市名)。
+ // 空关键词不发请求:后端在没有关键词时返回的是按行名排序的网点名单,没有意义。
const loadBranches = () => {
+ const keyword = searchKeyword.value.trim()
+ if (keyword.length == 0) {
+ branches.value = []
+ loading.value = false
+ loadError.value = false
+ searched.value = false
+ return
+ }
loading.value = true
loadError.value = false
- loadBankBranches(cityCode.value, cityName.value, searchKeyword.value.trim(), (items: any[]) => {
+ loadBankBranches(cityCode.value, cityName.value, keyword, (items: any[]) => {
branches.value = items
loading.value = false
+ searched.value = true
}, () => {
loading.value = false
loadError.value = true
+ searched.value = true
})
}
@@ -67,7 +82,6 @@
onLoad((options: any) => {
if (options.city != null && options.city != '') cityName.value = decodeURIComponent(options.city as string)
if (options.cityCode != null && options.cityCode != '') cityCode.value = decodeURIComponent(options.cityCode as string)
- loadBranches()
})
</script>
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/BankBranchKeyword.java b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/BankBranchKeyword.java
new file mode 100644
index 0000000..dcce522
--- /dev/null
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/BankBranchKeyword.java
@@ -0,0 +1,78 @@
+package com.talentcard.server.business.merchant;
+
+import java.util.Comparator;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * 开户行搜索关键词的解析结果,以及把用户写法映射到目录写法的银行名表。
+ *
+ * <p>为什么要拆:目录里的 {@code branch_name} 是「中国农业银行股份有限公司邻水丰禾支行」这样的全称,
+ * 而用户输入的是「农行邻水丰禾支行」——既用了简称,又在银行名与支行名之间少了「股份有限公司」,
+ * 整串并不连续,所以 {@code like '%农行邻水丰禾支行%'} 永远匹配不到。这里把关键词拆成「银行名」和
+ * 「支行名」两段,分别对 {@code bank_name} 与 {@code branch_name} 匹配,两段各自允许不完整。
+ *
+ * <p>{@code clearingCode} 非空表示用户输入的是联行号(或其片段),此时按联行号匹配、不再猜名字。
+ *
+ * <p>解析是纯函数,不查库。
+ */
+record BankBranchKeyword(String bankName, String branchName, String clearingCode) {
+
+ /**
+ * 用户可能的银行写法(含常见简称)→ 目录 {@code bank_name} 里确实存在的片段。
+ *
+ * <p>值刻意取不含「中国」前缀的片段:目录里既有「中国农业银行」也有「交通银行」「招商银行」
+ * 两种写法,用片段匹配可以同时覆盖。
+ */
+ private static final Map<String, String> BANK_SPELLINGS = Map.ofEntries(
+ Map.entry("中国农业银行", "农业银行"), Map.entry("农业银行", "农业银行"), Map.entry("农行", "农业银行"),
+ Map.entry("中国工商银行", "工商银行"), Map.entry("工商银行", "工商银行"), Map.entry("工行", "工商银行"),
+ Map.entry("中国建设银行", "建设银行"), Map.entry("建设银行", "建设银行"), Map.entry("建行", "建设银行"),
+ Map.entry("中国银行", "中国银行"), Map.entry("中行", "中国银行"),
+ Map.entry("中国邮政储蓄银行", "邮政储蓄银行"), Map.entry("邮政储蓄银行", "邮政储蓄银行"),
+ Map.entry("邮政储蓄", "邮政储蓄银行"), Map.entry("邮储银行", "邮政储蓄银行"), Map.entry("邮储", "邮政储蓄银行"),
+ Map.entry("中国农业发展银行", "农业发展银行"), Map.entry("农业发展银行", "农业发展银行"),
+ Map.entry("农发行", "农业发展银行"),
+ Map.entry("中国人民银行", "中国人民银行"), Map.entry("人民银行", "中国人民银行"), Map.entry("人行", "中国人民银行"),
+ Map.entry("交通银行", "交通银行"), Map.entry("交行", "交通银行"),
+ Map.entry("招商银行", "招商银行"), Map.entry("招行", "招商银行"),
+ Map.entry("中信银行", "中信银行"), Map.entry("中信", "中信银行"),
+ Map.entry("上海浦东发展银行", "浦东发展银行"), Map.entry("浦东发展银行", "浦东发展银行"),
+ Map.entry("浦发银行", "浦东发展银行"), Map.entry("浦发", "浦东发展银行"),
+ Map.entry("兴业银行", "兴业银行"), Map.entry("兴业", "兴业银行"),
+ Map.entry("中国民生银行", "民生银行"), Map.entry("民生银行", "民生银行"), Map.entry("民生", "民生银行"),
+ Map.entry("中国光大银行", "光大银行"), Map.entry("光大银行", "光大银行"), Map.entry("光大", "光大银行"),
+ Map.entry("华夏银行", "华夏银行"), Map.entry("华夏", "华夏银行"),
+ Map.entry("平安银行", "平安银行"), Map.entry("平安", "平安银行"),
+ Map.entry("广发银行", "广发银行"), Map.entry("广发", "广发银行"),
+ Map.entry("恒丰银行", "恒丰银行"), Map.entry("恒丰", "恒丰银行"),
+ Map.entry("渤海银行", "渤海银行"), Map.entry("渤海", "渤海银行"),
+ Map.entry("浙商银行", "浙商银行"), Map.entry("浙商", "浙商银行"),
+ Map.entry("农村商业银行", "农村商业银行"), Map.entry("农商银行", "农村商业银行"),
+ Map.entry("农商行", "农村商业银行"), Map.entry("农商", "农村商业银行"),
+ Map.entry("农村信用社", "农村信用"), Map.entry("农信社", "农村信用"),
+ Map.entry("农信", "农村信用"), Map.entry("信用社", "农村信用"),
+ Map.entry("村镇银行", "村镇银行"));
+
+ /** 长的写法优先:否则「中国农业银行」会先被「农业银行」命中,留下一个「中国」残片匹配不到任何支行。 */
+ private static final List<String> SPELLINGS_LONGEST_FIRST = BANK_SPELLINGS.keySet().stream()
+ .sorted(Comparator.comparingInt(String::length).reversed())
+ .toList();
+
+ static BankBranchKeyword parse(String rawKeyword) {
+ // 空格一律去掉:「农行 邻水 丰禾」去掉后剩下的「邻水丰禾」正好是目录里的连续片段。
+ String keyword = rawKeyword == null ? "" : rawKeyword.replaceAll("\\s+", "");
+ if (keyword.isEmpty()) return new BankBranchKeyword("", "", "");
+ // 联行号按片段匹配而不是补齐后精确匹配:用户常见的是只记得后几位,补齐前导零那种猜测
+ // 一次都命中不了,片段匹配则 12 位全码与后几位都能搜到。
+ if (keyword.matches("\\d{1,12}")) return new BankBranchKeyword("", "", keyword);
+ for (String spelling : SPELLINGS_LONGEST_FIRST) {
+ int position = keyword.indexOf(spelling);
+ if (position < 0) continue;
+ // 银行写法可能出现在任意位置(「攀枝花农行」与「农行邻水」都有),所以两头都要切。
+ String remainder = keyword.substring(0, position) + keyword.substring(position + spelling.length());
+ return new BankBranchKeyword(BANK_SPELLINGS.get(spelling), remainder, "");
+ }
+ return new BankBranchKeyword("", keyword, "");
+ }
+}
diff --git a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantSettlementService.java b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantSettlementService.java
index abc076c..73f3a6f 100644
--- a/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantSettlementService.java
+++ b/talent-card-server/src/main/java/com/talentcard/server/business/merchant/MerchantSettlementService.java
@@ -350,29 +350,51 @@
int safePage = Math.max(page, 1);
int safeSize = Math.min(Math.max(size, 1), 50);
StringBuilder where = new StringBuilder(" where 1=1");
- List<Object> params = new ArrayList<>();
- String cityFilter = blank(cityName) ? clean(cityCode) : clean(cityName);
- int citySeparator = cityFilter.lastIndexOf(' ');
- if (citySeparator >= 0 && citySeparator + 1 < cityFilter.length()) cityFilter = cityFilter.substring(citySeparator + 1).trim();
- cityFilter = cityKeyword(cityFilter);
- if (!blank(cityFilter)) {
+ List<Object> whereParams = new ArrayList<>();
+ BankBranchKeyword parsed = BankBranchKeyword.parse(keyword);
+ if (parsed.clearingCode().length() > 0) {
+ where.append(" and clearing_code like ?");
+ whereParams.add("%" + parsed.clearingCode() + "%");
+ } else if (parsed.bankName().length() > 0) {
+ appendLikeColumn(where, whereParams, "bank_name", parsed.bankName());
+ // 总行名已经限定过,剩下的片段只比支行名,不再拿它去匹配总行名。
+ if (parsed.branchName().length() > 0) appendLikeColumn(where, whereParams, "branch_name", parsed.branchName());
+ } else if (parsed.branchName().length() > 0) {
+ // 没识别出银行名时两列都试:「泰隆」这类不在表里的银行只能由 bank_name 命中。
where.append(" and (branch_name like ? or bank_name like ?)");
- String match = "%" + cityFilter + "%";
- params.add(match);
- params.add(match);
+ whereParams.add("%" + parsed.branchName() + "%");
+ whereParams.add("%" + parsed.branchName() + "%");
}
- if (!blank(keyword)) {
- where.append(" and (branch_name like ? or bank_name like ? or clearing_code = ?)");
- String match = "%" + clean(keyword) + "%";
- params.add(match);
- params.add(match);
- params.add(clean(keyword));
+ Long total = jdbc.queryForObject("select count(*) from bank_branch_directory" + where, Long.class, whereParams.toArray());
+
+ // 开户城市只用于把本地网点排在前面,不再作为过滤条件:目录里的支行名由县区名构成
+ // (「…邻水丰禾支行」里没有「广安」),按地市名过滤会把整批县区级网点筛掉,跨市开户的
+ // 商户就永远搜不到自己的开户行。排序兼顾两点:常见关键词先给就近网点,跨市网点也还搜得到。
+ String cityHint = cityHint(cityCode, cityName);
+ List<Object> pageParams = new ArrayList<>(whereParams);
+ String orderBy = " order by branch_name";
+ if (!blank(cityHint)) {
+ orderBy = " order by case when branch_name like ? or bank_name like ? then 0 else 1 end, branch_name";
+ pageParams.add("%" + cityHint + "%");
+ pageParams.add("%" + cityHint + "%");
}
- Long total = jdbc.queryForObject("select count(*) from bank_branch_directory" + where, Long.class, params.toArray());
- List<Map<String, Object>> values = rows("select clearing_code as id, clearing_code as branch_code, branch_name, head_bank_code, bank_name from bank_branch_directory" + where + " order by branch_name limit ? offset ?", appendPaging(params, safeSize, (safePage - 1) * safeSize));
+ List<Map<String, Object>> values = rows("select clearing_code as id, clearing_code as branch_code, branch_name, head_bank_code, bank_name from bank_branch_directory" + where + orderBy + " limit ? offset ?", appendPaging(pageParams, safeSize, (safePage - 1) * safeSize));
return new PageResult<>(values, safePage, safeSize, total == null ? 0 : total);
}
+ private void appendLikeColumn(StringBuilder where, List<Object> params, String column, String value) {
+ where.append(" and ").append(column).append(" like ?");
+ params.add("%" + value + "%");
+ }
+
+ /** 从「四川省 攀枝花市」这类选择器取值里取出用作排序提示的地市名。 */
+ private String cityHint(String cityCode, String cityName) {
+ String filter = blank(cityName) ? clean(cityCode) : clean(cityName);
+ int separator = filter.lastIndexOf(' ');
+ if (separator >= 0 && separator + 1 < filter.length()) filter = filter.substring(separator + 1).trim();
+ return cityKeyword(filter);
+ }
+
private void requireApproved(MerchantPrincipal principal) { if (!"approved".equals(onboardingStatus(principal).get("status"))) throw new BusinessException(ErrorCode.CONFLICT, "商户进件尚未审核通过"); }
private void submitBankCardToHuifu(MerchantPrincipal principal, Map<String, Object> card, String cardNumber, Map<String, Object> branch) {
List<Map<String, Object>> onboarding = rows("select * from merchant_onboarding_application where merchant_id=? and store_id=? and initial_bank_card_id is not null and status='approved' and provider_huifu_id is not null order by id desc limit 1", principal.merchantId(), principal.storeId());
diff --git a/talent-card-server/src/test/java/com/talentcard/server/business/merchant/BankBranchKeywordTests.java b/talent-card-server/src/test/java/com/talentcard/server/business/merchant/BankBranchKeywordTests.java
new file mode 100644
index 0000000..ef2f640
--- /dev/null
+++ b/talent-card-server/src/test/java/com/talentcard/server/business/merchant/BankBranchKeywordTests.java
@@ -0,0 +1,89 @@
+package com.talentcard.server.business.merchant;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.jupiter.api.Test;
+
+/**
+ * 关键词解析是纯函数,这里覆盖的是「用户输入的写法与目录里的写法不一致」这一类真实的搜索失败:
+ * 目录是「中国农业银行股份有限公司邻水丰禾支行」,用户输入「农行邻水丰禾支行」搜不到。
+ */
+class BankBranchKeywordTests {
+
+ @Test
+ void splitsTheUsersHabitualSpellingIntoBankAndBranchParts() {
+ BankBranchKeyword parsed = BankBranchKeyword.parse("农行邻水丰禾支行");
+
+ assertThat(parsed.bankName()).isEqualTo("农业银行");
+ assertThat(parsed.branchName()).isEqualTo("邻水丰禾支行");
+ assertThat(parsed.clearingCode()).isEmpty();
+ }
+
+ @Test
+ void normalizesCommonBankAbbreviations() {
+ assertThat(BankBranchKeyword.parse("工行").bankName()).isEqualTo("工商银行");
+ assertThat(BankBranchKeyword.parse("建行").bankName()).isEqualTo("建设银行");
+ assertThat(BankBranchKeyword.parse("中行").bankName()).isEqualTo("中国银行");
+ assertThat(BankBranchKeyword.parse("邮储").bankName()).isEqualTo("邮政储蓄银行");
+ assertThat(BankBranchKeyword.parse("农商行").bankName()).isEqualTo("农村商业银行");
+ assertThat(BankBranchKeyword.parse("浦发").bankName()).isEqualTo("浦东发展银行");
+ }
+
+ @Test
+ void prefersTheLongestBankNameSoNoStrayPrefixIsLeft() {
+ assertThat(BankBranchKeyword.parse("中国农业银行").bankName()).isEqualTo("农业银行");
+ assertThat(BankBranchKeyword.parse("中国农业银行").branchName()).isEmpty();
+ assertThat(BankBranchKeyword.parse("中国银行").bankName()).isEqualTo("中国银行");
+ assertThat(BankBranchKeyword.parse("中国银行").branchName()).isEmpty();
+ // 农业发展银行的名称里也含「农业」,不能和农业银行混为一谈。
+ assertThat(BankBranchKeyword.parse("中国农业发展银行").bankName()).isEqualTo("农业发展银行");
+ }
+
+ @Test
+ void keepsTheBranchFragmentWhenTheUserTypesTheOfficialFullName() {
+ BankBranchKeyword parsed = BankBranchKeyword.parse("中国农业银行股份有限公司邻水丰禾支行");
+
+ assertThat(parsed.bankName()).isEqualTo("农业银行");
+ assertThat(parsed.branchName()).isEqualTo("股份有限公司邻水丰禾支行");
+ }
+
+ @Test
+ void findsTheBankSpellingWhereverItAppears() {
+ BankBranchKeyword parsed = BankBranchKeyword.parse("攀枝花农行");
+
+ assertThat(parsed.bankName()).isEqualTo("农业银行");
+ assertThat(parsed.branchName()).isEqualTo("攀枝花");
+ }
+
+ @Test
+ void dropsSpacesSoTheBranchFragmentsBecomeContiguous() {
+ BankBranchKeyword parsed = BankBranchKeyword.parse("农行 邻水 丰禾");
+
+ assertThat(parsed.bankName()).isEqualTo("农业银行");
+ assertThat(parsed.branchName()).isEqualTo("邻水丰禾");
+ }
+
+ @Test
+ void treatsDigitsAsAClearingCodeFragment() {
+ assertThat(BankBranchKeyword.parse("103676368390").clearingCode()).isEqualTo("103676368390");
+ assertThat(BankBranchKeyword.parse("103676368390").branchName()).isEmpty();
+ // 只记得后几位也要能搜到,所以按片段匹配而不是补齐前导零。
+ assertThat(BankBranchKeyword.parse(" 676368390 ").clearingCode()).isEqualTo("676368390");
+ }
+
+ @Test
+ void fallsBackToTheWholeKeywordWhenNoBankNameIsRecognized() {
+ BankBranchKeyword parsed = BankBranchKeyword.parse("邻水丰禾");
+
+ assertThat(parsed.bankName()).isEmpty();
+ assertThat(parsed.branchName()).isEqualTo("邻水丰禾");
+ // 表里没有的银行(如浙江泰隆商业银行)走这条路径,由 bank_name 那一侧命中。
+ assertThat(BankBranchKeyword.parse("泰隆").branchName()).isEqualTo("泰隆");
+ }
+
+ @Test
+ void blankKeywordsProduceNoPredicates() {
+ assertThat(BankBranchKeyword.parse(null)).isEqualTo(new BankBranchKeyword("", "", ""));
+ assertThat(BankBranchKeyword.parse(" ")).isEqualTo(new BankBranchKeyword("", "", ""));
+ }
+}
diff --git a/talent-card-server/src/test/java/com/talentcard/server/business/merchant/BankBranchSearchTests.java b/talent-card-server/src/test/java/com/talentcard/server/business/merchant/BankBranchSearchTests.java
new file mode 100644
index 0000000..7c27770
--- /dev/null
+++ b/talent-card-server/src/test/java/com/talentcard/server/business/merchant/BankBranchSearchTests.java
@@ -0,0 +1,97 @@
+package com.talentcard.server.business.merchant;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.util.List;
+import java.util.Map;
+
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.test.context.ActiveProfiles;
+
+/**
+ * 开户行检索走真实 SQL,这里覆盖「关键词解析 + 组合谓词 + 排序」拼起来之后的行为:解析本身由
+ * {@link BankBranchKeywordTests} 覆盖,但参数顺序与 like 语义只有真跑一遍才看得出来。
+ *
+ * <p>网点用固定联行号写入,重跑时按主键更新,避免第二次运行撞主键。
+ */
+@SpringBootTest
+@ActiveProfiles("test")
+class BankBranchSearchTests {
+
+ private static final String AGRICULTURAL_LINSHUI = "900000000001";
+ private static final String AGRICULTURAL_PANZHIHUA = "900000000002";
+ private static final String INDUSTRIAL_PANZHIHUA = "900000000003";
+ private static final String TAILONG_TAIZHOU = "900000000004";
+ private static final String LINSHUI_FENGHE = "中国农业银行股份有限公司邻水丰禾支行";
+ private static final String PANZHIHUA_BINGCAOGANG = "中国农业银行攀枝花炳草岗支行";
+
+ @Autowired
+ private JdbcTemplate jdbc;
+
+ @Autowired
+ private MerchantSettlementService settlementService;
+
+ @BeforeEach
+ void prepareDirectory() {
+ upsert(AGRICULTURAL_LINSHUI, LINSHUI_FENGHE, "中国农业银行", "01030000");
+ upsert(AGRICULTURAL_PANZHIHUA, PANZHIHUA_BINGCAOGANG, "中国农业银行", "01030000");
+ upsert(INDUSTRIAL_PANZHIHUA, "中国工商银行股份有限公司攀枝花东区支行", "中国工商银行", "01020000");
+ upsert(TAILONG_TAIZHOU, "浙江泰隆商业银行股份有限公司台州分行", "浙江泰隆商业银行", "31334500");
+ }
+
+ private void upsert(String clearingCode, String branchName, String bankName, String headBankCode) {
+ jdbc.update("""
+ insert into bank_branch_directory(clearing_code, branch_name, head_bank_code, bank_name)
+ values (?, ?, ?, ?)
+ on duplicate key update branch_name = values(branch_name),
+ head_bank_code = values(head_bank_code),
+ bank_name = values(bank_name)
+ """, clearingCode, branchName, headBankCode, bankName);
+ }
+
+ /** 只保留本用例写入的网点,测试库是共享的。 */
+ private List<String> search(String cityName, String keyword) {
+ return settlementService.bankBranches("", cityName, keyword, 1, 30).records().stream()
+ .filter(row -> String.valueOf(row.get("branchCode")).startsWith("9000000000"))
+ .map(row -> String.valueOf(row.get("branchName")))
+ .toList();
+ }
+
+ @Test
+ void findsTheBranchWhenTheUserTypesTheAbbreviationAndDropsTheCorporateSuffix() {
+ // 目录里是「中国农业银行股份有限公司邻水丰禾支行」,用户输入「农行邻水丰禾支行」:
+ // 既用了简称,又在银行名与支行名之间少了「股份有限公司」,整串并不连续。
+ assertThat(search("四川省 攀枝花市", "农行邻水丰禾支行")).containsExactly(LINSHUI_FENGHE);
+ }
+
+ @Test
+ void ranksLocalBranchesFirstWithoutHidingAnotherCity() {
+ // 开户城市只影响排序。跨市开户的商户必须还能在结果里看到自己的开户行,
+ // 同时就近网点排在前面——这是把城市从过滤条件降级成排序提示的原因。
+ assertThat(search("四川省 攀枝花市", "农行"))
+ .containsExactly(PANZHIHUA_BINGCAOGANG, LINSHUI_FENGHE);
+ }
+
+ @Test
+ void matchesTheOfficialFullNameTypedVerbatim() {
+ assertThat(search("四川省 攀枝花市", "中国农业银行股份有限公司邻水丰禾支行")).containsExactly(LINSHUI_FENGHE);
+ assertThat(search("四川省 攀枝花市", "邻水丰禾")).containsExactly(LINSHUI_FENGHE);
+ }
+
+ @Test
+ void matchesAClearingCodeFragmentAsWellAsTheFullCode() {
+ assertThat(search("四川省 攀枝花市", "900000000001")).containsExactly(LINSHUI_FENGHE);
+ // 只记得后几位也要能搜到。
+ assertThat(search("四川省 攀枝花市", "0000000001")).containsExactly(LINSHUI_FENGHE);
+ }
+
+ @Test
+ void fallsBackToTheBankNameColumnForBanksOutsideTheSpellingTable() {
+ // 「泰隆」不在银行写法表里,只能由 bank_name 那一侧命中。
+ assertThat(search("", "泰隆")).containsExactly("浙江泰隆商业银行股份有限公司台州分行");
+ }
+}
--
Gitblit v1.7.1