From 5e1a50809be0ff900ad44fc2265f152bc9bbb98d Mon Sep 17 00:00:00 2001
From: shinnosuke <tantao@1hoper.com>
Date: 星期一, 12 十月 2026 11:19:58 +0800
Subject: [PATCH] fix(merchant): 回填臻享礼遇商家类型并验证管理链路一致性
---
talent-card-server/src/test/java/com/talentcard/server/business/admin/PremiumServiceMerchantTypeTests.java | 132 ++++++++++++++++++++++++++++++++++++++++++++
talent-card-server/src/main/resources/db/migration/V157__backfill_premium_service_merchant_types.sql | 15 +++++
2 files changed, 147 insertions(+), 0 deletions(-)
diff --git a/talent-card-server/src/main/resources/db/migration/V157__backfill_premium_service_merchant_types.sql b/talent-card-server/src/main/resources/db/migration/V157__backfill_premium_service_merchant_types.sql
new file mode 100644
index 0000000..7250549
--- /dev/null
+++ b/talent-card-server/src/main/resources/db/migration/V157__backfill_premium_service_merchant_types.sql
@@ -0,0 +1,15 @@
+-- V48 used the generic service type; V119 consequently classified these rows as other.
+-- Restore both fields from the active premium mapping without overwriting explicit types.
+-- The unique merchant_id key on premium_topic_merchant makes the mapping unambiguous.
+update merchant m
+join premium_topic_merchant p on p.merchant_id = m.id and p.deleted = 0
+set m.merchant_type_code = p.category_key,
+ m.merchant_type = case p.category_key
+ when 'children' then '子女托管'
+ when 'approval' then '行政审批'
+ when 'employment' then '配偶推荐就业'
+ end
+where m.deleted = 0
+ and m.merchant_type = 'service'
+ and m.merchant_type_code = 'other'
+ and p.category_key in ('children', 'approval', 'employment');
diff --git a/talent-card-server/src/test/java/com/talentcard/server/business/admin/PremiumServiceMerchantTypeTests.java b/talent-card-server/src/test/java/com/talentcard/server/business/admin/PremiumServiceMerchantTypeTests.java
new file mode 100644
index 0000000..372ffa6
--- /dev/null
+++ b/talent-card-server/src/test/java/com/talentcard/server/business/admin/PremiumServiceMerchantTypeTests.java
@@ -0,0 +1,132 @@
+package com.talentcard.server.business.admin;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Map;
+import java.util.UUID;
+
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.boot.test.context.SpringBootTest;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.test.context.ActiveProfiles;
+import org.springframework.transaction.annotation.Transactional;
+
+@SpringBootTest
+@ActiveProfiles("test")
+@Transactional
+class PremiumServiceMerchantTypeTests {
+
+ @Autowired
+ private JdbcTemplate jdbc;
+
+ @Autowired
+ private AdminBusinessService service;
+
+ @ParameterizedTest
+ @CsvSource({
+ "children, 子女托管, CHILD_ENTRUSTMENT",
+ "approval, 行政审批, ADMIN_APPROVAL",
+ "employment, 配偶推荐就业, SPOUSE_EMPLOYMENT"
+ })
+ void migratedTypeAgreesAcrossListFiltersDetailAndEditing(String code, String label, String businessType)
+ throws Exception {
+ long id = insertMerchant("service", "other", 0);
+ insertMapping(id, code, businessType, 0);
+
+ migrate();
+ Map<String, Object> detail = service.merchant(id);
+ assertThat(detail).containsEntry("merchantType", label).containsEntry("merchantTypeCode", code);
+ String name = (String) detail.get("merchantName");
+ assertList(id, name, null, code, label, 1);
+ assertList(id, name, label, code, label, 1);
+ assertList(id, name, code, code, label, 1);
+ assertList(id, name, "food", code, label, 0);
+
+ // The edit page submits the display name returned by the detail endpoint.
+ Map<String, Object> saved = service.updateMerchant(id, new MerchantUpdateRequest(
+ name + " edited", (String) detail.get("merchantType"), "Test contact", "13800000000",
+ "Test address", null, null, "营业中", (byte) 0, 0, (byte) 1,
+ null, null, (byte) 0, null, null, null, null, null, null, null, (byte) 0, null, null));
+ assertThat(saved).containsEntry("merchantType", label).containsEntry("merchantTypeCode", code);
+ assertThat(jdbc.queryForMap(
+ "select category_key, business_type, deleted from premium_topic_merchant where merchant_id = ?", id))
+ .containsEntry("category_key", code).containsEntry("business_type", businessType);
+ assertThat(jdbc.queryForObject(
+ "select deleted from premium_topic_merchant where merchant_id = ?", Integer.class, id)).isZero();
+ assertList(id, name + " edited", label, code, label, 1);
+
+ // Reapplying the repair must also preserve a subsequent explicit type edit.
+ jdbc.update("update merchant set merchant_type = '餐饮服务', merchant_type_code = 'food' where id = ?", id);
+ migrate();
+ assertThat(service.merchant(id))
+ .containsEntry("merchantType", "餐饮服务").containsEntry("merchantTypeCode", "food");
+ }
+
+ @Test
+ void repairIsIdempotentAndLeavesUnrelatedOrDeletedRecordsUntouched() throws Exception {
+ long fixed = insertMerchant("service", "other", 0);
+ insertMapping(fixed, "children", "CHILD_ENTRUSTMENT", 0);
+ long unmapped = insertMerchant("service", "other", 0);
+ long deletedMerchant = insertMerchant("service", "other", 1);
+ insertMapping(deletedMerchant, "children", "CHILD_ENTRUSTMENT", 0);
+ long deletedMapping = insertMerchant("service", "other", 0);
+ insertMapping(deletedMapping, "approval", "ADMIN_APPROVAL", 1);
+ long unsupportedMapping = insertMerchant("service", "other", 0);
+ insertMapping(unsupportedMapping, "finance", "FINANCE", 0);
+ long explicitCode = insertMerchant("service", "food", 0);
+ insertMapping(explicitCode, "employment", "SPOUSE_EMPLOYMENT", 0);
+ long explicitName = insertMerchant("LOCAL_TEST", "other", 0);
+ insertMapping(explicitName, "children", "CHILD_ENTRUSTMENT", 0);
+
+ migrate();
+ Map<String, Object> repaired = jdbc.queryForMap("select * from merchant where id = ?", fixed);
+ migrate();
+ assertThat(jdbc.queryForMap("select * from merchant where id = ?", fixed)).isEqualTo(repaired);
+ assertType(fixed, "子女托管", "children");
+ for (long id : new long[]{unmapped, deletedMerchant, deletedMapping, unsupportedMapping}) {
+ assertType(id, "service", "other");
+ }
+ assertType(explicitCode, "service", "food");
+ assertType(explicitName, "LOCAL_TEST", "other");
+ }
+
+ private void assertList(long id, String name, String filter, String code, String label, int expected) {
+ var page = service.merchants(name, null, filter, null, null, null, null, 1, 10);
+ assertThat(page.total()).isEqualTo(expected);
+ assertThat(page.records()).hasSize(expected);
+ if (expected == 1) {
+ assertThat(page.records().get(0))
+ .containsEntry("id", id).containsEntry("merchantType", label).containsEntry("merchantTypeCode", code);
+ }
+ }
+
+ private void assertType(long id, String label, String code) {
+ assertThat(jdbc.queryForMap("select merchant_type, merchant_type_code from merchant where id = ?", id))
+ .containsEntry("merchant_type", label).containsEntry("merchant_type_code", code);
+ }
+
+ private long insertMerchant(String label, String code, int deleted) {
+ jdbc.update("""
+ insert into merchant(merchant_name, merchant_type, merchant_type_code, address, deleted)
+ values (?, ?, ?, 'Test address', ?)
+ """, "premium-type-" + UUID.randomUUID(), label, code, deleted);
+ return jdbc.queryForObject("select last_insert_id()", Long.class);
+ }
+
+ private void insertMapping(long id, String category, String businessType, int deleted) {
+ jdbc.update("""
+ insert into premium_topic_merchant(merchant_id, category_key, business_type, deleted)
+ values (?, ?, ?, ?)
+ """, id, category, businessType, deleted);
+ }
+
+ private void migrate() throws Exception {
+ jdbc.execute(new ClassPathResource("db/migration/V157__backfill_premium_service_merchant_types.sql")
+ .getContentAsString(StandardCharsets.UTF_8));
+ }
+}
--
Gitblit v1.7.1