package com.talentcard.server.business.portal;
|
|
import java.util.LinkedHashMap;
|
import java.util.List;
|
import java.util.Locale;
|
import java.util.Map;
|
|
import com.alibaba.fastjson.JSON;
|
import com.alibaba.fastjson.TypeReference;
|
import com.talentcard.server.business.BusinessIdGenerator;
|
import com.talentcard.server.business.DatabaseRows;
|
import com.talentcard.server.business.PageResult;
|
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 ParkingMerchantPortalService {
|
private final JdbcTemplate jdbc;
|
|
public ParkingMerchantPortalService(JdbcTemplate jdbc) {
|
this.jdbc = jdbc;
|
}
|
|
@Transactional(readOnly = true)
|
public Map<String, Object> detail(Long merchantId, Long talentCardId) {
|
Map<String, Object> merchant = parkingMerchant(merchantId);
|
Map<String, Object> benefit = parkingBenefit(merchantId);
|
String cardLevel = talentCardId == null ? "" : talentCardLevel(talentCardId);
|
List<Map<String, Object>> rights = parseList(merchant.get("merchantRightsConfig"));
|
Map<String, Object> right = matchingRight(rights, cardLevel);
|
boolean hasCardRightsConfig = !rights.isEmpty() && !cardLevel.isBlank();
|
boolean available = number(merchant.get("status")) == 1
|
&& (!hasCardRightsConfig || right != null)
|
&& (right == null || booleanValue(right.get("enabled")));
|
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("merchantId", merchant.get("id"));
|
result.put("merchantName", merchant.get("merchantName"));
|
result.put("merchantType", "parking");
|
result.put("coverUrl", merchant.get("coverUrl"));
|
result.put("address", merchant.get("address"));
|
result.put("contactMobile", merchant.get("contactMobile"));
|
result.put("latitude", merchant.get("latitude"));
|
result.put("longitude", merchant.get("longitude"));
|
result.put("available", available);
|
result.put("unavailableReason", available ? "" : unavailableReason(merchant, right));
|
result.put("serviceLabel", text(merchant.get("serviceLabel"), "停车服务"));
|
result.put("benefitText", benefitText(merchant, right, benefit));
|
result.put("applicableCards", applicableCards(right, benefit));
|
result.put("usageInstructions", text(merchant.get("usageInstructions"), "请咨询商户"));
|
result.put("rightsText", rightsText(merchant, right));
|
String parkingType = text(right == null ? null : right.get("parkingType"), "paid");
|
result.put("actionMode", "free".equalsIgnoreCase(parkingType) ? "talentCode" : "plate");
|
result.put("plateFormMode", text(benefit.get("plateFormMode"), "simple"));
|
result.put("benefit", benefitView(benefit));
|
result.put("applicableStores", parkingStores(merchant.get("usageInstructions")));
|
result.put("merchantRightsConfig", rights);
|
result.put("merchantRight", right == null ? Map.of() : right);
|
result.put("parkingType", parkingType);
|
result.put("parkingScope", text(right == null ? null : right.get("parkingScope"), "all"));
|
result.put("consumeDesc", text(right == null ? null : right.get("consumeDesc"), ""));
|
return result;
|
}
|
|
@Transactional(readOnly = true)
|
public PageResult<Map<String, Object>> parkingLots(Long merchantId, String keyword, int page, int size) {
|
Map<String, Object> merchant = parkingMerchant(merchantId);
|
List<Map<String, Object>> stores = parkingStores(merchant.get("usageInstructions"));
|
String cleaned = keyword == null ? "" : keyword.trim();
|
List<Map<String, Object>> filtered = stores.stream()
|
.filter(item -> cleaned.isEmpty()
|
|| text(item.get("storeName"), "").contains(cleaned)
|
|| text(item.get("parkingLotName"), "").contains(cleaned)
|
|| text(item.get("name"), "").contains(cleaned)
|
|| text(item.get("address"), "").contains(cleaned))
|
.toList();
|
int safePage = Math.max(1, page);
|
int safeSize = Math.min(50, Math.max(1, size));
|
int from = Math.min(filtered.size(), (safePage - 1) * safeSize);
|
int to = Math.min(filtered.size(), from + safeSize);
|
return new PageResult<>(filtered.subList(from, to), safePage, safeSize, filtered.size());
|
}
|
|
@Transactional
|
public Map<String, Object> registerPlate(Long merchantId, ParkingPlateRegistrationRequest request, String idempotencyKey) {
|
if (idempotencyKey == null || idempotencyKey.isBlank() || idempotencyKey.length() > 128) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "提交凭证不能为空");
|
}
|
List<Map<String, Object>> existing = query("""
|
select id, registration_no, status
|
from transport_parking_plate_registration
|
where idempotency_key = ? and deleted = 0
|
""", idempotencyKey);
|
if (!existing.isEmpty()) return registrationView(existing.get(0));
|
|
Map<String, Object> merchant = parkingMerchant(merchantId);
|
if (number(merchant.get("status")) != 1) throw new BusinessException(ErrorCode.CONFLICT, "停车服务当前不可用");
|
Map<String, Object> benefit = one("""
|
select id, action_mode, plate_form_mode, status
|
from transport_parking_benefit
|
where id = ? and merchant_id = ? and deleted = 0
|
""", request.benefitId(), merchantId);
|
if (number(benefit.get("status")) != 1) throw new BusinessException(ErrorCode.CONFLICT, "停车权益当前不可用");
|
if (!"plate".equals(benefit.get("actionMode"))) throw new BusinessException(ErrorCode.CONFLICT, "当前停车权益无需登记车牌");
|
if ("withParking".equals(benefit.get("plateFormMode"))) {
|
if (request.parkingLotId() == null) throw new BusinessException(ErrorCode.BAD_REQUEST, "请选择停车场");
|
boolean matchedLot = parkingStores(merchant.get("usageInstructions")).stream()
|
.anyMatch(item -> String.valueOf(request.parkingLotId()).equals(String.valueOf(item.get("id"))));
|
if (!matchedLot) throw new BusinessException(ErrorCode.BAD_REQUEST, "停车场不可用");
|
}
|
Integer talentCount = jdbc.queryForObject("select count(*) from talent_user where id = ? and deleted = 0", Integer.class, request.talentId());
|
if (talentCount == null || talentCount == 0) throw new BusinessException(ErrorCode.NOT_FOUND, "人才用户不存在");
|
Integer duplicateCount = jdbc.queryForObject("""
|
select count(*) from transport_parking_plate_registration
|
where merchant_id = ? and talent_id = ? and plate_number = ? and deleted = 0
|
and status in ('SUBMITTED', 'ACTIVE')
|
""", Integer.class, merchantId, request.talentId(), request.plateNumber());
|
if (duplicateCount != null && duplicateCount > 0) throw new BusinessException(ErrorCode.CONFLICT, "该车牌已登记,如需变更请联系停车服务商");
|
|
String registrationNo = BusinessIdGenerator.next("PR");
|
jdbc.update("""
|
insert into transport_parking_plate_registration(registration_no, merchant_id, benefit_id, parking_lot_id, talent_id, plate_number, status, idempotency_key)
|
values (?, ?, ?, ?, ?, ?, 'SUBMITTED', ?)
|
""", registrationNo, merchantId, request.benefitId(), request.parkingLotId(), request.talentId(), request.plateNumber(), idempotencyKey);
|
return registrationView(one("""
|
select id, registration_no, status
|
from transport_parking_plate_registration
|
where idempotency_key = ? and deleted = 0
|
""", idempotencyKey));
|
}
|
|
private Map<String, Object> parkingMerchant(Long merchantId) {
|
return one("""
|
select id, merchant_name, contact_mobile, address, cover_url, service_label, usage_instructions,
|
rights_text, merchant_rights_config, status, latitude, longitude
|
from merchant
|
where id = ? and merchant_type_code = 'parking' and deleted = 0
|
""", merchantId);
|
}
|
|
private Map<String, Object> parkingBenefit(Long merchantId) {
|
List<Map<String, Object>> rows = query("""
|
select id, talent_levels, benefit_text, action_mode, plate_form_mode, valid_end, unavailable_reason, status
|
from transport_parking_benefit
|
where merchant_id = ? and deleted = 0
|
order by id asc limit 1
|
""", merchantId);
|
return rows.isEmpty() ? new LinkedHashMap<>() : rows.get(0);
|
}
|
|
private List<Map<String, Object>> parkingStores(Object usageInstructions) {
|
List<Map<String, Object>> stores = parseList(usageInstructions);
|
int index = 0;
|
for (Map<String, Object> store : stores) {
|
Object id = store.get("id");
|
if (id == null) store.put("id", index + 1);
|
Object name = store.get("name");
|
if (name != null && store.get("storeName") == null) store.put("storeName", name);
|
if (name != null && store.get("parkingLotName") == null) store.put("parkingLotName", name);
|
store.put("canNavigate", hasCoordinate(store));
|
index++;
|
}
|
return stores;
|
}
|
|
private Map<String, Object> benefitView(Map<String, Object> benefit) {
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("benefitId", benefit.get("id"));
|
result.put("actionMode", text(benefit.get("actionMode"), "plate"));
|
result.put("plateFormMode", text(benefit.get("plateFormMode"), "simple"));
|
result.put("validEnd", benefit.get("validEnd"));
|
return result;
|
}
|
|
private Map<String, Object> registrationView(Map<String, Object> row) {
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("registrationId", row.get("id"));
|
result.put("registrationNo", row.get("registrationNo"));
|
result.put("status", row.get("status"));
|
return result;
|
}
|
|
private Map<String, Object> matchingRight(List<Map<String, Object>> rights, String cardLevel) {
|
if (rights.isEmpty()) return null;
|
String normalizedCardLevel = normalizedCardLevel(cardLevel);
|
if (normalizedCardLevel.isBlank()) {
|
return rights.stream().filter(item -> booleanValue(item.get("enabled"))).findFirst().orElse(null);
|
}
|
for (Map<String, Object> right : rights) {
|
if (!booleanValue(right.get("enabled"))) continue;
|
if (normalizedCardLevel.equals(normalizedCardLevel(text(right.get("level"), "")))) return right;
|
}
|
return null;
|
}
|
|
private String applicableCards(Map<String, Object> right, Map<String, Object> benefit) {
|
return text(right == null ? null : right.get("level"), text(benefit.get("talentLevels"), "请咨询商户"));
|
}
|
|
private String rightsText(Map<String, Object> merchant, Map<String, Object> right) {
|
if (right != null) {
|
String detail = text(right.get("detail"), "");
|
if (!detail.isBlank()) return detail;
|
String consumeDesc = text(right.get("consumeDesc"), "");
|
if (!consumeDesc.isBlank()) return consumeDesc;
|
String tag = text(right.get("tag"), "");
|
if (!tag.isBlank()) return tag;
|
}
|
return text(merchant.get("rightsText"), "请咨询商户");
|
}
|
|
private String benefitText(Map<String, Object> merchant, Map<String, Object> right, Map<String, Object> benefit) {
|
if (right != null) {
|
String detail = text(right.get("detail"), "");
|
if (!detail.isBlank()) return detail;
|
String tag = text(right.get("tag"), "");
|
if (!tag.isBlank()) return tag;
|
}
|
return text(benefit.get("benefitText"), text(merchant.get("rightsText"), "免费停车"));
|
}
|
|
private String unavailableReason(Map<String, Object> merchant, Map<String, Object> right) {
|
if (number(merchant.get("status")) != 1) return "停车服务当前不可用";
|
if (right == null) return "当前人才卡暂无可用停车权益";
|
return "停车权益当前不可用";
|
}
|
|
private List<Map<String, Object>> parseList(Object value) {
|
if (value instanceof List<?> list) {
|
return list.stream().filter(Map.class::isInstance).map(item -> copyMap((Map<?, ?>) item)).toList();
|
}
|
String text = value == null ? "" : String.valueOf(value).trim();
|
if (text.isBlank()) return List.of();
|
try {
|
List<Map<String, Object>> rows = JSON.parseObject(text, new TypeReference<List<Map<String, Object>>>() {});
|
return rows == null ? List.of() : rows.stream().map(this::copyMap).toList();
|
} catch (Exception ignored) {
|
return List.of();
|
}
|
}
|
|
private Map<String, Object> copyMap(Map<?, ?> source) {
|
Map<String, Object> result = new LinkedHashMap<>();
|
for (Map.Entry<?, ?> entry : source.entrySet()) result.put(String.valueOf(entry.getKey()), entry.getValue());
|
return result;
|
}
|
|
private String talentCardLevel(Long talentCardId) {
|
List<Map<String, Object>> rows = query("select card_level from talent_card where id = ? and deleted = 0 limit 1", talentCardId);
|
return rows.isEmpty() ? "" : text(rows.get(0).get("cardLevel"), "");
|
}
|
|
private String normalizedCardLevel(String value) {
|
String raw = value == null ? "" : value.trim();
|
String upper = raw.toUpperCase(Locale.ROOT);
|
if (upper.contains("EXCELLENT") || raw.contains("卓越")) return "卓越人才卡";
|
if (upper.contains("OUTSTANDING") || raw.contains("杰出")) return "杰出人才卡";
|
if (upper.contains("ELITE") || raw.contains("菁英") || raw.contains("精英")) return "菁英人才卡";
|
return raw;
|
}
|
|
private boolean hasCoordinate(Map<String, Object> store) {
|
return decimal(store.get("latitude")) != 0D && decimal(store.get("longitude")) != 0D;
|
}
|
|
private double decimal(Object value) {
|
if (value instanceof Number number) return number.doubleValue();
|
try {
|
return value == null ? 0D : Double.parseDouble(String.valueOf(value));
|
} catch (Exception ignored) {
|
return 0D;
|
}
|
}
|
|
private boolean booleanValue(Object value) {
|
if (value instanceof Boolean bool) return bool;
|
if (value instanceof Number number) return number.intValue() != 0;
|
String text = value == null ? "" : String.valueOf(value).trim();
|
return "true".equalsIgnoreCase(text) || "1".equals(text) || "yes".equalsIgnoreCase(text);
|
}
|
|
private List<Map<String, Object>> query(String sql, Object... args) {
|
return jdbc.queryForList(sql, args).stream().map(DatabaseRows::normalize).toList();
|
}
|
|
private Map<String, Object> one(String sql, Object... args) {
|
List<Map<String, Object>> rows = query(sql, args);
|
if (rows.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "停车权益配置不存在");
|
return rows.get(0);
|
}
|
|
private int number(Object value) {
|
return value instanceof Number number ? number.intValue() : 0;
|
}
|
|
private String text(Object value, String fallback) {
|
if (value == null) return fallback;
|
String text = String.valueOf(value).trim();
|
return text.isEmpty() ? fallback : text;
|
}
|
}
|