| | |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.time.LocalDate; |
| | | import java.time.LocalTime; |
| | | import java.time.ZoneId; |
| | | import java.time.format.DateTimeParseException; |
| | | import java.util.ArrayList; |
| | | import java.util.Arrays; |
| | | import java.util.HashSet; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | |
| | | StringBuilder sql = new StringBuilder(""" |
| | | select m.id, m.merchant_name, m.merchant_type, m.contact_name, m.contact_mobile, |
| | | m.logo_url, m.cover_url, m.address, m.is_recommend, m.sort_no, m.status, |
| | | case |
| | | when m.status <> 1 then '休息中' |
| | | when lower(coalesce(m.business_status, 'open')) in ('closed', '休息中') then '休息中' |
| | | when exists ( |
| | | select 1 |
| | | from merchant_business_hours h |
| | | join merchant_store s on s.id = h.store_id and s.status = 1 and s.deleted = 0 |
| | | where h.merchant_id = m.id |
| | | and ( |
| | | (h.start_time <= h.end_time and current_time() >= h.start_time and current_time() < h.end_time) |
| | | or (h.start_time > h.end_time and (current_time() >= h.start_time or current_time() < h.end_time)) |
| | | ) |
| | | ) then '营业中' |
| | | else '休息中' |
| | | end as business_status, m.created_at, |
| | | m.created_at, |
| | | (select count(*) from merchant_product p where p.merchant_id = m.id and p.deleted = 0) as product_count, |
| | | (select count(*) from benefit_order o where o.merchant_id = m.id and o.deleted = 0) as order_count, |
| | | 0 as reservation_count |
| | |
| | | args.add(createdEnd); |
| | | } |
| | | sql.append(" order by m.is_recommend desc, m.sort_no desc, m.id desc"); |
| | | return page(sql.toString(), countSql.toString(), page, size, args.toArray()); |
| | | PageResult<Map<String, Object>> result = page(sql.toString(), countSql.toString(), page, size, args.toArray()); |
| | | applyBusinessStatus(result.records()); |
| | | return result; |
| | | } |
| | | |
| | | @Transactional(readOnly = true) |
| | | public List<Map<String, Object>> pendingNotices() { |
| | | List<Map<String, Object>> notices = new ArrayList<>(); |
| | | addPendingNotice(notices, "product-audit", "团单审核", "您有 %d 条团单待审核", "/products", |
| | | "select count(*) as pending_count, max(coalesce(submit_time, created_at)) as latest_time from merchant_product where deleted = 0 and audit_status = 1"); |
| | | addPendingNotice(notices, "family-card", "家属卡申请", "您有 %d 条家属卡申请待审核", "/family-card-applications", |
| | | "select count(*) as pending_count, max(apply_time) as latest_time from family_card_application where deleted = 0 and status = '待审核'"); |
| | | addPendingNotice(notices, "upgrade", "升级申请", "您有 %d 条升级申请待审核", "/upgrade-applications", |
| | | "select count(*) as pending_count, max(apply_time) as latest_time from upgrade_application where deleted = 0 and status = '待审核'"); |
| | | addPendingNotice(notices, "merchant-onboarding", "商户进件", "您有 %d 条商户进件待审核", "/merchant-settlement?kind=onboardings", |
| | | "select count(*) as pending_count, max(created_at) as latest_time from merchant_onboarding_application where status = 'pending'"); |
| | | addPendingNotice(notices, "merchant-bank-card", "银行卡申请", "您有 %d 条银行卡申请待审核", "/merchant-settlement?kind=bank-cards", |
| | | "select count(*) as pending_count, max(created_at) as latest_time from merchant_bank_card where status = 'pending'"); |
| | | addPendingNotice(notices, "merchant-withdrawal", "提现审核", "您有 %d 条提现申请待审核", "/merchant-settlement?kind=withdrawals", |
| | | "select count(*) as pending_count, max(created_at) as latest_time from merchant_withdrawal where status = 'pending'"); |
| | | addPendingNotice(notices, "medical-reservation", "体检预约", "您有 %d 条体检预约待处理", "/medical-reservations", |
| | | "select count(*) as pending_count, max(submitted_at) as latest_time from service_application where deleted = 0 and application_type = 'MEDICAL_RESERVATION' and status = '待处理'"); |
| | | addPendingNotice(notices, "medical-application", "就医申请", "您有 %d 条就医申请待处理", "/medical-applications", |
| | | "select count(*) as pending_count, max(submitted_at) as latest_time from service_application where deleted = 0 and application_type = 'MEDICAL_APPLICATION' and status = '待处理'"); |
| | | notices.sort((left, right) -> String.valueOf(right.get("time")).compareTo(String.valueOf(left.get("time")))); |
| | | return notices; |
| | | } |
| | | |
| | | private void addPendingNotice(List<Map<String, Object>> notices, String id, String category, String template, String path, String sql) { |
| | | Map<String, Object> summary = DatabaseRows.normalize(jdbcTemplate.queryForMap(sql)); |
| | | long count = summary.get("pendingCount") instanceof Number number ? number.longValue() : 0; |
| | | if (count == 0) return; |
| | | Map<String, Object> notice = new LinkedHashMap<>(); |
| | | notice.put("id", id); |
| | | notice.put("category", category); |
| | | notice.put("content", template.formatted(count)); |
| | | notice.put("time", summary.get("latestTime")); |
| | | notice.put("path", path); |
| | | notices.add(notice); |
| | | } |
| | | |
| | | @Transactional(readOnly = true) |
| | |
| | | @Transactional |
| | | public Map<String, Object> createMerchant(MerchantCreateRequest request) { |
| | | validateMerchantAddress(request.merchantType(), request.address()); |
| | | validateScenicMerchantCreate(request); |
| | | validateRequiredMerchantCreateFields(request); |
| | | jdbcTemplate.update(""" |
| | | insert into merchant(merchant_name, merchant_type, contact_name, contact_mobile, address, |
| | | logo_url, cover_url, business_status, longitude, latitude, can_login_merchant_app, group_buying_enabled, |
| | |
| | | return MERCHANT_APP_LOGIN_TYPES.contains(merchantType); |
| | | } |
| | | |
| | | private void validateScenicMerchantCreate(MerchantCreateRequest request) { |
| | | if (!"景区服务".equals(request.merchantType())) { |
| | | private void validateRequiredMerchantCreateFields(MerchantCreateRequest request) { |
| | | if (!Set.of("景区服务", "餐饮服务").contains(request.merchantType())) { |
| | | return; |
| | | } |
| | | if (request.businessHours() == null || request.businessHours().isEmpty() |
| | |
| | | return new PageResult<>(records, safePage, safeSize, total == null ? 0 : total); |
| | | } |
| | | |
| | | private void applyBusinessStatus(List<Map<String, Object>> merchants) { |
| | | if (merchants.isEmpty()) return; |
| | | List<Long> merchantIds = merchants.stream().map(row -> Long.valueOf(String.valueOf(row.get("id")))).toList(); |
| | | String placeholders = String.join(",", java.util.Collections.nCopies(merchantIds.size(), "?")); |
| | | List<Map<String, Object>> periods = DatabaseRows.normalize(jdbcTemplate.queryForList(""" |
| | | select h.merchant_id, h.start_time, h.end_time |
| | | from merchant_business_hours h |
| | | join merchant_store s on s.id = h.store_id |
| | | where s.status = 1 and s.deleted = 0 and h.merchant_id in (""" + placeholders + ")", merchantIds.toArray())); |
| | | LocalTime now = LocalTime.now(ZoneId.of("Asia/Shanghai")); |
| | | Set<Long> openMerchantIds = new HashSet<>(); |
| | | for (Map<String, Object> period : periods) { |
| | | if (isWithinBusinessPeriod(now, String.valueOf(period.get("startTime")), String.valueOf(period.get("endTime")))) { |
| | | openMerchantIds.add(Long.valueOf(String.valueOf(period.get("merchantId")))); |
| | | } |
| | | } |
| | | for (Map<String, Object> merchant : merchants) { |
| | | Long merchantId = Long.valueOf(String.valueOf(merchant.get("id"))); |
| | | boolean enabled = "1".equals(String.valueOf(merchant.get("status"))); |
| | | merchant.put("businessStatus", enabled && openMerchantIds.contains(merchantId) ? "营业中" : "休息中"); |
| | | } |
| | | } |
| | | |
| | | private boolean isWithinBusinessPeriod(LocalTime now, String startValue, String endValue) { |
| | | try { |
| | | LocalTime start = LocalTime.parse(startValue); |
| | | LocalTime end = LocalTime.parse(endValue); |
| | | return start.isBefore(end) |
| | | ? !now.isBefore(start) && now.isBefore(end) |
| | | : !now.isBefore(start) || now.isBefore(end); |
| | | } catch (DateTimeParseException ignored) { |
| | | return false; |
| | | } |
| | | } |
| | | |
| | | private void ensureUpdated(int updated) { |
| | | if (updated == 0) { |
| | | throw new BusinessException(ErrorCode.NOT_FOUND, "Data not found"); |