shinnosuke
2026-08-21 bc2efbdb52190fe2e7be9920bbed6902d4ee28aa
talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminBusinessService.java
@@ -17,6 +17,8 @@
import com.alibaba.fastjson.JSON;
import com.talentcard.server.business.DatabaseRows;
import com.talentcard.server.business.PageResult;
import com.talentcard.server.business.merchant.MerchantTypeCatalog;
import com.talentcard.server.business.merchant.MerchantTypeCatalog.MerchantType;
import com.talentcard.server.business.merchant.auth.MerchantPasswordHasher;
import com.talentcard.server.common.BusinessException;
import com.talentcard.server.common.ErrorCode;
@@ -28,12 +30,6 @@
@Service
public class AdminBusinessService {
   private static final Set<String> MERCHANT_APP_LOGIN_TYPES = Set.of(
         "餐饮服务", "景区服务", "免费体检", "配偶推荐就业", "金融服务", "行政审批", "子女托管",
         "酒店服务", "公寓租赁", "加油服务", "停车服务", "自助洗车", "购房优惠", "购车服务",
         "家电服务", "数码服务", "文化服务", "电信服务", "口腔治疗保健"
   );
   private final JdbcTemplate jdbcTemplate;
@@ -255,9 +251,9 @@
         args.add("%" + contactMobile.trim() + "%");
      }
      if (type != null && !type.isBlank()) {
         sql.append(" and m.merchant_type = ?");
         countSql.append(" and m.merchant_type = ?");
         args.add(type);
         sql.append(" and m.merchant_type_code = ?");
         countSql.append(" and m.merchant_type_code = ?");
         args.add(MerchantTypeCatalog.canonicalCode(type));
      }
      if (status != null) {
         sql.append(" and m.status = ?");
@@ -362,16 +358,18 @@
   @Transactional
   public Map<String, Object> createMerchant(MerchantCreateRequest request) {
      validateMerchantAddress(request.merchantType(), request.address());
      MerchantType merchantType = requireMerchantType(request.merchantType());
      validateMerchantAddress(merchantType.code(), request.address());
      validateRequiredMerchantCreateFields(request);
      jdbcTemplate.update("""
            insert into merchant(merchant_name, merchant_type, contact_name, contact_mobile, address,
            insert into merchant(merchant_name, merchant_type, merchant_type_code, contact_name, contact_mobile, address,
                                    logo_url, cover_url, business_status, longitude, latitude, can_login_merchant_app, group_buying_enabled,
                                    usage_instructions, rights_text, merchant_rights_config, service_label, is_recommend, status, sort_no)
            values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)
            values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 1, ?)
            """,
            request.merchantName(),
            request.merchantType(),
            merchantType.displayName(),
            merchantType.code(),
            request.contactName(),
            request.contactMobile(),
            request.address(),
@@ -386,11 +384,12 @@
            request.rightsText(),
            toJson(request.merchantRightsConfig()),
            request.serviceLabel(),
            request.recommend() == null ? defaultRecommend(request.merchantType()) : request.recommend(),
            request.recommend() == null ? defaultRecommend(merchantType.code()) : request.recommend(),
            request.sortNo() == null ? 0 : request.sortNo());
      Long id = jdbcTemplate.queryForObject("select last_insert_id()", Long.class);
      ensureParkingBenefit(id, request.merchantType());
      ensureFuelBenefit(id, request.merchantType(), request.serviceLabel());
      ensureParkingBenefit(id, merchantType.code());
      ensureFuelBenefit(id, merchantType.code(), request.serviceLabel());
      syncPremiumTopicMerchant(id, merchantType, request.sortNo());
      Long storeId = createDefaultStore(id, request.merchantName(), request.address());
      syncMerchantBusinessHours(id, storeId, request.businessHours());
      if (hasMerchantAppAccess(request)) {
@@ -412,7 +411,7 @@
   }
   void validateMerchantAddress(String merchantType, String address) {
      if (!"公寓租赁".equals(merchantType) && (address == null || address.isBlank())) {
      if (!"apartment".equals(MerchantTypeCatalog.canonicalCode(merchantType)) && (address == null || address.isBlank())) {
         throw new BusinessException(ErrorCode.BAD_REQUEST, "请输入商家地址");
      }
   }
@@ -423,11 +422,13 @@
   }
   boolean supportsMerchantAppLogin(String merchantType) {
      return MERCHANT_APP_LOGIN_TYPES.contains(merchantType);
      return MerchantTypeCatalog.find(merchantType)
            .map(MerchantType::supportsMerchantAppLogin)
            .orElse(false);
   }
   private void validateRequiredMerchantCreateFields(MerchantCreateRequest request) {
      if (!Set.of("景区服务", "餐饮服务").contains(request.merchantType())) {
      if (!Set.of("scenic", "food").contains(MerchantTypeCatalog.canonicalCode(request.merchantType()))) {
         return;
      }
      if (request.loginAccount() == null || !request.loginAccount().trim().matches("^[A-Za-z0-9]{1,32}$")) {
@@ -476,21 +477,24 @@
   }
   private byte defaultRecommend(String merchantType) {
      return (byte) (("餐饮服务".equals(merchantType) || "景区服务".equals(merchantType)) ? 1 : 0);
      return (byte) (("food".equals(merchantType) || "scenic".equals(merchantType)) ? 1 : 0);
   }
   @Transactional
   public Map<String, Object> updateMerchant(Long id, MerchantUpdateRequest request) {
      MerchantType merchantType = resolveMerchantTypeForUpdate(id, request.merchantType());
      validateMerchantAddress(merchantType.code(), request.address());
      int updated = jdbcTemplate.update("""
            update merchant
            set merchant_name = ?, merchant_type = ?, contact_name = ?, contact_mobile = ?,
            set merchant_name = ?, merchant_type = ?, merchant_type_code = ?, contact_name = ?, contact_mobile = ?,
                address = ?, logo_url = ?, cover_url = ?, business_status = ?, longitude = ?, latitude = ?, can_login_merchant_app = ?,
                group_buying_enabled = ?, usage_instructions = ?, rights_text = ?, merchant_rights_config = ?, service_label = ?,
                is_recommend = ?, sort_no = ?, status = ?
            where deleted = 0 and id = ?
            """,
            request.merchantName(),
            request.merchantType(),
            merchantType.displayName(),
            merchantType.code(),
            request.contactName(),
            request.contactMobile(),
            request.address(),
@@ -510,8 +514,9 @@
            request.status() == null ? 1 : request.status(),
            id);
      ensureUpdated(updated);
      ensureParkingBenefit(id, request.merchantType());
      ensureFuelBenefit(id, request.merchantType(), request.serviceLabel());
      ensureParkingBenefit(id, merchantType.code());
      ensureFuelBenefit(id, merchantType.code(), request.serviceLabel());
      syncPremiumTopicMerchant(id, merchantType, request.sortNo());
      Long storeId = findOrCreateDefaultStore(id, request.merchantName(), request.address());
      syncMerchantBusinessHours(id, storeId, request.businessHours());
      if (request.loginPassword() != null && !request.loginPassword().isBlank()) {
@@ -549,7 +554,7 @@
   }
   private void ensureParkingBenefit(Long merchantId, String merchantType) {
      if (!"停车服务".equals(merchantType) && !"parking".equalsIgnoreCase(merchantType)) return;
      if (!"parking".equals(merchantType)) return;
      jdbcTemplate.update("""
            insert into transport_parking_benefit(merchant_id, talent_levels, benefit_text, action_mode, plate_form_mode, usage_instructions, rule_text, status, deleted)
            select ?, '', '停车权益', 'plate', 'withParking',
@@ -560,7 +565,7 @@
   }
   private void ensureFuelBenefit(Long merchantId, String merchantType, String serviceLabel) {
      if (!"加油服务".equals(merchantType) && !"fuel".equalsIgnoreCase(merchantType)) return;
      if (!"fuel".equals(merchantType)) return;
      String discountText = serviceLabel == null || serviceLabel.isBlank() ? "请咨询商户" : serviceLabel.trim();
      jdbcTemplate.update("""
            insert into transport_fuel_benefit(
@@ -572,6 +577,35 @@
                   '具体适用油品、优惠额度、可用时段和活动规则以加油站说明为准。', 1, 0
            where not exists (select 1 from transport_fuel_benefit where merchant_id = ? and deleted = 0)
            """, merchantId, discountText, merchantId);
   }
   private void syncPremiumTopicMerchant(Long merchantId, MerchantType merchantType, Integer sortNo) {
      if (merchantType.premiumCategoryKey() == null || merchantType.premiumBusinessType() == null) {
         jdbcTemplate.update("update premium_topic_merchant set deleted = 1 where merchant_id = ? and deleted = 0", merchantId);
         return;
      }
      jdbcTemplate.update("""
            insert into premium_topic_merchant(merchant_id, category_key, business_type, sort_no, deleted)
            values (?, ?, ?, ?, 0)
            on duplicate key update category_key = values(category_key), business_type = values(business_type),
                                    sort_no = values(sort_no), deleted = 0
            """, merchantId, merchantType.premiumCategoryKey(), merchantType.premiumBusinessType(), sortNo == null ? 0 : sortNo);
   }
   private MerchantType requireMerchantType(String value) {
      return MerchantTypeCatalog.find(value)
            .orElseThrow(() -> new BusinessException(ErrorCode.BAD_REQUEST, "不支持的商家类型"));
   }
   private MerchantType resolveMerchantTypeForUpdate(Long merchantId, String value) {
      MerchantType requested = requireMerchantType(value);
      if (!"shopping".equals(requested.code())) return requested;
      List<String> existingCodes = jdbcTemplate.queryForList(
            "select merchant_type_code from merchant where id = ? and deleted = 0 limit 1",
            String.class,
            merchantId);
      if (existingCodes.isEmpty()) return requested;
      return MerchantTypeCatalog.find(existingCodes.get(0)).orElse(requested);
   }
   @Transactional
@@ -657,9 +691,9 @@
         args.add(like);
      }
      if (type != null && !type.isBlank()) {
         sql.append(" and m.merchant_type = ?");
         countSql.append(" and m.merchant_type = ?");
         args.add(type);
         sql.append(" and m.merchant_type_code = ?");
         countSql.append(" and m.merchant_type_code = ?");
         args.add(MerchantTypeCatalog.canonicalCode(type));
      }
      if (status != null) {
         sql.append(" and p.audit_status = ?");