package com.xiaopeng.account.service.impl;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import com.xiaopeng.account.mapper.XpAppUserCouponMapper;
import com.xiaopeng.account.model.dto.ReceiveCouponDto;
import com.xiaopeng.account.model.dto.SendCouponDto;
import com.xiaopeng.account.model.query.XpUserCouponListQuery;
import com.xiaopeng.account.model.vo.XpAppUserCouponListVo;
import com.xiaopeng.account.model.vo.XpAppUserCouponTotalVo;
import com.xiaopeng.account.model.vo.XpAppUserCouponVo;
import com.xiaopeng.account.service.IXpAppUserCouponService;
import com.xiaopeng.account.util.holiday.HolidayUtil;
import com.xiaopeng.api.dto.account.NavigationCouponPickerRequest;
import com.xiaopeng.api.dto.account.UcUserInfo;
import com.xiaopeng.api.enums.NavigationCouponBubbleType;
import com.xiaopeng.api.vo.account.NavigationCouponItemVo;
import com.xiaopeng.api.vo.account.NavigationCouponPickerVo;
import com.xiaopeng.api.feignClient.account.UserResourcesClient;
import com.xiaopeng.api.feignClient.operation.XpCouponClient;
import com.xiaopeng.api.feignClient.operation.XpWhiteListClient;
import com.xiaopeng.api.model.account.XpAppUserCoupon;
import com.xiaopeng.api.model.operation.XpCoupon;
import com.xiaopeng.common.core.domain.R;
import com.xiaopeng.common.core.utils.DateUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import java.math.BigDecimal;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
/**
*
* 用户优惠券 服务实现类
*
*
* @author mitao
* @since 2026-02-03
*/
@Service
public class XpAppUserCouponServiceImpl extends ServiceImpl implements IXpAppUserCouponService {
@Autowired
private HolidayUtil holidayUtil;
@Autowired
private XpCouponClient xpCouponClient;
@Autowired
private XpWhiteListClient xpWhiteListClient;
@Autowired
private UserResourcesClient userResourcesClient;
/**
* 获取用户优惠券列表
*
* @param query
* @param uid
* @return
*/
@Override
public XpAppUserCouponVo userCouponList(XpUserCouponListQuery query, String uid) {
Page page = new Page<>(query.getPageNum(), query.getPageSize());
Page list = baseMapper.userCouponList(page, query, Long.valueOf(uid));
XpAppUserCouponVo vo = new XpAppUserCouponVo();
vo.setTotal((int) page.getTotal());
vo.setList(list.getRecords());
return vo;
}
/**
* 获取用户优惠券数量total
*
* @param uid
* @return
*/
@Override
public XpAppUserCouponTotalVo userCouponTotal(String uid) {
List couponList = baseMapper.selectList(new LambdaQueryWrapper().eq(XpAppUserCoupon::getAppUserId, uid));
XpAppUserCouponTotalVo vo = new XpAppUserCouponTotalVo();
// vo.setAwaitReceiveNum((int) couponList.stream().filter(e -> e.getStatus() == 0).count());
vo.setAvailableNum((int) couponList.stream().filter(e -> e.getStatus() == 1).count());
vo.setUsedNum((int) couponList.stream().filter(e -> e.getStatus() == 2).count());
vo.setExpiredNum((int) couponList.stream().filter(e -> e.getStatus() == 3).count());
return vo;
}
/**
* 领取优惠券
*
* @param dto
* @param uid
* @return
*/
@Override
public boolean receive(ReceiveCouponDto dto, String uid) {
XpAppUserCoupon coupon = baseMapper.selectById(dto.getCouponId());
if (coupon.getAppUserId().toString().equals(uid)) {
coupon.setStatus(1);
return updateById(coupon);
}
return false;
}
/**
* 时间规则
*
* @param couponList
* @return
*/
@Override
public List timeRule(List couponList) {
List xpAppUserCoupons = new ArrayList<>();
for (XpAppUserCoupon coupon : couponList) {
String timeJson = coupon.getTimeJson();
Boolean inOperationTime = isInOperationTime(timeJson);
if (inOperationTime) {
xpAppUserCoupons.add(coupon);
}
}
return xpAppUserCoupons;
}
/**
* 发送优惠券
*
* @param dto
* @return
*/
@Override
@Async("couponSendExecutor")
public R send(SendCouponDto dto) {
R coupon = xpCouponClient.getCouponById(dto.getCouponId());
if (coupon != null) {
XpCoupon xpCoupon = coupon.getData();
if (dto.getPhone() == null || dto.getPhone().isEmpty()) {
// 全部发放
R> allWhiteList = xpWhiteListClient.getAllWhiteList();
if (allWhiteList != null) {
List phoneList = allWhiteList.getData();
dto.setPhone(phoneList);
}
}
for (String phone : dto.getPhone()) {
UcUserInfo userByMobile = userResourcesClient.getUserByMobile(phone);
if (userByMobile != null) {
Long uid = userByMobile.getUid();
XpAppUserCoupon xpAppUserCoupon = new XpAppUserCoupon();
xpAppUserCoupon.setFullMoney(xpCoupon.getConditionalAmount());
xpAppUserCoupon.setMoney(xpCoupon.getAmount());
xpAppUserCoupon.setCouponId(dto.getCouponId());
xpAppUserCoupon.setAppUserId(uid);
xpAppUserCoupon.setExpirationTime(LocalDateTime.now().plusDays(7));
xpAppUserCoupon.setStatus(1);
xpAppUserCoupon.setCouponType(xpCoupon.getCouponType());
xpAppUserCoupon.setPhone(phone);
xpAppUserCoupon.setCouponName(xpCoupon.getCouponName());
xpAppUserCoupon.setOpenCityId("0");
xpAppUserCoupon.setTimeJson("[{\"time\": [\"00:00-23:59\"], \"type\": 3, \"typeStr\": 0}]");
baseMapper.insert(xpAppUserCoupon);
}
}
}
return null;
}
@Override
public Integer getUserCouponNum(String uid) {
if (uid == null) {
return 0;
}
Long num = baseMapper.selectCount(new LambdaQueryWrapper().eq(XpAppUserCoupon::getAppUserId, uid).eq(XpAppUserCoupon::getStatus, 1).lt(XpAppUserCoupon::getExpirationTime, LocalDateTime.now()));
return num.intValue();
}
/**
* 是否在运营内
*
* @param timeJson 规则json字符串
* @return
*/
private Boolean isInOperationTime(String timeJson) {
JSONArray jsonArray = JSONArray.parseArray(timeJson);
Integer holidayToday = holidayUtil.isHolidayToday();
for (Object o : jsonArray) {
JSONObject jsonObject = JSONObject.parseObject(o.toString());
String type = jsonObject.getString("type");
if (type.equals(holidayToday.toString())) {
JSONArray time = jsonObject.getJSONArray("time");
// 判断当前时间 是否在time时间段中
for (Object object : time) {
String[] timeArray = object.toString().split("-");
String startTime = timeArray[0];
String endTime = timeArray[1];
boolean currentTimeInRange = DateUtils.isCurrentTimeInRange(startTime, endTime);
if (currentTimeInRange) {
return true;
}
}
} else if (type.equals("3")) {
// 判断是什么类型 0每天 1-7 想起
String typeStr = jsonObject.getString("typeStr");
// 查看今天周几
Integer week = DateUtils.getWeek();
// 包含每天 或者 包含当天
if (typeStr.contains("0") || typeStr.contains(week.toString())) {
JSONArray time = jsonObject.getJSONArray("time");
// 判断当前时间 是否在time时间段中
for (Object object : time) {
String[] timeArray = object.toString().split("-");
String startTime = timeArray[0];
String endTime = timeArray[1];
boolean currentTimeInRange = DateUtils.isCurrentTimeInRange(startTime, endTime);
if (currentTimeInRange) {
return true;
}
}
}
}
}
return false;
}
@Override
public NavigationCouponPickerVo navigationCouponPicker(Long userId, NavigationCouponPickerRequest req) {
NavigationCouponPickerVo vo = new NavigationCouponPickerVo();
double orderAmount = req.getOrderAmount() == null ? 0d : req.getOrderAmount();
List couponList = baseMapper.selectList(new LambdaQueryWrapper()
.eq(XpAppUserCoupon::getStatus, 1)
.eq(XpAppUserCoupon::getAppUserId, userId)
.apply("(FIND_IN_SET({0}, open_city_id) or open_city_id='0')", req.getOpenCityId()));
couponList = timeRule(couponList);
Long bestId = null;
double bestEff = -1d;
LocalDateTime bestExp = null;
for (XpAppUserCoupon c : couponList) {
if (!couponAmountAvailable(c, orderAmount)) {
continue;
}
double eff = effectiveDiscountAmount(c, orderAmount);
if (eff <= 0) {
continue;
}
LocalDateTime exp = c.getExpirationTime();
if (bestId == null || eff > bestEff + 1e-6
|| (Math.abs(eff - bestEff) < 1e-6 && exp != null && bestExp != null && exp.isBefore(bestExp))
|| (Math.abs(eff - bestEff) < 1e-6 && bestExp == null && exp != null)) {
bestId = c.getId();
bestEff = eff;
bestExp = exp;
}
}
vo.setRecommendedUserCouponId(bestId);
Long selected = req.getSelectedUserCouponId();
Long applied;
if (selected != null && selected == 0L) {
applied = null;
} else if (selected != null && selected > 0L) {
XpAppUserCoupon chosen = couponList.stream().filter(c -> selected.equals(c.getId())).findFirst().orElse(null);
if (chosen != null && couponAmountAvailable(chosen, orderAmount)) {
applied = selected;
} else {
applied = null;
}
} else {
applied = bestId;
}
vo.setAppliedUserCouponId(applied);
long availCount = couponList.stream().filter(c -> couponAmountAvailable(c, orderAmount)).count();
if (availCount == 0) {
vo.setBubbleType(NavigationCouponBubbleType.HIDDEN);
List hiddenItems = buildNavigationItems(couponList, orderAmount, bestId, applied);
vo.setItems(hiddenItems);
vo.setListTotalCount(hiddenItems.size());
return vo;
}
if (applied == null) {
vo.setBubbleType(NavigationCouponBubbleType.MULTI_AVAILABLE);
vo.setAvailableCouponCount((int) availCount);
} else {
vo.setBubbleType(NavigationCouponBubbleType.SELECTED);
XpAppUserCoupon ap = couponList.stream().filter(c -> applied.equals(c.getId())).findFirst().orElse(null);
vo.setSelectedDiscountAmount(ap != null ? effectiveDiscountAmount(ap, orderAmount) : 0d);
}
List items = buildNavigationItems(couponList, orderAmount, bestId, applied);
vo.setItems(items);
vo.setListTotalCount(items.size());
return vo;
}
private List buildNavigationItems(List couponList, double orderAmount,
Long bestId, Long appliedId) {
List raw = new ArrayList<>();
for (XpAppUserCoupon c : couponList) {
NavigationCouponItemVo it = new NavigationCouponItemVo();
it.setUserCouponId(c.getId());
it.setCouponName(c.getCouponName());
it.setCouponType(c.getCouponType());
it.setDiscountMoney(c.getMoney() != null ? c.getMoney().doubleValue() : 0d);
it.setFullMoney(c.getFullMoney() != null ? c.getFullMoney().doubleValue() : null);
it.setExpireTime(c.getExpirationTime());
boolean ok = couponAmountAvailable(c, orderAmount);
it.setAvailable(ok);
if (c.getCouponType() != null && c.getCouponType() == 2) {
it.setThresholdLabel("满" + moneyStr(c.getFullMoney()) + "元可用");
if (!ok) {
it.setUnavailableReason("未满足使用门槛");
}
} else {
it.setThresholdLabel("无门槛");
}
it.setBestOffer(bestId != null && bestId.equals(c.getId()));
it.setSelected(appliedId != null && appliedId.equals(c.getId()));
raw.add(it);
}
List available = raw.stream().filter(x -> Boolean.TRUE.equals(x.getAvailable()))
.sorted(Comparator
.comparing((NavigationCouponItemVo x) -> !Boolean.TRUE.equals(x.getBestOffer()))
.thenComparing((NavigationCouponItemVo x) -> -effectiveDiscountAmount(findCoupon(couponList, x.getUserCouponId()), orderAmount))
.thenComparing(NavigationCouponItemVo::getExpireTime, Comparator.nullsLast(Comparator.naturalOrder())))
.collect(Collectors.toList());
List unavailable = raw.stream().filter(x -> !Boolean.TRUE.equals(x.getAvailable()))
.sorted(Comparator.comparing(NavigationCouponItemVo::getExpireTime, Comparator.nullsLast(Comparator.naturalOrder())))
.collect(Collectors.toList());
available.addAll(unavailable);
return available;
}
private static XpAppUserCoupon findCoupon(List list, Long id) {
return list.stream().filter(c -> Objects.equals(c.getId(), id)).findFirst().orElse(null);
}
private static String moneyStr(BigDecimal b) {
if (b == null) {
return "0";
}
return b.stripTrailingZeros().toPlainString();
}
private static boolean couponAmountAvailable(XpAppUserCoupon c, double orderAmount) {
if (c.getCouponType() == null || c.getCouponType() == 1) {
return true;
}
if (c.getCouponType() == 2) {
return c.getFullMoney() != null && orderAmount >= c.getFullMoney().doubleValue();
}
return true;
}
private static double effectiveDiscountAmount(XpAppUserCoupon c, double orderAmount) {
if (c == null || c.getMoney() == null) {
return 0d;
}
double m = c.getMoney().doubleValue();
if (!couponAmountAvailable(c, orderAmount)) {
return 0d;
}
return Math.min(orderAmount, m);
}
}