package com.talentcard.server.business.portal; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import com.alibaba.fastjson.JSON; import com.talentcard.server.business.DatabaseRows; import com.talentcard.server.common.BusinessException; import com.talentcard.server.common.ErrorCode; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class ChildrenEntrustmentPortalService { private final JdbcTemplate jdbc; public ChildrenEntrustmentPortalService(JdbcTemplate jdbc) { this.jdbc = jdbc; } @Transactional(readOnly = true) public Map detail(Long serviceId, Long talentId) { Map merchant = approvalService(serviceId); Map benefit = benefit(); boolean available = number(benefit.get("enabled")) == 1; Map matchedRight = matchedRight(merchant.get("merchantRightsConfig"), activeCardLevel(talentId)); Map result = new LinkedHashMap<>(); result.put("benefitId", benefit.get("id")); result.put("serviceId", serviceId); result.put("title", benefit.get("title")); result.put("applicableCards", text(matchedRight.get("tag"), "")); // 详情页图优先用该商家自己配置的轮播图,没配时回落商家封面图,最后才是服务权益里的全局图。 result.put("heroImage", merchantHeroImage(serviceId, text(merchant.get("coverUrl"), text(benefit.get("heroImage"), "")))); result.put("heroImages", merchantHeroImages(serviceId)); // 使用说明只认商家权益里配置的内容:商家没配(或该人才等级没配)就返回空, // 小程序显示"暂无使用说明",不再回落到服务权益里的历史默认文案。 result.put("rightsText", text(matchedRight.get("detail"), "")); result.put("available", available); result.put("unavailableReason", available ? "" : text(benefit.get("unavailableReason"), "该权益暂未开放")); return result; } @Transactional(readOnly = true) public List> schools(Long serviceId) { Map merchant = approvalService(serviceId); // 只返回商家自己配置的适用学校;没配就是空列表(小程序显示"暂无适用学校"), // 不再回落到历史全局学校表,避免新添加的商家显示别人的学校。 return configuredSchools(merchant.get("usageInstructions")); } private List merchantHeroImages(Long merchantId) { return query(""" select image_url from merchant_hero_image where merchant_id = ? and deleted = 0 and enabled = 1 order by sort_no asc, id asc """, merchantId).stream() .map(row -> text(row.get("imageUrl"), "")) .filter(url -> !url.isBlank()) .toList(); } private String merchantHeroImage(Long merchantId, String fallback) { List images = merchantHeroImages(merchantId); return images.isEmpty() ? fallback : images.get(0); } private List> configuredSchools(Object value) { return configuredSchoolRows(value).stream().map(row -> { Map school = new LinkedHashMap<>(); school.put("id", row.get("id")); school.put("name", row.get("name")); school.put("address", row.get("address")); school.put("latitude", row.get("latitude")); school.put("longitude", row.get("longitude")); school.put("phone", row.get("phone")); school.put("detailAvailable", row.get("detailAvailable")); return school; }).toList(); } private List> configuredSchoolRows(Object value) { String json = text(value, ""); if (json.isBlank()) return List.of(); try { List> rows = JSON.parseObject(json, new com.alibaba.fastjson.TypeReference<>() {}); if (rows == null) return List.of(); return rows.stream().map(row -> { Map school = new LinkedHashMap<>(); String name = text(row.get("name"), "未命名学校"); school.put("id", text(row.get("id"), name)); school.put("name", name); school.put("address", text(row.get("address"), "暂未配置学校地址")); school.put("latitude", row.get("latitude")); school.put("longitude", row.get("longitude")); school.put("phone", text(row.get("phone"), text(row.get("contactMobile"), ""))); String content = text(row.get("detail"), ""); school.put("content", content); school.put("detailAvailable", !content.isBlank()); return school; }).toList(); } catch (RuntimeException ignored) { return List.of(); } } @Transactional(readOnly = true) public Map school(Long serviceId, String schoolId) { if (schoolId == null || schoolId.isBlank()) throw new BusinessException(ErrorCode.BAD_REQUEST, "学校信息不能为空"); Map merchant = approvalService(serviceId); List> configuredSchools = configuredSchoolRows(merchant.get("usageInstructions")); if (!configuredSchools.isEmpty()) { Map matched = configuredSchools.stream() .filter(row -> schoolId.equals(text(row.get("id"), "")) || schoolId.equals(text(row.get("name"), ""))) .findFirst() .orElseThrow(() -> new BusinessException(ErrorCode.NOT_FOUND, "学校不存在或暂未开放")); if (!booleanValue(matched.get("detailAvailable"))) throw new BusinessException(ErrorCode.CONFLICT, "学校介绍暂未开放"); Map result = new LinkedHashMap<>(); result.put("id", matched.get("id")); result.put("name", matched.get("name")); result.put("address", matched.get("address")); result.put("content", matched.get("content")); return result; } // 商家没配学校时不去查历史全局学校表,直接按"学校不存在"返回。 throw new BusinessException(ErrorCode.NOT_FOUND, "学校不存在或暂未开放"); } @Transactional(readOnly = true) public Map school(Long schoolId) { if (schoolId == null || schoolId <= 0) throw new BusinessException(ErrorCode.BAD_REQUEST, "学校信息不能为空"); List> rows = query(""" select id, school_name, address, detail_content, detail_available from children_entrustment_school where id = ? and enabled = 1 and deleted = 0 limit 1 """, schoolId); if (rows.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "学校不存在或暂未开放"); Map row = rows.get(0); if (number(row.get("detailAvailable")) != 1) throw new BusinessException(ErrorCode.CONFLICT, "学校介绍暂未开放"); Map result = new LinkedHashMap<>(); result.put("id", row.get("id")); result.put("name", row.get("schoolName")); result.put("address", text(row.get("address"), "暂未配置学校地址")); result.put("content", text(row.get("detailContent"), "")); return result; } private Map approvalService(Long serviceId) { if (serviceId == null || serviceId <= 0) throw new BusinessException(ErrorCode.BAD_REQUEST, "学生托付服务信息不能为空"); List> rows = query(""" select m.id, m.cover_url, m.merchant_rights_config, m.usage_instructions from merchant m where m.id = ? and m.deleted = 0 and m.merchant_type_code = 'children' """, serviceId); if (rows.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "学生托付服务不存在"); return rows.get(0); } private Map matchedRight(Object value, String cardLevel) { if (cardLevel.isBlank() || value == null) return Map.of(); try { List> rights = JSON.parseObject(String.valueOf(value), new com.alibaba.fastjson.TypeReference<>() {}); if (rights != null) { for (Map right : rights) { if (booleanValue(right.get("enabled")) && cardLevel.equals(text(right.get("level"), ""))) return right; } } } catch (RuntimeException ignored) { // Fall back to the legacy children entrustment description when the configuration is invalid. } return Map.of(); } private String activeCardLevel(Long talentId) { if (talentId == null || talentId <= 0) return ""; List> cards = query(""" select card_level from talent_card where talent_id = ? and deleted = 0 and card_type = 'PERSONAL' and status = 1 and (valid_end is null or valid_end >= current_date) order by id desc limit 1 """, talentId); return cards.isEmpty() ? "" : text(cards.get(0).get("cardLevel"), ""); } private boolean booleanValue(Object value) { if (value instanceof Boolean bool) return bool; if (value instanceof Number number) return number.intValue() != 0; String result = text(value, ""); return "true".equalsIgnoreCase(result) || "1".equals(result) || "yes".equalsIgnoreCase(result); } private Map benefit() { List> rows = query(""" select id, title, applicable_cards, hero_image, rights_text, enabled, unavailable_reason from children_entrustment_benefit where deleted = 0 order by id asc limit 1 """); if (rows.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "学生托付权益暂未配置"); return rows.get(0); } private List> query(String sql, Object... args) { return DatabaseRows.normalize(jdbc.queryForList(sql, args)); } private int number(Object value) { return value instanceof Number number ? number.intValue() : Integer.parseInt(String.valueOf(value)); } private String text(Object value, String fallback) { String result = value == null ? "" : String.valueOf(value).trim(); return result.isBlank() ? fallback : result; } }