fix(merchant): 开户行按银行名与支行名分别匹配,城市降级为本地优先排序
商户在进件里搜不到「农行邻水丰禾支行」,而这条网点在目录里是存在的
(103676368390 中国农业银行股份有限公司邻水丰禾支行)。搜不到是三层原因叠加,
每一层单独都足以把它挡掉:
- 城市是硬过滤:前端传了开户城市就追加 `and branch_name like '%城市%'`,而目录里的支行名
由县区名构成(「邻水丰禾支行」里没有「广安」),整批县区级网点被筛掉。开户城市选攀枝花时
结果集被锁死在 98 条里,把官方全名一字不差地输进去同样是 0 条。
- 简称不是子串:「农行」不是「中国农业银行」的子串,全库命中 0 条。
- 整串不连续:「农行邻水丰禾支行」在官方全名里中间隔着「股份有限公司」,子串匹配对不上。
改法:新增 BankBranchKeyword 把关键词拆成银行名与支行名两段,分别对 bank_name 与 branch_name
匹配,并把常见银行简称归一(农行、工行、建行、邮储、农商行、浦发等);空格一律去掉,所以
「农行 邻水 丰禾」也能命中;纯数字按联行号片段匹配,只记得后几位也搜得到。城市不再作为过滤
条件,改为本地网点优先排序,跨市开户的商户仍能搜到自己的开户行。
前端空关键词不再发请求(否则会拿到一份按行名排序的全国网点名单),补未搜索与未匹配两种空态,
并修掉「请尝试输入银行简称」这句已经不成立的提示。
回归:BankBranchKeywordTests(9 项解析用例)、BankBranchSearchTests(5 项对真实 MySQL 走完整
服务调用的检索用例,覆盖组合谓词与参数顺序)。
| | |
| | | </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"> |
| | |
| | | </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> |
| | |
| | | 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 |
| | | }) |
| | | } |
| | | |
| | |
| | | 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> |
| | | |
| New file |
| | |
| | | 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, ""); |
| | | } |
| | | } |
| | |
| | | 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()); |
| New file |
| | |
| | | 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("", "", "")); |
| | | } |
| | | } |
| New file |
| | |
| | | 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("浙江泰隆商业银行股份有限公司台州分行"); |
| | | } |
| | | } |