| | |
| | | import org.springframework.jdbc.core.JdbcTemplate; |
| | | import org.springframework.stereotype.Service; |
| | | import org.springframework.transaction.annotation.Transactional; |
| | | import tools.jackson.core.type.TypeReference; |
| | | import tools.jackson.databind.ObjectMapper; |
| | | |
| | | @Service |
| | | public class AdminApprovalPortalService { |
| | | |
| | | private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); |
| | | private static final TypeReference<List<Map<String, Object>>> APPROVAL_ITEMS = new TypeReference<>() {}; |
| | | |
| | | private final JdbcTemplate jdbc; |
| | | |
| | |
| | | |
| | | @Transactional(readOnly = true) |
| | | public Map<String, Object> detail(Long serviceId) { |
| | | approvalService(serviceId); |
| | | Map<String, Object> merchant = approvalService(serviceId); |
| | | Map<String, Object> benefit = benefit(); |
| | | Map<String, Object> result = new LinkedHashMap<>(); |
| | | result.put("benefitId", benefit.get("id")); |
| | | result.put("serviceId", serviceId); |
| | | result.put("title", benefit.get("title")); |
| | | result.put("freeLabel", benefit.get("freeLabel")); |
| | | result.put("heroImage", benefit.get("heroImage")); |
| | | result.put("available", number(benefit.get("enabled")) == 1); |
| | | // 行政审批详情页顶部直接用商家封面图(后台不再单独配置「详情页图片」); |
| | | // 商家没配封面时再回落服务权益里的全局图。 |
| | | result.put("heroImage", text(merchant.get("coverUrl"), text(benefit.get("heroImage")))); |
| | | result.put("heroImages", List.of()); |
| | | result.put("available", number(benefit.get("enabled")) == 1 && number(merchant.get("status")) == 1); |
| | | result.put("unavailableReason", text(benefit.get("unavailableReason"), "该权益暂未开放")); |
| | | result.put("organizations", organizations(((Number) benefit.get("id")).longValue())); |
| | | List<Map<String, Object>> configuredOrganizations = configuredOrganizations(merchant); |
| | | result.put("organizations", configuredOrganizations.isEmpty() |
| | | ? organizations(((Number) benefit.get("id")).longValue()) |
| | | : configuredOrganizations); |
| | | return result; |
| | | } |
| | | |
| | | private List<Map<String, Object>> configuredOrganizations(Map<String, Object> merchant) { |
| | | List<Map<String, Object>> items = parseApprovalItems(merchant.get("usageInstructions")); |
| | | if (items.isEmpty()) { |
| | | String rightsText = text(merchant.get("rightsText")); |
| | | if (rightsText.isBlank()) return List.of(); |
| | | items = List.of(Map.of( |
| | | "name", text(merchant.get("merchantName"), "行政审批服务"), |
| | | "content", rightsText, |
| | | "address", text(merchant.get("address")), |
| | | "phone", text(merchant.get("contactMobile")))); |
| | | } |
| | | List<Map<String, Object>> result = new java.util.ArrayList<>(); |
| | | for (int index = 0; index < items.size(); index++) { |
| | | Map<String, Object> item = items.get(index); |
| | | Map<String, Object> organization = new LinkedHashMap<>(); |
| | | organization.put("id", merchant.get("id") + "-" + index); |
| | | organization.put("name", text(item.get("name"), text(merchant.get("merchantName"), "行政审批服务"))); |
| | | organization.put("address", text(item.get("address"), text(merchant.get("address")))); |
| | | organization.put("latitude", merchant.get("latitude")); |
| | | organization.put("longitude", merchant.get("longitude")); |
| | | organization.put("description", text(item.get("content"), text(merchant.get("rightsText"), "暂无权益说明"))); |
| | | organization.put("phone", text(item.get("phone"), text(merchant.get("contactMobile")))); |
| | | organization.put("officeHours", ""); |
| | | String windowInfoUrl = firstText(item, |
| | | "windowInfoUrl", "window_info_url", "pdfFile", "pdf_file", |
| | | "windowInfo", "window_info", "windowFile", "window_file", |
| | | "fileUrl", "file_url"); |
| | | organization.put("windowInfoUrl", windowInfoUrl); |
| | | organization.put("windows", List.of()); |
| | | organization.put("hasWindowInfo", !windowInfoUrl.isBlank()); |
| | | result.add(organization); |
| | | } |
| | | return result; |
| | | } |
| | | |
| | | private List<Map<String, Object>> parseApprovalItems(Object value) { |
| | | if (value == null || String.valueOf(value).isBlank()) return List.of(); |
| | | try { |
| | | return OBJECT_MAPPER.readValue(String.valueOf(value), APPROVAL_ITEMS); |
| | | } catch (Exception ignored) { |
| | | return List.of(); |
| | | } |
| | | } |
| | | |
| | | private List<Map<String, Object>> organizations(Long benefitId) { |
| | | return query(""" |
| | | select id, organization_name, address, latitude, longitude, rights_text, consultation_phone, office_hours |
| | | select id, organization_name, address, latitude, longitude, rights_text, consultation_phone, office_hours, |
| | | pdf_file, is_pdf_visible |
| | | from admin_approval_organization |
| | | where benefit_id = ? and enabled = 1 and deleted = 0 |
| | | order by sort_no asc, id asc |
| | |
| | | organization.put("description", row.get("rightsText")); |
| | | organization.put("phone", text(row.get("consultationPhone"))); |
| | | organization.put("officeHours", text(row.get("officeHours"))); |
| | | String windowInfoUrl = number(row.get("isPdfVisible")) == 1 ? text(row.get("pdfFile")) : ""; |
| | | organization.put("windowInfoUrl", windowInfoUrl); |
| | | organization.put("windows", windows); |
| | | organization.put("hasWindowInfo", !windows.isEmpty()); |
| | | organization.put("hasWindowInfo", !windowInfoUrl.isBlank()); |
| | | return organization; |
| | | }).toList(); |
| | | } |
| | |
| | | private Map<String, Object> approvalService(Long serviceId) { |
| | | if (serviceId == null || serviceId <= 0) throw new BusinessException(ErrorCode.BAD_REQUEST, "行政审批服务信息不能为空"); |
| | | List<Map<String, Object>> rows = query(""" |
| | | select m.id |
| | | select m.id, m.merchant_name, m.address, m.contact_mobile, m.cover_url, m.latitude, m.longitude, |
| | | m.status, m.usage_instructions, m.rights_text |
| | | from merchant m |
| | | where m.id = ? and m.deleted = 0 and m.merchant_type_code = 'approval' |
| | | """, serviceId); |
| | |
| | | String valueText = text(value).trim(); |
| | | return valueText.isBlank() ? fallback : valueText; |
| | | } |
| | | |
| | | /** |
| | | * Window PDFs were stored under several names while the admin form was |
| | | * being rolled out. Keep the portal contract stable by resolving the |
| | | * first non-empty URL from all supported names (including nested upload |
| | | * response objects). |
| | | */ |
| | | private String firstText(Map<String, Object> source, String... keys) { |
| | | for (String key : keys) { |
| | | Object value = source.get(key); |
| | | String resolved = uploadUrl(value); |
| | | if (!resolved.isBlank()) return resolved; |
| | | } |
| | | return ""; |
| | | } |
| | | |
| | | @SuppressWarnings("unchecked") |
| | | private String uploadUrl(Object value) { |
| | | if (value instanceof Map<?, ?> map) { |
| | | for (String key : List.of("url", "path", "fileUrl", "file_url", "downloadUrl", "download_url")) { |
| | | Object nested = map.get(key); |
| | | if (nested != null) { |
| | | String resolved = text(nested).trim(); |
| | | if (!resolved.isBlank()) return resolved; |
| | | } |
| | | } |
| | | return ""; |
| | | } |
| | | return text(value).trim(); |
| | | } |
| | | } |