shinnosuke
2026-08-03 1a0f9d5456bc44c2db93c978ab573499a920e294
fix: calculate merchant status from business hours
1个文件已添加
5个文件已修改
88 ■■■■■ 已修改文件
talent-card-admin/src/types/admin.ts 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/views/MerchantCreateView.vue 16 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminBusinessService.java 52 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/business/admin/MerchantBusinessHourRequest.java 14 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/business/admin/MerchantCreateRequest.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-server/src/main/java/com/talentcard/server/business/admin/MerchantUpdateRequest.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
talent-card-admin/src/types/admin.ts
@@ -182,6 +182,7 @@
  rightsText?: string
  merchantRightsConfig?: Array<Record<string, unknown>> | string
  serviceLabel?: string
  businessHours?: Array<{ startTime: string; endTime: string }>
  isRecommend?: number
  sortNo?: number
  status?: number
@@ -210,6 +211,7 @@
  rightsText?: string
  merchantRightsConfig?: Array<Record<string, unknown>>
  serviceLabel?: string
  businessHours?: Array<{ startTime: string; endTime: string }>
  recommend?: number
  sortNo?: number
  status?: number
talent-card-admin/src/views/MerchantCreateView.vue
@@ -358,8 +358,19 @@
function hasValidBusinessHours() {
  return businessHours.length > 0 && businessHours.every((item) => {
    const [startTime, endTime] = item.value
    return Boolean(startTime && endTime && startTime < endTime)
    return Boolean(startTime && endTime && startTime !== endTime)
  })
}
function businessHoursPayload() {
  return businessHours.map((item) => ({ startTime: item.value[0], endTime: item.value[1] }))
}
function resetBusinessHours(hours?: Array<{ startTime: string; endTime: string }>) {
  const normalized = (hours || []).filter((item) => item.startTime && item.endTime)
  businessHours.splice(0, businessHours.length, ...(normalized.length > 0
    ? normalized.map((item, index) => ({ id: Date.now() + index, value: [item.startTime, item.endTime] }))
    : [{ id: Date.now(), value: [] }]))
}
function selectedCoordinates() {
@@ -524,6 +535,7 @@
      rightsText: form.rightsText,
      merchantRightsConfig: rightsConfigs.map((item) => ({ ...item })),
      serviceLabel: form.serviceLabel,
      businessHours: businessHoursPayload(),
      recommend: form.recommend,
      sortNo: Number(form.sortNo || 0),
      status: form.status,
@@ -1035,6 +1047,7 @@
        rightsText: form.rightsText,
        merchantRightsConfig: rightsConfigs.map((item) => ({ ...item })),
        serviceLabel: form.serviceLabel,
        businessHours: businessHoursPayload(),
      recommend: form.recommend,
      sortNo: Number(form.sortNo || 0),
      status: form.status,
@@ -1083,6 +1096,7 @@
            form.canLoginMerchantApp = Number(merchant.canLoginMerchantApp ?? 1)
            form.recommend = Number(merchant.isRecommend ?? 0)
            accountForm.loginAccount = merchant.loginAccount || ''
            resetBusinessHours(merchant.businessHours)
            resetMerchantRights(merchant.merchantRightsConfig)
        })
    }
talent-card-server/src/main/java/com/talentcard/server/business/admin/AdminBusinessService.java
@@ -285,7 +285,7 @@
    @Transactional(readOnly = true)
    public Map<String, Object> merchant(Long id) {
        return one("""
        Map<String, Object> result = one("""
                select id, merchant_name, merchant_type, contact_name, contact_mobile, logo_url, cover_url,
                       address, 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, created_at, updated_at,
@@ -295,6 +295,13 @@
                from merchant
                where deleted = 0 and id = ?
                """, id);
        result.put("businessHours", DatabaseRows.normalize(jdbcTemplate.queryForList("""
                select start_time, end_time
                from merchant_business_hours
                where merchant_id = ?
                order by sort_no, id
                """, id)));
        return result;
    }
    @Transactional(readOnly = true)
@@ -335,6 +342,8 @@
                request.recommend() == null ? defaultRecommend(request.merchantType()) : request.recommend(),
                request.sortNo() == null ? 0 : request.sortNo());
        Long id = jdbcTemplate.queryForObject("select last_insert_id()", Long.class);
        Long storeId = createDefaultStore(id, request.merchantName(), request.address());
        syncMerchantBusinessHours(id, storeId, request.businessHours());
        if (hasMerchantAppAccess(request)) {
            String account = request.loginAccount().trim();
            if (!account.matches("^[A-Za-z0-9]{1,32}$")) {
@@ -345,8 +354,6 @@
            }
            Integer occupied = jdbcTemplate.queryForObject("select count(*) from merchant_operator where account = ?", Integer.class, account);
            if (occupied != null && occupied > 0) throw new BusinessException(ErrorCode.CONFLICT, "商户登录账号已存在");
            jdbcTemplate.update("insert into merchant_store(merchant_id, store_name, address, status) values (?, ?, ?, 1)", id, request.merchantName().trim(), request.address());
            Long storeId = jdbcTemplate.queryForObject("select last_insert_id()", Long.class);
            jdbcTemplate.update("insert into merchant_operator(merchant_id, account, password_hash, display_name, role_code, status) values (?, ?, ?, ?, 'owner', 1)",
                    id, account, MerchantPasswordHasher.encode(request.loginPassword()), request.merchantName().trim());
            Long operatorId = jdbcTemplate.queryForObject("select last_insert_id()", Long.class);
@@ -358,6 +365,43 @@
    private boolean hasMerchantAppAccess(MerchantCreateRequest request) {
        return ("餐饮服务".equals(request.merchantType()) || "景区服务".equals(request.merchantType()))
                && (request.canLoginMerchantApp() == null || request.canLoginMerchantApp() == 1);
    }
    private Long createDefaultStore(Long merchantId, String merchantName, String address) {
        jdbcTemplate.update("insert into merchant_store(merchant_id, store_name, address, status) values (?, ?, ?, 1)",
                merchantId, merchantName.trim(), address);
        return jdbcTemplate.queryForObject("select last_insert_id()", Long.class);
    }
    private Long findOrCreateDefaultStore(Long merchantId, String merchantName, String address) {
        List<Long> stores = jdbcTemplate.queryForList("""
                select id from merchant_store
                where merchant_id = ? and deleted = 0
                order by id
                limit 1
                """, Long.class, merchantId);
        if (stores.isEmpty()) return createDefaultStore(merchantId, merchantName, address);
        Long storeId = stores.get(0);
        jdbcTemplate.update("update merchant_store set store_name = ?, address = ? where id = ?", merchantName.trim(), address, storeId);
        return storeId;
    }
    private void syncMerchantBusinessHours(Long merchantId, Long storeId, List<MerchantBusinessHourRequest> businessHours) {
        if (businessHours == null) return;
        if (businessHours.isEmpty()) {
            throw new BusinessException(ErrorCode.BAD_REQUEST, "请至少配置一个营业时间段");
        }
        if (businessHours.stream().anyMatch(item -> item.startTime().equals(item.endTime()))) {
            throw new BusinessException(ErrorCode.BAD_REQUEST, "营业开始时间和结束时间不能相同");
        }
        jdbcTemplate.update("delete from merchant_business_hours where merchant_id = ? and store_id = ?", merchantId, storeId);
        for (int index = 0; index < businessHours.size(); index++) {
            MerchantBusinessHourRequest item = businessHours.get(index);
            jdbcTemplate.update("""
                    insert into merchant_business_hours(merchant_id, store_id, start_time, end_time, sort_no)
                    values (?, ?, ?, ?, ?)
                    """, merchantId, storeId, item.startTime(), item.endTime(), index + 1);
        }
    }
    private byte defaultRecommend(String merchantType) {
@@ -395,6 +439,8 @@
                request.status() == null ? 1 : request.status(),
                id);
        ensureUpdated(updated);
        Long storeId = findOrCreateDefaultStore(id, request.merchantName(), request.address());
        syncMerchantBusinessHours(id, storeId, request.businessHours());
        return merchant(id);
    }
talent-card-server/src/main/java/com/talentcard/server/business/admin/MerchantBusinessHourRequest.java
New file
@@ -0,0 +1,14 @@
package com.talentcard.server.business.admin;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
public record MerchantBusinessHourRequest(
        @NotBlank
        @Pattern(regexp = "^(?:[01]\\d|2[0-3]):[0-5]\\d$")
        String startTime,
        @NotBlank
        @Pattern(regexp = "^(?:[01]\\d|2[0-3]):[0-5]\\d$")
        String endTime
) {
}
talent-card-server/src/main/java/com/talentcard/server/business/admin/MerchantCreateRequest.java
@@ -3,6 +3,7 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
@@ -37,6 +38,7 @@
        List<Map<String, Object>> merchantRightsConfig,
        @Size(max = 64)
        String serviceLabel,
        List<@Valid MerchantBusinessHourRequest> businessHours,
        Byte canLoginMerchantApp,
        @Size(max = 32) String loginAccount,
        @Size(max = 64) String loginPassword
talent-card-server/src/main/java/com/talentcard/server/business/admin/MerchantUpdateRequest.java
@@ -2,6 +2,7 @@
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;
import jakarta.validation.Valid;
import java.util.List;
import java.util.Map;
@@ -33,6 +34,7 @@
        List<Map<String, Object>> merchantRightsConfig,
        @Size(max = 64)
        String serviceLabel,
        List<@Valid MerchantBusinessHourRequest> businessHours,
        Byte canLoginMerchantApp
) {
}