| | |
| | | import java.time.LocalDateTime; |
| | | import java.time.temporal.TemporalAdjusters; |
| | | import java.util.ArrayList; |
| | | import java.util.Collections; |
| | | import java.util.LinkedHashMap; |
| | | import java.util.List; |
| | | import java.util.Map; |
| | | import java.util.Collections; |
| | | import java.util.Locale; |
| | | |
| | | 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.common.BusinessException; |
| | | import com.talentcard.server.common.ErrorCode; |
| | | import org.jsoup.Jsoup; |
| | |
| | | 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(""" |
| | |
| | | limit 8 |
| | | """)); |
| | | result.put("recommendedMerchants", query(""" |
| | | select id, merchant_name, merchant_type, logo_url, cover_url, address |
| | | select id, merchant_name, merchant_type_code as merchant_type, logo_url, cover_url, address |
| | | from merchant |
| | | where deleted = 0 and status = 1 and is_recommend = 1 |
| | | order by sort_no desc, id desc |
| | |
| | | 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, contact_name, contact_mobile, logo_url, cover_url, address, |
| | | is_recommend, sort_no, longitude, latitude, business_status, |
| | | 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, 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()); |
| | | } |
| | | |
| | |
| | | |
| | | @Transactional(readOnly = true) |
| | | public PageResult<Map<String, Object>> foodMerchants(int page, int size, Long talentId) { |
| | | String merchantWhere = " where deleted = 0 and merchant_type in ('food', '餐饮服务')"; |
| | | String merchantWhere = " where deleted = 0 and merchant_type_code = 'food'"; |
| | | PageResult<Map<String, Object>> result = page(""" |
| | | select id, merchant_name, merchant_type, contact_name, contact_mobile, logo_url, cover_url, address, |
| | | select id, merchant_name, merchant_type_code as merchant_type, contact_name, contact_mobile, logo_url, cover_url, address, |
| | | status, sort_no, longitude, latitude, business_status, |
| | | (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 |
| | |
| | | |
| | | @Transactional(readOnly = true) |
| | | public PageResult<Map<String, Object>> travelMerchants(int page, int size) { |
| | | String merchantWhere = " where deleted = 0 and merchant_type in ('scenic', '景区服务')"; |
| | | String merchantWhere = " where deleted = 0 and merchant_type_code = 'scenic'"; |
| | | PageResult<Map<String, Object>> result = page(""" |
| | | select id, merchant_name, merchant_type, contact_name, contact_mobile, logo_url, cover_url, address, |
| | | select id, merchant_name, merchant_type_code as merchant_type, contact_name, contact_mobile, logo_url, cover_url, address, |
| | | status, sort_no, longitude, latitude, business_status, |
| | | (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 |
| | |
| | | |
| | | @Transactional(readOnly = true) |
| | | public PageResult<Map<String, Object>> stayMerchants(String type, int page, int size, Long talentId) { |
| | | String requestedType = type == null ? "" : type.trim(); |
| | | String requestedType = MerchantTypeCatalog.canonicalCode(type); |
| | | boolean apartment = switch (requestedType) { |
| | | case "hotel", "酒店服务" -> false; |
| | | case "apartment", "公寓租赁", "公寓租赁服务" -> true; |
| | | case "hotel" -> false; |
| | | case "apartment" -> true; |
| | | default -> throw new BusinessException(ErrorCode.BAD_REQUEST, "不支持的住宿服务类型"); |
| | | }; |
| | | String responseMerchantType = apartment ? "apartment" : "hotel"; |
| | | String merchantWhere = apartment |
| | | ? " where deleted = 0 and merchant_type in ('apartment', '公寓租赁', '公寓租赁服务')" |
| | | : " where deleted = 0 and merchant_type in ('hotel', '酒店服务')"; |
| | | String merchantWhere = " where deleted = 0 and merchant_type_code = '" + responseMerchantType + "'"; |
| | | PageResult<Map<String, Object>> result = page(""" |
| | | select id, merchant_name, merchant_type, contact_name, contact_mobile, logo_url, cover_url, address, |
| | | select id, merchant_name, merchant_type_code as merchant_type, contact_name, contact_mobile, logo_url, cover_url, address, |
| | | status, sort_no, longitude, latitude, business_status, group_buying_enabled, merchant_rights_config, |
| | | (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 |
| | |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | |
| | |
| | | |
| | | @Transactional(readOnly = true) |
| | | public PageResult<Map<String, Object>> entertainmentMerchants(int page, int size) { |
| | | String merchantWhere = " where deleted = 0 and merchant_type in ('cinema', 'theatre', '观影服务', '电影院', '影院', '影城', '剧院')"; |
| | | String merchantWhere = " where deleted = 0 and merchant_type_code = 'cinema'"; |
| | | PageResult<Map<String, Object>> result = page(""" |
| | | select id, merchant_name, merchant_type, contact_name, contact_mobile, logo_url, cover_url, address, |
| | | 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) { |
| | | if (!List.of("fuel", "parking", "carWash").contains(type)) { |
| | | 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, "不支持的行服务类型"); |
| | | } |
| | | String requestedType = type; |
| | | String merchantWhere = switch (type) { |
| | | case "fuel" -> " where deleted = 0 and merchant_type in ('fuel', '加油服务')"; |
| | | case "parking" -> " where deleted = 0 and merchant_type in ('parking', '停车服务')"; |
| | | case "carWash" -> " where deleted = 0 and merchant_type in ('carWash', '自助洗车', '自助洗车服务')"; |
| | | default -> throw new BusinessException(ErrorCode.BAD_REQUEST, "不支持的行服务类型"); |
| | | }; |
| | | String merchantWhere = " where deleted = 0 and merchant_type_code = '" + requestedType + "'"; |
| | | PageResult<Map<String, Object>> result = page(""" |
| | | select id, merchant_name, merchant_type, contact_name, contact_mobile, logo_url, cover_url, address, |
| | | service_label, status, sort_no, longitude, latitude, business_status, |
| | | 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, merchant_rights_config, |
| | | (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); |
| | | 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)) { |
| | | 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; |
| | | }).toList(); |
| | | return new PageResult<>(records, result.page(), result.size(), result.total()); |
| | |
| | | |
| | | @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 |
| | | where category_key = ? and enabled = 1 and deleted = 0 |
| | | """, Integer.class, categoryId); |
| | | if (categoryCount == 0) throw new BusinessException(ErrorCode.BAD_REQUEST, "不支持的购物服务分类"); |
| | | String merchantTypeFilter = switch (categoryId) { |
| | | case "housing" -> "('购房优惠')"; |
| | | case "car" -> "('购车服务')"; |
| | | case "appliance" -> "('家电服务')"; |
| | | case "digital" -> "('数码服务')"; |
| | | case "book" -> "('文化服务', '图书服务')"; |
| | | default -> "('')"; |
| | | }; |
| | | List<String> merchantTypeCodes = MerchantTypeCatalog.codesForShoppingCategory(categoryId); |
| | | if (merchantTypeCodes.isEmpty()) throw new BusinessException(ErrorCode.BAD_REQUEST, "该购物分类未配置商家类型"); |
| | | String placeholders = String.join(",", java.util.Collections.nCopies(merchantTypeCodes.size(), "?")); |
| | | String merchantWhere = """ |
| | | where m.deleted = 0 |
| | | and (m.merchant_type in %s or (m.merchant_type = 'shopping' and mapping.deleted = 0 and mapping.category_key = ?)) |
| | | """.formatted(merchantTypeFilter); |
| | | and m.merchant_type_code in (%s) |
| | | """.formatted(placeholders); |
| | | List<Object> args = new ArrayList<>(); |
| | | args.add(categoryId); |
| | | args.addAll(merchantTypeCodes); |
| | | PageResult<Map<String, Object>> result = page(""" |
| | | select m.id, m.merchant_name, m.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 |
| | | 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.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, categoryId, categoryId); |
| | | 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) |
| | |
| | | where category_key = ? and enabled = 1 and deleted = 0 |
| | | """, Integer.class, categoryId); |
| | | if (categoryCount == null || categoryCount == 0) throw new BusinessException(ErrorCode.BAD_REQUEST, "不支持的臻享服务分类"); |
| | | String serviceWhere = """ |
| | | where m.deleted = 0 and mapping.deleted = 0 and mapping.category_key = ? |
| | | """; |
| | | MerchantType merchantType = MerchantTypeCatalog.forPremiumCategory(categoryId) |
| | | .orElseThrow(() -> new BusinessException(ErrorCode.BAD_REQUEST, "该臻享分类未配置商家类型")); |
| | | String serviceWhere = " where m.deleted = 0 and m.merchant_type_code = ?"; |
| | | PageResult<Map<String, Object>> result = page(""" |
| | | select m.id, m.merchant_name, m.merchant_type, m.contact_mobile, m.cover_url, m.address, |
| | | select m.id, m.merchant_name, m.merchant_type_code as merchant_type, m.contact_mobile, m.cover_url, m.address, |
| | | m.service_label, m.status, m.sort_no, m.longitude, m.latitude, |
| | | mapping.category_key, mapping.business_type |
| | | mapping.sort_no as topic_sort_no |
| | | from merchant m |
| | | join premium_topic_merchant mapping on mapping.merchant_id = m.id |
| | | left join premium_topic_merchant mapping on mapping.merchant_id = m.id and mapping.deleted = 0 and mapping.category_key = ? |
| | | """ + serviceWhere + " order by mapping.sort_no asc, m.sort_no asc, m.id asc", |
| | | "select count(*) from merchant m join premium_topic_merchant mapping on mapping.merchant_id = m.id " + serviceWhere, |
| | | page, size, categoryId); |
| | | "select count(*) from merchant m left join premium_topic_merchant mapping on mapping.merchant_id = m.id and mapping.deleted = 0 and mapping.category_key = ? " + serviceWhere, |
| | | page, size, categoryId, merchantType.code()); |
| | | List<Map<String, Object>> records = result.records().stream().map(row -> { |
| | | Map<String, Object> item = enrichMerchantSearchRow(row); |
| | | item.put("businessType", row.get("businessType")); |
| | | item.put("targetAction", row.get("businessType")); |
| | | item.put("businessType", merchantType.premiumBusinessType()); |
| | | item.put("targetAction", merchantType.premiumBusinessType()); |
| | | item.put("benefitText", textOrDefault(row.get("serviceLabel"), "人才专享")); |
| | | item.put("discountText", textOrDefault(row.get("serviceLabel"), "人才专享")); |
| | | return item; |
| | |
| | | } |
| | | |
| | | private String categoryName(String merchantType) { |
| | | return switch (merchantType) { |
| | | case "food", "餐饮服务" -> "暖心食享"; |
| | | case "hotel", "apartment" -> "舒心安居"; |
| | | case "cinema", "theatre" -> "怡乐生活"; |
| | | case "scenic", "景区服务" -> "悦览花城"; |
| | | case "fuel", "parking", "carWash" -> "畅行无忧"; |
| | | case "shopping" -> "惠享好物"; |
| | | case "medical", "finance" -> "臻享礼遇"; |
| | | default -> "其他权益"; |
| | | }; |
| | | return MerchantTypeCatalog.find(merchantType).map(MerchantType::topicName).orElse("其他权益"); |
| | | } |
| | | |
| | | private String appendMerchantTypeFilter(String merchantWhere, List<String> merchantTypes, List<Object> args) { |
| | | if (merchantTypes.size() == 1) { |
| | | args.add(merchantTypes.get(0)); |
| | | return merchantWhere + " and merchant_type = ?"; |
| | | return merchantWhere + " and merchant_type_code = ?"; |
| | | } |
| | | args.addAll(merchantTypes); |
| | | String placeholders = String.join(",", Collections.nCopies(merchantTypes.size(), "?")); |
| | | return merchantWhere + " and merchant_type in (" + placeholders + ")"; |
| | | return merchantWhere + " and merchant_type_code in (" + placeholders + ")"; |
| | | } |
| | | |
| | | private List<String> merchantTypesForRequestedValue(String value) { |
| | | List<String> categoryTypes = merchantTypesForCategory(value); |
| | | return categoryTypes.isEmpty() ? List.of(value) : categoryTypes; |
| | | List<String> types = MerchantTypeCatalog.codesForRequestedValue(value); |
| | | return types.isEmpty() ? List.of("other") : types; |
| | | } |
| | | |
| | | private List<String> merchantTypesForCategory(String value) { |
| | | return switch (value) { |
| | | case "暖心食享", "餐饮服务", "food" -> List.of("food", "餐饮服务"); |
| | | case "舒心安居", "hotel" -> List.of("hotel"); |
| | | case "怡乐生活", "cinema", "theatre" -> List.of("cinema", "theatre"); |
| | | case "悦览花城", "scenic", "景区服务" -> List.of("scenic", "景区服务"); |
| | | case "畅行无忧" -> List.of("fuel", "parking", "carWash"); |
| | | case "fuel" -> List.of("fuel"); |
| | | case "parking" -> List.of("parking"); |
| | | case "carWash" -> List.of("carWash"); |
| | | case "惠享好物", "shopping" -> List.of("shopping"); |
| | | case "臻享礼遇" -> List.of("medical", "finance"); |
| | | case "medical" -> List.of("medical"); |
| | | case "finance" -> List.of("finance"); |
| | | default -> List.of(); |
| | | }; |
| | | return MerchantTypeCatalog.codesForRequestedValue(value); |
| | | } |
| | | |
| | | private String detailType(String merchantType) { |
| | | return switch (merchantType) { |
| | | case "cinema" -> "CINEMA"; |
| | | case "theatre" -> "THEATRE"; |
| | | case "shopping" -> "SHOPPING"; |
| | | case "medical" -> "MEDICAL"; |
| | | case "finance" -> "FINANCE"; |
| | | case "carWash" -> "CAR_WASH"; |
| | | case "fuel" -> "FUEL"; |
| | | case "parking" -> "PARKING"; |
| | | default -> "MERCHANT"; |
| | | }; |
| | | return MerchantTypeCatalog.find(merchantType).map(MerchantType::detailType).orElse("MERCHANT"); |
| | | } |
| | | |
| | | private String targetAction(String merchantType) { |
| | |
| | | @Transactional(readOnly = true) |
| | | public Map<String, Object> merchantDetail(Long id, Long talentId) { |
| | | Map<String, Object> merchant = one(""" |
| | | select id, merchant_name, merchant_type, contact_name, contact_mobile, logo_url, cover_url, address, |
| | | select id, merchant_name, merchant_type_code as merchant_type, contact_name, contact_mobile, logo_url, cover_url, address, |
| | | business_status, longitude, latitude, group_buying_enabled, usage_instructions, rights_text, merchant_rights_config, |
| | | service_label, status, is_recommend, sort_no |
| | | from merchant |
| | |
| | | boolean freeVoucherEnabled = false; |
| | | String freeVoucherTimes = ""; |
| | | String freeVoucherCycle = ""; |
| | | String consumeType = ""; |
| | | String consumeDiscount = ""; |
| | | String consumeCoupon = ""; |
| | | boolean storeConsume = false; |
| | | boolean consumeEnabled = false; |
| | | merchant.put("rightsAvailable", configuredRight != null); |
| | | if (configuredRight != null) { |
| | | merchant.put("rightsTag", textOrDefault(configuredRight.get("tag"), "")); |
| | | merchant.put("rightsText", textOrDefault(configuredRight.get("detail"), textOrDefault(merchant.get("rightsText"), ""))); |
| | | merchant.put("applicableCards", textOrDefault(configuredRight.get("tag"), cardLevelText(cardLevel) + "可用")); |
| | | merchant.put("applicableCards", textOrDefault(configuredRight.get("tag"), "")); |
| | | merchant.put("rightsStatus", "AVAILABLE"); |
| | | freeVoucherEnabled = booleanValue(configuredRight.get("freeVoucherEnabled")); |
| | | freeVoucherTimes = textOrDefault(configuredRight.get("freeVoucherTimes"), ""); |
| | | freeVoucherCycle = textOrDefault(configuredRight.get("freeVoucherCycle"), ""); |
| | | consumeType = textOrDefault(configuredRight.get("consumeType"), ""); |
| | | consumeDiscount = textOrDefault(configuredRight.get("consumeDiscount"), ""); |
| | | consumeCoupon = textOrDefault(configuredRight.get("consumeCoupon"), ""); |
| | | storeConsume = booleanValue(configuredRight.get("storeConsume")); |
| | | consumeEnabled = booleanValue(configuredRight.get("consumeEnabled")); |
| | | } else if (levelRight != null) { |
| | | merchant.put("rightsTag", textOrDefault(levelRight.get("tag"), "")); |
| | | merchant.put("rightsText", "当前人才卡暂未开通该权益"); |
| | | merchant.put("applicableCards", textOrDefault(levelRight.get("tag"), cardLevelText(cardLevel) + "可用")); |
| | | merchant.put("applicableCards", textOrDefault(levelRight.get("tag"), "")); |
| | | merchant.put("rightsStatus", "DISABLED"); |
| | | } else if (!rightsConfigs.isEmpty()) { |
| | | merchant.put("rightsTag", ""); |
| | | merchant.put("rightsText", "当前人才卡暂未配置该权益"); |
| | | merchant.put("applicableCards", cardLevelText(cardLevel) + "可用"); |
| | | merchant.put("applicableCards", ""); |
| | | merchant.put("rightsStatus", "MISSING"); |
| | | } else { |
| | | merchant.put("rightsStatus", "UNCONFIGURED"); |
| | |
| | | merchant.put("freeVoucherEnabled", freeVoucherEnabled); |
| | | merchant.put("freeVoucherTimes", freeVoucherTimes); |
| | | merchant.put("freeVoucherCycle", freeVoucherCycle); |
| | | merchant.put("consumeType", consumeType); |
| | | merchant.put("consumeDiscount", consumeDiscount); |
| | | merchant.put("consumeCoupon", consumeCoupon); |
| | | merchant.put("storeConsume", storeConsume); |
| | | merchant.put("consumeEnabled", consumeEnabled); |
| | | merchant.put("usageInstructions", merchantUsageInstructions( |
| | | id, merchant.get("merchantRightsConfig"), cardLevel, merchant.get("usageInstructions"))); |
| | | List<String> heroImages = new ArrayList<>(); |
| | |
| | | } catch (Exception ignored) { |
| | | return List.of(); |
| | | } |
| | | } |
| | | |
| | | private String carWashExternalLinkType(List<Map<String, Object>> rights) { |
| | | for (Map<String, Object> right : rights) { |
| | | if (right.containsKey("externalLinkType") |
| | | || right.containsKey("externalMiniProgramShortLink") |
| | | || right.containsKey("externalMiniProgramPath") |
| | | || right.containsKey("externalWebUrl")) { |
| | | return textOrDefault(right.get("externalLinkType"), ""); |
| | | } |
| | | } |
| | | 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) { |
| | |
| | | PageResult<Map<String, Object>> result = page(sql.toString(), countSql.toString(), page, size, args.toArray()); |
| | | List<Map<String, Object>> records = result.records().stream().map(product -> { |
| | | Map<String, Object> item = new LinkedHashMap<>(product); |
| | | BigDecimal configuredSalePrice = decimal(product.get("salePrice")); |
| | | BigDecimal configuredOriginalPrice = decimal(product.get("originalPrice")); |
| | | BigDecimal originalPrice = decimal(item.get("originalPrice")); |
| | | if (originalPrice.signum() <= 0) originalPrice = decimal(item.get("salePrice")); |
| | | BigDecimal personalizedPrice = personalizedProductPrice(item, talentId); |
| | | item.put("configuredSalePrice", configuredSalePrice); |
| | | item.put("configuredOriginalPrice", configuredOriginalPrice.signum() > 0 ? configuredOriginalPrice : originalPrice); |
| | | item.put("originalPrice", originalPrice); |
| | | item.put("salePrice", personalizedPrice); |
| | | item.put("personalized", talentId != null && personalizedPrice.compareTo(decimal(product.get("salePrice"))) != 0); |