package com.talentcard.server.service; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.function.Function; import java.util.stream.Collectors; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper; import com.talentcard.server.business.PageResult; import com.talentcard.server.common.BusinessException; import com.talentcard.server.common.ErrorCode; import com.talentcard.server.entity.TransportParkingBenefit; import com.talentcard.server.entity.TransportParkingCardLevelRight; import com.talentcard.server.entity.TransportParkingLot; import com.talentcard.server.mapper.TransportParkingBenefitMapper; import com.talentcard.server.mapper.TransportParkingCardLevelRightMapper; import com.talentcard.server.mapper.TransportParkingLotMapper; import com.talentcard.server.mapper.TransportParkingPlateRegistrationMapper; import com.talentcard.server.request.ParkingCardLevelRightRequest; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class ParkingRightsConfigAdminService { private static final List CARD_LEVELS = List.of("卓越人才卡", "杰出人才卡", "菁英人才卡"); private final TransportParkingCardLevelRightMapper rightMapper; private final TransportParkingPlateRegistrationMapper registrationMapper; private final TransportParkingLotMapper lotMapper; private final TransportParkingBenefitMapper benefitMapper; public ParkingRightsConfigAdminService( TransportParkingCardLevelRightMapper rightMapper, TransportParkingPlateRegistrationMapper registrationMapper, TransportParkingLotMapper lotMapper, TransportParkingBenefitMapper benefitMapper ) { this.rightMapper = rightMapper; this.registrationMapper = registrationMapper; this.lotMapper = lotMapper; this.benefitMapper = benefitMapper; } @Transactional(readOnly = true) public PageResult> page(Long merchantId, String cardLevel, Integer enabled, String keyword, int page, int size) { int safePage = Math.max(page, 1); int safeSize = Math.min(Math.max(size, 1), 100); String level = filterCardLevel(cardLevel); Integer normalizedEnabled = normalizeEnabledFilter(enabled); String cleanedKeyword = clean(keyword); Long total = rightMapper.countConfigPage(merchantId, level, normalizedEnabled, cleanedKeyword); List> records = rightMapper.selectConfigPage(merchantId, level, normalizedEnabled, cleanedKeyword, safeSize, (safePage - 1) * safeSize) .stream() .map(this::rightView) .toList(); return new PageResult<>(records, safePage, safeSize, total == null ? 0 : total); } @Transactional(readOnly = true) public Map detail(Long merchantId) { Long id = requireMerchantId(merchantId); Map merchant = parkingMerchant(id); Map result = new LinkedHashMap<>(); result.put("merchantId", value(merchant, "merchantId", "merchant_id")); result.put("merchantName", text(merchant, "merchantName", "merchant_name")); result.put("contactMobile", text(merchant, "contactMobile", "contact_mobile")); result.put("address", text(merchant, "address")); result.put("coverUrl", text(merchant, "coverUrl", "cover_url")); result.put("merchantStatus", intValue(merchant, "merchantStatus", "merchant_status")); result.put("parkingLots", lotMapper.selectList(new QueryWrapper() .eq("merchant_id", id) .eq("deleted", 0) .orderByAsc("sort_no", "id")) .stream() .map(this::lotView) .toList()); Map> rights = CARD_LEVELS.stream() .map(level -> defaultRightView(id, level)) .collect(Collectors.toMap(row -> text(row, "cardLevel"), Function.identity(), (a, b) -> a, LinkedHashMap::new)); for (Map row : rightMapper.selectConfigPage(id, "", null, "", 100, 0)) { rights.put(text(row, "cardLevel", "card_level"), rightView(row)); } result.put("cardLevelRights", CARD_LEVELS.stream().map(rights::get).toList()); return result; } @Transactional public Map save(Long merchantId, String cardLevel, ParkingCardLevelRightRequest request) { Long id = requireMerchantId(merchantId); String level = requireCardLevel(cardLevel); parkingMerchant(id); ensureBenefit(id); String chargeType = normalizeChargeType(request.chargeType()); String selectionMode = normalizeSelectionMode(request.selectionMode()); int status = normalizeStatus(request.status()); int enabled = Boolean.TRUE.equals(request.enabled()) ? 1 : 0; TransportParkingCardLevelRight current = rightMapper.selectOne(new QueryWrapper() .eq("merchant_id", id) .eq("card_level", level) .eq("deleted", 0) .last("limit 1 for update")); if (current == null) { current = new TransportParkingCardLevelRight(); current.setMerchantId(id); current.setCardLevel(level); current.setEnabled(enabled); current.setLabel(clean(request.label())); current.setBenefitDetail(clean(request.benefitDetail())); current.setChargeType(chargeType); current.setSelectionMode(selectionMode); current.setStoreConsumeEnabled(Boolean.TRUE.equals(request.storeConsumeEnabled()) ? 1 : 0); current.setDiscountText(clean(request.discountText())); current.setGovernmentSubsidy(Boolean.TRUE.equals(request.governmentSubsidy()) ? 1 : 0); current.setConsumeUsageText(clean(request.consumeUsageText())); current.setStatus(status); current.setDeleted(0); rightMapper.insert(current); } else { int updated = rightMapper.update(null, new UpdateWrapper() .set("enabled", enabled) .set("label", clean(request.label())) .set("benefit_detail", clean(request.benefitDetail())) .set("charge_type", chargeType) .set("selection_mode", selectionMode) .set("store_consume_enabled", Boolean.TRUE.equals(request.storeConsumeEnabled()) ? 1 : 0) .set("discount_text", clean(request.discountText())) .set("government_subsidy", Boolean.TRUE.equals(request.governmentSubsidy()) ? 1 : 0) .set("consume_usage_text", clean(request.consumeUsageText())) .set("status", status) .eq("id", current.getId()) .eq("deleted", 0)); if (updated != 1) { throw new BusinessException(ErrorCode.CONFLICT, "停车权益配置已变化,请刷新后重试"); } } syncLegacyTalentLevels(id); return rightView(rightMapper.selectConfig(id, level)); } private void ensureBenefit(Long merchantId) { Long count = benefitMapper.selectCount(new QueryWrapper() .eq("merchant_id", merchantId) .eq("deleted", 0)); if (count != null && count > 0) { return; } TransportParkingBenefit benefit = new TransportParkingBenefit(); benefit.setMerchantId(merchantId); benefit.setTalentLevels(""); benefit.setBenefitText("停车权益"); benefit.setActionMode("plate"); benefit.setPlateFormMode("withParking"); benefit.setUsageInstructions("登记车牌后可享受当前停车场的人才专属停车权益。"); benefit.setRuleText("具体免费时长、适用时段和停车场范围以现场说明为准。"); benefit.setStatus(1); benefit.setDeleted(0); benefitMapper.insert(benefit); } private void syncLegacyTalentLevels(Long merchantId) { List levels = rightMapper.selectList(new QueryWrapper() .eq("merchant_id", merchantId) .eq("deleted", 0) .eq("enabled", 1) .eq("status", 1) .orderByAsc("case card_level when '卓越人才卡' then 1 when '杰出人才卡' then 2 when '菁英人才卡' then 3 else 99 end")) .stream() .map(TransportParkingCardLevelRight::getCardLevel) .filter(CARD_LEVELS::contains) .distinct() .toList(); benefitMapper.update(null, new UpdateWrapper() .set("talent_levels", String.join("、", levels)) .eq("merchant_id", merchantId) .eq("deleted", 0)); } private Map rightView(Map row) { if (row == null || row.isEmpty()) { return Map.of(); } String selectionMode = normalizeSelectionMode(text(row, "selectionMode", "selection_mode")); String chargeType = normalizeChargeType(text(row, "chargeType", "charge_type")); Map result = new LinkedHashMap<>(); result.put("rightId", value(row, "rightId", "right_id")); result.put("merchantId", value(row, "merchantId", "merchant_id")); result.put("merchantName", text(row, "merchantName", "merchant_name")); result.put("contactMobile", text(row, "contactMobile", "contact_mobile")); result.put("address", text(row, "address")); result.put("coverUrl", text(row, "coverUrl", "cover_url")); result.put("merchantStatus", intValue(row, "merchantStatus", "merchant_status")); result.put("cardLevel", text(row, "cardLevel", "card_level")); result.put("enabled", intValue(row, "enabled") == 1); result.put("label", text(row, "label")); result.put("benefitDetail", text(row, "benefitDetail", "benefit_detail")); result.put("chargeType", chargeType); result.put("chargeTypeName", "PAID".equals(chargeType) ? "付费" : "免费"); result.put("selectionMode", selectionMode); result.put("selectionModeName", "ONE".equals(selectionMode) ? "停车场仅选择其一" : "停车场全部可选"); result.put("storeConsumeEnabled", intValue(row, "storeConsumeEnabled", "store_consume_enabled") == 1); result.put("discountText", text(row, "discountText", "discount_text")); result.put("governmentSubsidy", intValue(row, "governmentSubsidy", "government_subsidy") == 1); result.put("consumeUsageText", text(row, "consumeUsageText", "consume_usage_text")); result.put("status", intValue(row, "rightStatus", "right_status", "status")); result.put("benefitId", value(row, "benefitId", "benefit_id")); result.put("updatedAt", value(row, "updatedAt", "updated_at")); return result; } private Map defaultRightView(Long merchantId, String cardLevel) { Map result = new LinkedHashMap<>(); result.put("rightId", null); result.put("merchantId", merchantId); result.put("cardLevel", cardLevel); result.put("enabled", false); result.put("label", ""); result.put("benefitDetail", ""); result.put("chargeType", "FREE"); result.put("chargeTypeName", "免费"); result.put("selectionMode", "ALL"); result.put("selectionModeName", "停车场全部可选"); result.put("storeConsumeEnabled", false); result.put("discountText", ""); result.put("governmentSubsidy", false); result.put("consumeUsageText", ""); result.put("status", 1); return result; } private Map lotView(TransportParkingLot lot) { Map result = new LinkedHashMap<>(); result.put("parkingLotId", lot.getId()); result.put("parkingLotName", lot.getParkingLotName()); result.put("address", lot.getAddress()); result.put("latitude", lot.getLatitude()); result.put("longitude", lot.getLongitude()); result.put("sortNo", lot.getSortNo()); result.put("enabled", lot.getEnabled() != null && lot.getEnabled() == 1); return result; } private Map parkingMerchant(Long merchantId) { Map merchant = registrationMapper.selectParkingMerchant(merchantId); if (merchant == null || merchant.isEmpty()) { throw new BusinessException(ErrorCode.NOT_FOUND, "停车商家不存在"); } return merchant; } private String normalizeChargeType(String raw) { String value = clean(raw).toUpperCase(Locale.ROOT); return switch (value) { case "", "FREE", "免费" -> "FREE"; case "PAID", "PAY", "付费" -> "PAID"; default -> throw new BusinessException(ErrorCode.BAD_REQUEST, "权益类型仅支持免费或付费"); }; } private String normalizeSelectionMode(String raw) { String value = clean(raw).toUpperCase(Locale.ROOT).replace("_", "").replace("-", ""); return switch (value) { case "", "ALL", "全部可选", "停车场全部可选" -> "ALL"; case "ONE", "SELECTONE", "ONLYONE", "只选其一", "仅选其一", "停车场仅选择其一" -> "ONE"; default -> throw new BusinessException(ErrorCode.BAD_REQUEST, "停车场选择模式仅支持全部可选或只选其一"); }; } private int normalizeStatus(Integer status) { if (status == null) { return 1; } if (status != 0 && status != 1) { throw new BusinessException(ErrorCode.BAD_REQUEST, "状态只能为0停用或1启用"); } return status; } private Integer normalizeEnabledFilter(Integer enabled) { if (enabled == null) { return null; } if (enabled != 0 && enabled != 1) { throw new BusinessException(ErrorCode.BAD_REQUEST, "是否启用只能为0或1"); } return enabled; } private String filterCardLevel(String value) { String level = clean(value); if (level.isBlank()) { return ""; } return requireCardLevel(level); } private String requireCardLevel(String value) { String level = clean(value); if (!CARD_LEVELS.contains(level)) { throw new BusinessException(ErrorCode.BAD_REQUEST, "人才卡等级仅支持卓越人才卡、杰出人才卡、菁英人才卡"); } return level; } private Long requireMerchantId(Long merchantId) { if (merchantId == null || merchantId <= 0) { throw new BusinessException(ErrorCode.BAD_REQUEST, "停车商家ID不能为空"); } return merchantId; } private Object value(Map row, String... keys) { for (String key : keys) { if (row.containsKey(key)) { return row.get(key); } } return null; } private String text(Map row, String... keys) { Object value = value(row, keys); return value == null ? "" : String.valueOf(value).trim(); } private int intValue(Map row, String... keys) { Object value = value(row, keys); return value instanceof Number number ? number.intValue() : 0; } private String clean(String value) { return value == null ? "" : value.trim(); } }