package com.talentcard.server.business.portal;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
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.common.BusinessException;
|
import com.talentcard.server.common.ErrorCode;
|
import com.talentcard.server.service.TicketPackagePortalService;
|
import org.springframework.jdbc.core.JdbcTemplate;
|
import org.springframework.stereotype.Service;
|
import org.springframework.transaction.annotation.Transactional;
|
|
@Service
|
public class CinemaPortalService {
|
private final JdbcTemplate jdbc;
|
private final TicketPackagePortalService ticketPackageService;
|
public CinemaPortalService(JdbcTemplate jdbc, TicketPackagePortalService ticketPackageService) {
|
this.jdbc = jdbc;
|
this.ticketPackageService = ticketPackageService;
|
}
|
|
@Transactional(readOnly = true)
|
public Map<String, Object> detail(Long merchantId, Long talentId, Long cardId) {
|
Map<String, Object> merchant = cinema(merchantId, false);
|
String cardLevel = activeCardLevel(talentId, cardId);
|
List<Map<String, Object>> rights = parseList(merchant.get("merchantRightsConfig"));
|
Map<String, Object> currentRight = matchingRight(rights, cardLevel);
|
Map<String, Object> displayRight = currentRight == null && normalizedCardLevel(cardLevel).isBlank()
|
? firstEnabledRight(rights)
|
: currentRight;
|
Map<String, Object> ticketPackage = firstTicketPackage(merchantId, cardLevel);
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("merchantId", merchant.get("id"));
|
result.put("name", merchant.get("merchantName"));
|
result.put("heroImage", merchant.get("coverUrl"));
|
result.put("address", merchant.get("address"));
|
result.put("mobile", merchant.get("contactMobile"));
|
result.put("latitude", merchant.get("latitude"));
|
result.put("longitude", merchant.get("longitude"));
|
result.put("available", number(merchant.get("status")) == 1);
|
result.put("serviceLabel", text(merchant.get("serviceLabel"), "人才观影券"));
|
result.put("talentCardTag", rightsTag(displayRight, merchant));
|
result.put("rightsTitle", rightsTitle(displayRight));
|
result.put("rightsDescription", rightsDescription(displayRight, merchant));
|
result.put("applicableCards", applicableCards(rights, displayRight, ticketPackage));
|
result.put("claimPrice", claimPriceText(ticketPackage));
|
result.put("usage", usageText(ticketPackage));
|
result.put("scope", scopeText(ticketPackage, merchant));
|
return result;
|
}
|
|
@Transactional(readOnly = true)
|
public List<Map<String, Object>> coupons(Long merchantId, Long talentId) {
|
cinema(merchantId, false);
|
if (ticketPackageService.hasPortalPackages(merchantId)) {
|
return ticketPackageService.packages(talentId, null, merchantId).stream()
|
.map(this::ticketPackageCouponView)
|
.toList();
|
}
|
Map<String, Object> card = activeCard(talentId);
|
return query("""
|
select c.id as coupon_id, c.coupon_name, c.cover_url, c.claim_price, c.stock, c.per_user_limit,
|
c.valid_end, c.talent_levels, c.usage_text, c.scope_text, c.status,
|
exists(select 1 from cinema_coupon_claim cc where cc.coupon_id = c.id and cc.talent_id = ? and cc.deleted = 0) as claimed
|
from cinema_coupon c
|
where c.merchant_id = ? and c.deleted = 0 and c.status = 1
|
order by c.sort_no asc, c.id asc
|
""", talentId, merchantId).stream().map(coupon -> couponView(coupon, String.valueOf(card.get("cardLevel")))).toList();
|
}
|
|
@Transactional
|
public Map<String, Object> claim(Long merchantId, Long couponId, Long talentId, String idempotencyKey) {
|
if (idempotencyKey == null || idempotencyKey.isBlank() || idempotencyKey.length() > 128) {
|
throw new BusinessException(ErrorCode.BAD_REQUEST, "领取凭证不能为空");
|
}
|
cinema(merchantId, true);
|
if (ticketPackageService.hasPortalPackages(merchantId)) {
|
Map<String, Object> claimed = ticketPackageService.claimByMerchant(merchantId, talentId, null, idempotencyKey);
|
claimed.put("couponId", claimed.get("packageId"));
|
claimed.put("couponName", claimed.getOrDefault("packageName", "电影票兑换券"));
|
claimed.put("dynamicCodeEntry", "/pages/coupon-received/coupon-received?type=ticketPackage&claimId="
|
+ claimed.get("claimId"));
|
return claimed;
|
}
|
List<Map<String, Object>> idempotent = query("select id from cinema_coupon_claim where idempotency_key = ? and deleted = 0", idempotencyKey);
|
if (!idempotent.isEmpty()) return claimView(Long.valueOf(number(idempotent.get(0).get("id"))));
|
|
Map<String, Object> card = activeCard(talentId);
|
Map<String, Object> merchant = cinema(merchantId, true);
|
List<Map<String, Object>> coupons = query("""
|
select id, merchant_id, coupon_name, stock, talent_levels, valid_end, status
|
from cinema_coupon where id = ? and merchant_id = ? and deleted = 0
|
""", couponId, merchantId);
|
if (coupons.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "电影券不存在");
|
Map<String, Object> coupon = coupons.get(0);
|
if (number(coupon.get("status")) != 1) throw new BusinessException(ErrorCode.CONFLICT, "电影券已失效");
|
if (!supportsCard(coupon.get("talentLevels"), String.valueOf(card.get("cardLevel")))) throw new BusinessException(ErrorCode.CONFLICT, "当前人才卡不适用该电影券");
|
List<Map<String, Object>> duplicate = query("select id from cinema_coupon_claim where coupon_id = ? and talent_id = ? and deleted = 0", couponId, talentId);
|
if (!duplicate.isEmpty()) throw new BusinessException(ErrorCode.CONFLICT, "优惠券已领取");
|
int updated = jdbc.update("update cinema_coupon set stock = stock - 1 where id = ? and merchant_id = ? and status = 1 and deleted = 0 and stock > 0", couponId, merchantId);
|
if (updated == 0) throw new BusinessException(ErrorCode.CONFLICT, "电影券已领完");
|
|
String claimNo = BusinessIdGenerator.next("MC");
|
String exchangeCode = BusinessIdGenerator.next("EX");
|
jdbc.update("""
|
insert into cinema_coupon_claim(claim_no, coupon_id, merchant_id, talent_id, card_id, card_no, valid_end, status, exchange_code, code_status, idempotency_key)
|
values (?, ?, ?, ?, ?, ?, ?, 'RECEIVED', ?, 'ACTIVE', ?)
|
""", claimNo, couponId, merchantId, talentId, card.get("cardId"), card.get("cardNo"), coupon.get("validEnd"), exchangeCode, idempotencyKey);
|
Long claimId = jdbc.queryForObject("select id from cinema_coupon_claim where idempotency_key = ?", Long.class, idempotencyKey);
|
jdbc.update("""
|
insert into movie_ticket_record(receive_no, package_name, merchants, talent_name, talent_mobile, talent_level, card_no, received_at)
|
values (?, ?, ?, ?, ?, ?, ?, now())
|
""", claimNo, coupon.get("couponName"), merchant.get("merchantName"), card.get("talentName"), card.get("talentMobile"), card.get("cardLevel"), card.get("cardNo"));
|
return claimView(claimId);
|
}
|
|
@Transactional(readOnly = true)
|
public Map<String, Object> claimDetail(Long claimId, Long talentId) {
|
if (claimId == null || talentId == null || talentId <= 0) throw new BusinessException(ErrorCode.BAD_REQUEST, "用户券参数不正确");
|
List<Map<String, Object>> claims = query("""
|
select cc.id as claim_id, cc.claim_no, cc.status as claim_status, cc.exchange_code, cc.code_status,
|
cc.valid_end, cc.used_at, cc.card_id, cc.talent_id, cc.merchant_id,
|
cc.coupon_id,
|
c.coupon_name, c.per_user_limit, c.annual_quota, c.usage_text, c.scope_text,
|
m.merchant_name
|
from cinema_coupon_claim cc
|
join cinema_coupon c on c.id = cc.coupon_id and c.deleted = 0
|
join merchant m on m.id = cc.merchant_id and m.deleted = 0
|
where cc.id = ? and cc.talent_id = ? and cc.deleted = 0
|
""", claimId, talentId);
|
if (claims.isEmpty()) return legacyClaimDetail(claimId, talentId);
|
Map<String, Object> claim = claims.get(0);
|
int annualTotal = number(claim.get("perUserLimit"));
|
if (annualTotal < 1) annualTotal = number(claim.get("annualQuota"));
|
if (annualTotal < 1) annualTotal = 1;
|
Long annualClaimed = jdbc.queryForObject("""
|
select count(*)
|
from cinema_coupon_claim
|
where coupon_id = ?
|
and talent_id = ?
|
and deleted = 0
|
and date(created_at) = current_date
|
""", Long.class, claim.get("couponId"), talentId);
|
int claimedCount = annualClaimed == null ? 0 : annualClaimed.intValue();
|
String codeStatus = effectiveCodeStatus(claim);
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("claimId", claim.get("claimId"));
|
result.put("couponName", claim.get("couponName"));
|
result.put("merchantName", claim.get("merchantName"));
|
result.put("claimStatus", claim.get("claimStatus"));
|
result.put("annualTotal", annualTotal);
|
result.put("annualClaimed", claimedCount);
|
result.put("annualRemaining", Math.max(0, annualTotal - claimedCount));
|
result.put("exchangeCodeStatus", codeStatus);
|
result.put("exchangeCode", "ACTIVE".equals(codeStatus) ? claim.get("exchangeCode") : "");
|
result.put("validEnd", claim.get("validEnd"));
|
result.put("channel", "影城小程序");
|
result.put("verificationRule", text(claim.get("usageText"), "到店出示兑换码") + ",适用范围:" + text(claim.get("scopeText"), "以影城说明为准"));
|
result.put("invalidReason", codeStatusReason(codeStatus, claim.get("usedAt")));
|
return result;
|
}
|
|
private Map<String, Object> legacyClaimDetail(Long recordId, Long talentId) {
|
List<Map<String, Object>> records = query("""
|
select r.id, r.receive_no, r.package_name, r.merchants
|
from movie_ticket_record r
|
where r.id = ? and r.deleted = 0
|
and exists (
|
select 1 from talent_card c
|
where c.talent_id = ? and c.deleted = 0 and c.card_type = 'PERSONAL' and c.card_no = r.card_no
|
)
|
""", recordId, talentId);
|
if (records.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "用户券不存在或无权查看");
|
Map<String, Object> record = records.get(0);
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("claimId", record.get("id"));
|
result.put("couponName", text(record.get("packageName"), "电影券"));
|
result.put("merchantName", text(record.get("merchants"), "影城"));
|
result.put("exchangeCodeStatus", "LEGACY");
|
result.put("exchangeCode", "");
|
result.put("annualTotal", 0);
|
result.put("annualClaimed", 0);
|
result.put("annualRemaining", 0);
|
result.put("channel", "影城小程序");
|
result.put("verificationRule", "历史电影券领取记录");
|
result.put("invalidReason", "历史记录");
|
return result;
|
}
|
|
private Map<String, Object> ticketPackageCouponView(Map<String, Object> row) {
|
int quotaTotal = number(value(row, "packageQuotaTotal", "quotaTotal"));
|
int quotaUsed = number(value(row, "packageQuotaUsed", "quotaUsed"));
|
int quotaRemaining = number(value(row, "packageQuotaRemaining", "quotaRemaining"));
|
int availableCodes = number(value(row, "availableCodes"));
|
boolean available = Boolean.TRUE.equals(row.get("available")) || number(row.get("available")) == 1;
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("couponId", row.get("packageId"));
|
result.put("packageId", row.get("packageId"));
|
result.put("name", row.get("packageName"));
|
result.put("couponName", row.get("packageName"));
|
result.put("coverUrl", row.get("coverUrl"));
|
result.put("claimPrice", row.get("claimPrice"));
|
result.put("stock", Math.min(availableCodes, quotaRemaining));
|
result.put("limitCount", quotaTotal);
|
result.put("quotaTotal", quotaTotal);
|
result.put("quotaUsed", quotaUsed);
|
result.put("quotaRemaining", quotaRemaining);
|
result.put("packageQuotaTotal", quotaTotal);
|
result.put("packageQuotaUsed", quotaUsed);
|
result.put("packageQuotaRemaining", quotaRemaining);
|
result.put("remainingCount", quotaRemaining);
|
result.put("totalQuantity", row.get("totalQuantity"));
|
result.put("claimedCount", row.get("claimedCount"));
|
result.put("availableCodes", availableCodes);
|
result.put("validEnd", row.get("validEnd"));
|
result.put("usage", row.get("usageText"));
|
result.put("scope", row.get("merchantNames"));
|
result.put("claimed", quotaRemaining <= 0);
|
result.put("soldOut", !available);
|
result.put("sourceType", "TICKET_PACKAGE");
|
result.put("unavailableReason", row.get("unavailableReason"));
|
return result;
|
}
|
|
private Map<String, Object> cinema(Long merchantId, boolean requireAvailable) {
|
List<Map<String, Object>> merchants = query("""
|
select id, merchant_name, merchant_type_code as merchant_type, contact_mobile, address, cover_url, service_label, rights_text,
|
merchant_rights_config, latitude, longitude, status
|
from merchant where id = ? and deleted = 0 and merchant_type_code = 'cinema'
|
""", merchantId);
|
if (merchants.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "影城不存在");
|
Map<String, Object> merchant = merchants.get(0);
|
if (requireAvailable && number(merchant.get("status")) != 1) throw new BusinessException(ErrorCode.CONFLICT, "影城已下架");
|
return merchant;
|
}
|
|
private Map<String, Object> activeCard(Long talentId) {
|
if (talentId == null || talentId <= 0) throw new BusinessException(ErrorCode.BAD_REQUEST, "人才信息不能为空");
|
List<Map<String, Object>> cards = query("""
|
select c.id as card_id, c.card_no, c.card_level, u.name as talent_name, u.mobile as talent_mobile
|
from talent_card c join talent_user u on u.id = c.talent_id and u.deleted = 0
|
where c.talent_id = ? and c.deleted = 0 and c.card_type = 'PERSONAL' and c.status = 1
|
and (c.valid_end is null or c.valid_end >= current_date)
|
order by c.id desc limit 1
|
""", talentId);
|
if (cards.isEmpty()) throw new BusinessException(ErrorCode.CONFLICT, "当前用户未持有有效人才卡");
|
return cards.get(0);
|
}
|
|
private String activeCardLevel(Long talentId, Long cardId) {
|
if ((talentId == null || talentId <= 0) && (cardId == null || cardId <= 0)) return "";
|
List<Map<String, Object>> cards = query("""
|
select c.id as card_id, c.card_level, c.card_type
|
from talent_card c join talent_user u on u.id = c.talent_id and u.deleted = 0 and u.status = 1
|
where c.deleted = 0 and c.status = 1
|
and (c.valid_start is null or c.valid_start <= current_date)
|
and (c.valid_end is null or c.valid_end >= current_date)
|
and (c.expire_time is null or c.expire_time > now())
|
and ((? is not null and c.id = ?) or (? is null and c.talent_id = ?))
|
and (? is null or c.talent_id = ?)
|
order by case when c.card_type = 'PERSONAL' then 0 else 1 end,
|
case
|
when upper(coalesce(c.card_level, '')) like '%EXCELLENT%' or coalesce(c.card_level, '') like '%卓越%' then 0
|
when upper(coalesce(c.card_level, '')) like '%OUTSTANDING%' or coalesce(c.card_level, '') like '%杰出%' then 1
|
when upper(coalesce(c.card_level, '')) like '%ELITE%' or coalesce(c.card_level, '') like '%菁英%' or coalesce(c.card_level, '') like '%精英%' then 2
|
else 9
|
end,
|
c.id desc
|
limit 1
|
""", cardId, cardId, cardId, talentId, talentId, talentId);
|
if (cards.isEmpty()) return "";
|
Map<String, Object> card = cards.get(0);
|
String cardType = text(card.get("cardType"), "");
|
if (!"PERSONAL".equalsIgnoreCase(cardType)) {
|
Long familyCardId = longValue(card.get("cardId"));
|
if (familyCardId != null && familyCardId > 0) {
|
List<Map<String, Object>> primaryCards = query("""
|
select c.card_level
|
from family_card_application f
|
join talent_card c on c.talent_id = f.talent_id and c.card_type = 'PERSONAL'
|
where f.deleted = 0
|
and f.status = '已通过'
|
and f.family_card_id = ?
|
and c.deleted = 0
|
and c.status = 1
|
and (c.valid_start is null or c.valid_start <= current_date)
|
and (c.valid_end is null or c.valid_end >= current_date)
|
and (c.expire_time is null or c.expire_time > now())
|
order by c.id desc
|
limit 1
|
""", familyCardId);
|
if (!primaryCards.isEmpty()) return text(primaryCards.get(0).get("cardLevel"), "");
|
}
|
}
|
return text(card.get("cardLevel"), "");
|
}
|
|
private Map<String, Object> firstTicketPackage(Long merchantId, String cardLevel) {
|
List<Map<String, Object>> rows = query("""
|
select p.id, p.package_name, p.claim_price, p.talent_levels, p.usage_text, p.valid_start, p.valid_end,
|
coalesce((
|
select group_concat(m1.merchant_name order by pm1.sort_no asc, pm1.id asc separator '、')
|
from ticket_package_merchant pm1
|
join merchant m1 on m1.id = pm1.merchant_id and m1.deleted = 0
|
where pm1.package_id = p.id and pm1.deleted = 0
|
), m.merchant_name) as merchant_names
|
from ticket_package p
|
left join merchant m on m.id = p.merchant_id and m.deleted = 0
|
where p.deleted = 0
|
and p.status = 1
|
and (p.valid_start is null or p.valid_start <= current_date)
|
and (p.valid_end is null or p.valid_end >= current_date)
|
and (p.merchant_id = ? or exists (
|
select 1
|
from ticket_package_merchant pm
|
where pm.package_id = p.id and pm.merchant_id = ? and pm.deleted = 0
|
))
|
order by p.sort_no desc, p.id desc
|
""", merchantId, merchantId);
|
for (Map<String, Object> row : rows) {
|
if (supportsCard(row.get("talentLevels"), cardLevel)) return row;
|
}
|
return rows.isEmpty() ? new LinkedHashMap<>() : rows.get(0);
|
}
|
|
private String rightsTag(Map<String, Object> right, Map<String, Object> merchant) {
|
if (right != null) {
|
String tag = text(right.get("tag"), "");
|
if (!tag.isBlank()) return tag;
|
}
|
return text(merchant.get("serviceLabel"), "人才卡专享");
|
}
|
|
private String rightsTitle(Map<String, Object> right) {
|
if (right != null) {
|
String level = text(right.get("level"), "");
|
if (!level.isBlank()) return level + "观影权益";
|
}
|
return "人才观影专属权益";
|
}
|
|
private String rightsDescription(Map<String, Object> right, Map<String, Object> merchant) {
|
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(merchant.get("rightsText"), "持人才卡可领取普通厅电影兑换券,到店出示券码后按影城规则兑换。具体适用影片、场次及补差金额以影城现场公示为准。");
|
}
|
|
private String applicableCards(List<Map<String, Object>> rights, Map<String, Object> right, Map<String, Object> ticketPackage) {
|
if (!rights.isEmpty()) {
|
List<String> levels = rights.stream()
|
.filter(item -> booleanValue(item.get("enabled")))
|
.map(item -> normalizedCardLevel(text(item.get("level"), "")))
|
.filter(level -> !level.isBlank())
|
.distinct()
|
.toList();
|
if (!levels.isEmpty()) return String.join("、", levels);
|
}
|
if (right != null) {
|
String level = normalizedCardLevel(text(right.get("level"), ""));
|
if (!level.isBlank()) return level;
|
}
|
String packageLevels = text(ticketPackage.get("talentLevels"), "");
|
if (!packageLevels.isBlank()) return packageLevels;
|
return "卓越、杰出、菁英人才卡";
|
}
|
|
private String claimPriceText(Map<String, Object> ticketPackage) {
|
Object value = ticketPackage.get("claimPrice");
|
if (value == null) return "0元";
|
try {
|
BigDecimal price = value instanceof BigDecimal decimal
|
? decimal
|
: new BigDecimal(String.valueOf(value));
|
if (price.compareTo(BigDecimal.ZERO) == 0) return "0元";
|
return price.setScale(2, RoundingMode.HALF_UP).stripTrailingZeros().toPlainString() + "元";
|
} catch (NumberFormatException exception) {
|
return text(value, "0元");
|
}
|
}
|
|
private String usageText(Map<String, Object> ticketPackage) {
|
return text(ticketPackage.get("usageText"), "复制兑换码后前往指定商家APP兑换电影票");
|
}
|
|
private String scopeText(Map<String, Object> ticketPackage, Map<String, Object> merchant) {
|
String merchants = text(ticketPackage.get("merchantNames"), "");
|
if (!merchants.isBlank()) return merchants;
|
return text(merchant.get("merchantName"), "指定影城");
|
}
|
|
private Map<String, Object> matchingRight(List<Map<String, Object>> rights, String cardLevel) {
|
if (rights.isEmpty()) return null;
|
String normalizedLevel = normalizedCardLevel(cardLevel);
|
if (normalizedLevel.isBlank()) return null;
|
for (Map<String, Object> right : rights) {
|
if (!booleanValue(right.get("enabled"))) continue;
|
if (normalizedLevel.equals(normalizedCardLevel(text(right.get("level"), "")))) return right;
|
}
|
return null;
|
}
|
|
private Map<String, Object> firstEnabledRight(List<Map<String, Object>> rights) {
|
return rights.stream().filter(item -> booleanValue(item.get("enabled"))).findFirst().orElse(null);
|
}
|
|
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 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 Map<String, Object> couponView(Map<String, Object> row, String cardLevel) {
|
Map<String, Object> result = new LinkedHashMap<>();
|
boolean claimed = Boolean.TRUE.equals(row.get("claimed")) || number(row.get("claimed")) == 1;
|
boolean supported = supportsCard(row.get("talentLevels"), cardLevel);
|
int stock = number(row.get("stock"));
|
result.put("couponId", row.get("couponId"));
|
result.put("name", row.get("couponName"));
|
result.put("coverUrl", row.get("coverUrl"));
|
result.put("claimPrice", row.get("claimPrice"));
|
result.put("stock", stock);
|
result.put("limitCount", row.get("perUserLimit"));
|
result.put("validEnd", row.get("validEnd"));
|
result.put("usage", row.get("usageText"));
|
result.put("scope", row.get("scopeText"));
|
result.put("claimed", claimed);
|
result.put("soldOut", stock < 1 || !supported);
|
result.put("unavailableReason", supported ? (stock < 1 ? "电影券已领完" : "") : "当前人才卡不适用该电影券");
|
return result;
|
}
|
|
private Map<String, Object> claimView(Long claimId) {
|
List<Map<String, Object>> claims = query("""
|
select cc.id as claim_id, cc.claim_no, cc.status, cc.valid_end, c.id as coupon_id, c.coupon_name, m.merchant_name
|
from cinema_coupon_claim cc
|
join cinema_coupon c on c.id = cc.coupon_id
|
join merchant m on m.id = cc.merchant_id
|
where cc.id = ? and cc.deleted = 0
|
""", claimId);
|
if (claims.isEmpty()) throw new BusinessException(ErrorCode.NOT_FOUND, "领取记录不存在");
|
Map<String, Object> claim = claims.get(0);
|
Map<String, Object> result = new LinkedHashMap<>();
|
result.put("claimId", claim.get("claimId"));
|
result.put("couponId", claim.get("couponId"));
|
result.put("couponName", claim.get("couponName"));
|
result.put("merchantName", claim.get("merchantName"));
|
result.put("claimNo", claim.get("claimNo"));
|
result.put("status", claim.get("status"));
|
result.put("validEnd", claim.get("validEnd"));
|
result.put("dynamicCodeEntry", "/pages/coupon-received/coupon-received?claimId=" + claim.get("claimId"));
|
return result;
|
}
|
|
private String effectiveCodeStatus(Map<String, Object> claim) {
|
String status = String.valueOf(claim.getOrDefault("codeStatus", "ACTIVE"));
|
if ("USED".equals(status)) return "USED";
|
Object validEnd = claim.get("validEnd");
|
if (validEnd instanceof java.time.LocalDate date && date.isBefore(java.time.LocalDate.now())) return "EXPIRED";
|
return "ACTIVE";
|
}
|
private String codeStatusReason(String status, Object usedAt) {
|
if ("USED".equals(status)) return "兑换码已使用" + (usedAt == null ? "" : ",核销时间:" + usedAt);
|
if ("EXPIRED".equals(status)) return "兑换码已过期,请联系影城确认处理方式";
|
return "";
|
}
|
|
private boolean supportsCard(Object levels, String cardLevel) {
|
String configured = levels == null ? "" : String.valueOf(levels);
|
String normalizedLevel = normalizedCardLevel(cardLevel);
|
return configured.isBlank()
|
|| configured.contains(cardLevel)
|
|| (!normalizedLevel.isBlank() && configured.contains(normalizedLevel));
|
}
|
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 int number(Object value) { return value instanceof Number number ? number.intValue() : 0; }
|
private Long longValue(Object value) { return value instanceof Number number ? number.longValue() : null; }
|
private String text(Object value, String fallback) { return value == null || String.valueOf(value).isBlank() ? fallback : String.valueOf(value); }
|
private Object value(Map<String, Object> row, String... keys) {
|
for (String key : keys) {
|
if (row.containsKey(key)) return row.get(key);
|
}
|
return null;
|
}
|
private List<Map<String, Object>> query(String sql, Object... args) { return DatabaseRows.normalize(jdbc.queryForList(sql, args)); }
|
}
|