| | |
| | | select id, title, summary, cover_url, source, view_count, is_top, publish_time |
| | | from news_article |
| | | where deleted = 0 and status = 1 |
| | | order by is_top desc, case when is_top = 1 then top_time end desc, publish_time desc, id desc |
| | | order by is_top desc, case when is_top = 1 then updated_at end desc, publish_time desc, id desc |
| | | limit 4 |
| | | """)); |
| | | result.put("policies", query(""" |
| | |
| | | select id, title, summary, cover_url, source, content, view_count, is_top, publish_time |
| | | from news_article |
| | | where deleted = 0 and status = 1 |
| | | order by is_top desc, case when is_top = 1 then top_time end desc, publish_time desc, id desc |
| | | order by is_top desc, case when is_top = 1 then updated_at end desc, publish_time desc, id desc |
| | | """, "select count(*) from news_article where deleted = 0 and status = 1", page, size); |
| | | List<Map<String, Object>> records = result.records().stream().map(row -> { |
| | | Map<String, Object> item = new LinkedHashMap<>(row); |
| | |
| | | String mode, |
| | | String sortField, |
| | | String sortOrder, |
| | | Long talentId, |
| | | int page, |
| | | int size |
| | | ) { |
| | |
| | | } |
| | | PageResult<Map<String, Object>> result = page(""" |
| | | select id, merchant_name, merchant_type_code as merchant_type, contact_name, contact_mobile, logo_url, cover_url, address, |
| | | is_recommend, sort_no, longitude, latitude, business_status, |
| | | is_recommend, sort_no, longitude, latitude, business_status, service_label, merchant_rights_config, |
| | | (exists(select 1 from merchant_coupon c |
| | | where c.merchant_id = merchant.id and c.deleted = 0 and c.status = 1 |
| | | and (c.valid_start is null or c.valid_start <= current_date) |
| | | and (c.valid_end is null or c.valid_end >= current_date)) |
| | | or (merchant.merchant_type_code = 'cinema' and exists(select 1 from ticket_package tp |
| | | where tp.deleted = 0 and tp.status = 1 |
| | | and (tp.valid_start is null or tp.valid_start <= current_date) |
| | | and (tp.valid_end is null or tp.valid_end >= current_date) |
| | | and (tp.merchant_id = merchant.id or exists ( |
| | | select 1 from ticket_package_merchant tpm |
| | | where tpm.package_id = tp.id and tpm.merchant_id = merchant.id and tpm.deleted = 0 |
| | | ))))) as coupon_available, |
| | | (select min(p.sale_price) from merchant_product p where p.merchant_id = merchant.id and p.deleted = 0 and p.status = 1 and p.audit_status = 2) as sale_price, |
| | | (select min(p.original_price) from merchant_product p where p.merchant_id = merchant.id and p.deleted = 0 and p.status = 1 and p.audit_status = 2) as original_price |
| | | from merchant |
| | | """ + merchantWhere + " order by sort_no asc, id asc", |
| | | "select count(*) from merchant" + merchantWhere, page, size, args.toArray()); |
| | | List<Map<String, Object>> records = result.records().stream().map(this::enrichMerchantSearchRow).toList(); |
| | | List<Map<String, Object>> records = result.records().stream().map(row -> { |
| | | Map<String, Object> item = enrichMerchantSearchRow(row); |
| | | Long merchantId = item.get("id") instanceof Number id ? id.longValue() : null; |
| | | item.put("couponAvailable", booleanValue(row.get("couponAvailable"))); |
| | | item.put("discountText", merchantId == null ? "" : merchantLowestTalentDiscount(merchantId, talentId)); |
| | | item.put("benefitTags", merchantListBenefitTags(item, row, talentId)); |
| | | item.remove("merchantRightsConfig"); |
| | | return item; |
| | | }).toList(); |
| | | return new PageResult<>(records, result.page(), result.size(), result.total()); |
| | | } |
| | | |
| | |
| | | } |
| | | |
| | | private List<String> stayBenefitTags(Map<String, Object> right, Map<String, Object> merchant) { |
| | | List<String> tags = new ArrayList<>(); |
| | | Object merchantId = merchant.get("id"); |
| | | if (merchantId instanceof Number id && hasActiveMerchantCoupon(id.longValue())) tags.add("券"); |
| | | boolean couponAvailable = merchantId instanceof Number id && hasActiveMerchantCoupon(id.longValue()); |
| | | return stayBenefitTags(right, couponAvailable); |
| | | } |
| | | |
| | | private List<String> stayBenefitTags(Map<String, Object> right, boolean couponAvailable) { |
| | | List<String> tags = new ArrayList<>(); |
| | | if (couponAvailable) tags.add("券"); |
| | | if (right == null) return tags; |
| | | if (booleanValue(right.get("freeVoucherEnabled"))) { |
| | | String times = textOrDefault(right.get("freeVoucherTimes"), ""); |
| | |
| | | String discount = textOrDefault(right.get("consumeDiscount"), ""); |
| | | if ("折扣".equals(type) && !discount.isBlank()) tags.add(discount + "折"); |
| | | } |
| | | return tags; |
| | | } |
| | | |
| | | private List<String> merchantListBenefitTags(Map<String, Object> item, Map<String, Object> row, Long talentId) { |
| | | String merchantType = MerchantTypeCatalog.canonicalCode(textOrDefault(item.get("merchantType"), "")); |
| | | boolean couponAvailable = booleanValue(item.get("couponAvailable")); |
| | | String discountText = textOrDefault(item.get("discountText"), "").trim(); |
| | | String serviceLabel = textOrDefault(row.get("serviceLabel"), "").trim(); |
| | | List<String> tags = new ArrayList<>(); |
| | | |
| | | if ("food".equals(merchantType)) { |
| | | if (couponAvailable) tags.add("券"); |
| | | if (!discountText.isBlank()) tags.add(discountText); |
| | | return tags; |
| | | } |
| | | if ("cinema".equals(merchantType)) { |
| | | if (couponAvailable) tags.add("券"); |
| | | return tags; |
| | | } |
| | | // The travel module displays the calculated discount, not the merchant's |
| | | // service label, so search results must use the same source. |
| | | if ("scenic".equals(merchantType)) { |
| | | if (!discountText.isBlank()) tags.add(discountText); |
| | | return tags; |
| | | } |
| | | |
| | | String cardLevel = activeProductCardLevel(talentId); |
| | | String sourceAreaType = activeTalentSourceAreaType(talentId); |
| | | Map<String, Object> right = matchingMerchantRight( |
| | | merchantRightsConfigs(row.get("merchantRightsConfig")), cardLevel, sourceAreaType, true); |
| | | if ("hotel".equals(merchantType) || "apartment".equals(merchantType)) { |
| | | tags.addAll(stayBenefitTags(right, couponAvailable)); |
| | | if (tags.isEmpty()) tags.add(!discountText.isBlank() ? discountText : "人才专享"); |
| | | return tags; |
| | | } |
| | | |
| | | boolean shoppingType = MerchantTypeCatalog.find(merchantType) |
| | | .map(type -> type.shoppingCategoryKey() != null) |
| | | .orElse(false); |
| | | if (shoppingType) { |
| | | if (couponAvailable) tags.add("券"); |
| | | if ("culture".equals(merchantType)) { |
| | | if (right != null && booleanValue(right.get("storeConsume")) && booleanValue(right.get("consumeEnabled"))) { |
| | | tags.add("到店消费"); |
| | | } |
| | | } else if (right != null) { |
| | | String configuredDiscount = merchantRightDiscountText(right); |
| | | if (!configuredDiscount.isBlank()) tags.add(configuredDiscount); |
| | | } |
| | | return tags; |
| | | } |
| | | |
| | | String categoryTag = !serviceLabel.isBlank() ? serviceLabel : discountText; |
| | | if (categoryTag.isBlank()) categoryTag = "人才专享"; |
| | | tags.add(categoryTag); |
| | | return tags; |
| | | } |
| | | |
| | |
| | | PageResult<Map<String, Object>> result = page(""" |
| | | select id, merchant_name, merchant_type_code as merchant_type, contact_name, contact_mobile, logo_url, cover_url, address, |
| | | service_label, status, sort_no, longitude, latitude, business_status, |
| | | (exists(select 1 from merchant_coupon c |
| | | where c.merchant_id = merchant.id and c.deleted = 0 and c.status = 1 |
| | | and (c.valid_start is null or c.valid_start <= current_date) |
| | | and (c.valid_end is null or c.valid_end >= current_date)) |
| | | or exists(select 1 from ticket_package p |
| | | where p.deleted = 0 and p.status = 1 |
| | | and (p.valid_start is null or p.valid_start <= current_date) |
| | | and (p.valid_end is null or p.valid_end >= current_date) |
| | | and (p.merchant_id = merchant.id or exists ( |
| | | select 1 from ticket_package_merchant pm |
| | | where pm.package_id = p.id and pm.merchant_id = merchant.id and pm.deleted = 0 |
| | | )))) as coupon_available, |
| | | (select min(p.sale_price) from merchant_product p where p.merchant_id = merchant.id and p.deleted = 0 and p.status = 1 and p.audit_status = 2) as sale_price, |
| | | (select min(p.original_price) from merchant_product p where p.merchant_id = merchant.id and p.deleted = 0 and p.status = 1 and p.audit_status = 2) as original_price |
| | | from merchant |
| | |
| | | "select count(*) from merchant" + merchantWhere, page, size); |
| | | List<Map<String, Object>> records = result.records().stream().map(row -> { |
| | | Map<String, Object> item = enrichMerchantSearchRow(row); |
| | | item.put("discountText", textOrDefault(item.get("serviceLabel"), textOrDefault(item.get("discountText"), "人才专享"))); |
| | | boolean couponAvailable = booleanValue(item.get("couponAvailable")); |
| | | item.put("couponAvailable", couponAvailable); |
| | | item.put("discountText", couponAvailable ? "券" : ""); |
| | | return item; |
| | | }).toList(); |
| | | return new PageResult<>(records, result.page(), result.size(), result.total()); |
| | |
| | | } |
| | | |
| | | @Transactional(readOnly = true) |
| | | public PageResult<Map<String, Object>> transportMerchants(String type, int page, int size) { |
| | | public PageResult<Map<String, Object>> transportMerchants(String type, int page, int size, Long talentId) { |
| | | String requestedType = MerchantTypeCatalog.canonicalCode(type); |
| | | if (!List.of("fuel", "parking", "carWash").contains(requestedType)) { |
| | | throw new BusinessException(ErrorCode.BAD_REQUEST, "不支持的行服务类型"); |
| | |
| | | from merchant |
| | | """ + merchantWhere + " order by sort_no asc, id asc", |
| | | "select count(*) from merchant" + merchantWhere, page, size); |
| | | String cardLevel = "carWash".equals(requestedType) ? activeProductCardLevel(talentId) : null; |
| | | String sourceAreaType = "carWash".equals(requestedType) ? activeTalentSourceAreaType(talentId) : ""; |
| | | List<Map<String, Object>> records = result.records().stream().map(row -> { |
| | | Map<String, Object> item = enrichMerchantSearchRow(row); |
| | | item.put("merchantType", requestedType); |
| | | item.put("discountText", textOrDefault(item.get("serviceLabel"), textOrDefault(item.get("discountText"), "人才专享"))); |
| | | if ("carWash".equals(requestedType)) { |
| | | item.put("externalLinkType", carWashExternalLinkType(item.get("merchantRightsConfig"))); |
| | | List<Map<String, Object>> rights = merchantRightsConfigs(item.get("merchantRightsConfig")); |
| | | String externalLinkType = carWashExternalLinkType(rights); |
| | | Map<String, Object> right = matchingMerchantRight(rights, cardLevel, sourceAreaType, true); |
| | | String discountText = carWashListDiscountText(externalLinkType, right); |
| | | item.put("externalLinkType", externalLinkType); |
| | | item.put("discountText", discountText); |
| | | item.put("benefitTagVisible", !discountText.isBlank()); |
| | | } else { |
| | | item.put("discountText", textOrDefault(item.get("serviceLabel"), textOrDefault(item.get("discountText"), "人才专享"))); |
| | | } |
| | | item.remove("merchantRightsConfig"); |
| | | return item; |
| | |
| | | |
| | | @Transactional(readOnly = true) |
| | | public PageResult<Map<String, Object>> shoppingMerchants(String categoryId, int page, int size) { |
| | | return shoppingMerchants(categoryId, page, size, null); |
| | | } |
| | | |
| | | @Transactional(readOnly = true) |
| | | public PageResult<Map<String, Object>> shoppingMerchants(String categoryId, int page, int size, Long talentId) { |
| | | if (categoryId == null || categoryId.isBlank()) throw new BusinessException(ErrorCode.BAD_REQUEST, "请选择购物服务分类"); |
| | | int categoryCount = jdbcTemplate.queryForObject(""" |
| | | select count(*) from shopping_topic_category |
| | |
| | | args.addAll(merchantTypeCodes); |
| | | PageResult<Map<String, Object>> result = page(""" |
| | | select m.id, m.merchant_name, m.merchant_type_code as merchant_type, m.contact_name, m.contact_mobile, m.logo_url, m.cover_url, m.address, |
| | | m.service_label, m.status, m.sort_no, m.longitude, m.latitude, m.business_status |
| | | m.service_label, m.status, m.sort_no, m.longitude, m.latitude, m.business_status, m.merchant_rights_config |
| | | from merchant m |
| | | left join shopping_topic_merchant mapping on mapping.merchant_id = m.id and mapping.deleted = 0 and mapping.category_key = ? |
| | | """ + merchantWhere + " order by mapping.sort_no asc, m.sort_no asc, m.id asc", |
| | | "select count(*) from merchant m left join shopping_topic_merchant mapping on mapping.merchant_id = m.id and mapping.deleted = 0 and mapping.category_key = ? " + merchantWhere, |
| | | page, size, args.toArray()); |
| | | String cardLevel = activeProductCardLevel(talentId); |
| | | String sourceAreaType = activeTalentSourceAreaType(talentId); |
| | | List<Map<String, Object>> records = result.records().stream().map(row -> { |
| | | Map<String, Object> item = enrichMerchantSearchRow(row); |
| | | item.put("discountText", textOrDefault(item.get("serviceLabel"), "人才专享")); |
| | | List<Map<String, Object>> rights = merchantRightsConfigs(row.get("merchantRightsConfig")); |
| | | Map<String, Object> configuredRight = matchingMerchantRight(rights, cardLevel, sourceAreaType, true); |
| | | Map<String, Object> levelRight = matchingMerchantRight(rights, cardLevel, sourceAreaType, false); |
| | | Long merchantId = row.get("id") instanceof Number number ? number.longValue() : null; |
| | | item.put("couponAvailable", merchantId != null && hasActiveMerchantCoupon(merchantId)); |
| | | item.put("storeConsumeAvailable", configuredRight != null |
| | | && booleanValue(configuredRight.get("storeConsume")) |
| | | && booleanValue(configuredRight.get("consumeEnabled"))); |
| | | if (configuredRight != null) { |
| | | item.put("discountText", merchantRightDiscountText(configuredRight)); |
| | | item.put("rightsAvailable", true); |
| | | } else if (levelRight != null || !rights.isEmpty()) { |
| | | item.put("discountText", levelRight == null ? "" : merchantRightDiscountText(levelRight)); |
| | | item.put("rightsAvailable", false); |
| | | item.put("available", false); |
| | | item.put("unavailableReason", levelRight == null ? "当前人才卡暂未配置该权益" : "当前人才卡暂未开通该权益"); |
| | | } else { |
| | | item.put("discountText", textOrDefault(item.get("serviceLabel"), "")); |
| | | item.put("rightsAvailable", true); |
| | | } |
| | | item.remove("merchantRightsConfig"); |
| | | return item; |
| | | }).toList(); |
| | | return new PageResult<>(records, result.page(), result.size(), result.total()); |
| | | } |
| | | |
| | | private String merchantRightDiscountText(Map<String, Object> right) { |
| | | if (!booleanValue(right.get("storeConsume")) || !booleanValue(right.get("consumeEnabled"))) return ""; |
| | | String discount = textOrDefault(right.get("discount"), "").trim(); |
| | | if (discount.isBlank()) return ""; |
| | | try { |
| | | BigDecimal value = new BigDecimal(discount).stripTrailingZeros(); |
| | | if (value.compareTo(BigDecimal.ZERO) <= 0 || value.compareTo(BigDecimal.TEN) > 0) return ""; |
| | | return value.toPlainString() + "折"; |
| | | } catch (NumberFormatException ignored) { |
| | | return ""; |
| | | } |
| | | } |
| | | |
| | | @Transactional(readOnly = true) |
| | |
| | | } |
| | | } |
| | | |
| | | private String carWashExternalLinkType(Object rightsConfig) { |
| | | for (Map<String, Object> right : merchantRightsConfigs(rightsConfig)) { |
| | | private String carWashExternalLinkType(List<Map<String, Object>> rights) { |
| | | for (Map<String, Object> right : rights) { |
| | | if (right.containsKey("externalLinkType") |
| | | || right.containsKey("externalMiniProgramShortLink") |
| | | || right.containsKey("externalMiniProgramPath") |
| | |
| | | return ""; |
| | | } |
| | | |
| | | private String carWashListDiscountText(String externalLinkType, Map<String, Object> right) { |
| | | if ("miniProgram".equals(externalLinkType) || right == null) return ""; |
| | | return merchantRightDiscountText(right); |
| | | } |
| | | |
| | | private boolean booleanValue(Object value) { |
| | | if (value instanceof Boolean bool) return bool; |
| | | if (value instanceof Number number) return number.intValue() != 0; |