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<String> 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<Map<String, Object>> 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<Map<String, Object>> 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<String, Object> detail(Long merchantId) {
|
Long id = requireMerchantId(merchantId);
|
Map<String, Object> merchant = parkingMerchant(id);
|
Map<String, Object> 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("usageInstructions", text(merchant, "usageInstructions", "usage_instructions"));
|
result.put("merchantRightsConfig", text(merchant, "merchantRightsConfig", "merchant_rights_config"));
|
result.put("coverUrl", text(merchant, "coverUrl", "cover_url"));
|
result.put("merchantStatus", intValue(merchant, "merchantStatus", "merchant_status"));
|
result.put("parkingLots", lotMapper.selectList(new QueryWrapper<TransportParkingLot>()
|
.eq("merchant_id", id)
|
.eq("deleted", 0)
|
.orderByAsc("sort_no", "id"))
|
.stream()
|
.map(this::lotView)
|
.toList());
|
Map<String, Map<String, Object>> 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<String, Object> 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<String, Object> 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<TransportParkingCardLevelRight>()
|
.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<TransportParkingCardLevelRight>()
|
.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<TransportParkingBenefit>()
|
.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<String> levels = rightMapper.selectList(new QueryWrapper<TransportParkingCardLevelRight>()
|
.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<TransportParkingBenefit>()
|
.set("talent_levels", String.join("、", levels))
|
.eq("merchant_id", merchantId)
|
.eq("deleted", 0));
|
}
|
|
private Map<String, Object> rightView(Map<String, Object> 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<String, Object> 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("usageInstructions", text(row, "usageInstructions", "usage_instructions"));
|
result.put("merchantRightsConfig", text(row, "merchantRightsConfig", "merchant_rights_config"));
|
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<String, Object> defaultRightView(Long merchantId, String cardLevel) {
|
Map<String, Object> 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<String, Object> lotView(TransportParkingLot lot) {
|
Map<String, Object> 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<String, Object> parkingMerchant(Long merchantId) {
|
Map<String, Object> 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<String, Object> row, String... keys) {
|
for (String key : keys) {
|
if (row.containsKey(key)) {
|
return row.get(key);
|
}
|
}
|
return null;
|
}
|
|
private String text(Map<String, Object> row, String... keys) {
|
Object value = value(row, keys);
|
return value == null ? "" : String.valueOf(value).trim();
|
}
|
|
private int intValue(Map<String, Object> 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();
|
}
|
}
|