|
|
@@ -0,0 +1,178 @@
|
|
|
+package shop.alien.store.service.impl;
|
|
|
+
|
|
|
+import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
|
+import com.baomidou.mybatisplus.core.metadata.IPage;
|
|
|
+import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|
|
+import lombok.RequiredArgsConstructor;
|
|
|
+import org.apache.commons.collections4.CollectionUtils;
|
|
|
+import org.springframework.stereotype.Service;
|
|
|
+import shop.alien.entity.result.R;
|
|
|
+import shop.alien.entity.store.StoreOrder;
|
|
|
+import shop.alien.entity.store.vo.TurnoverBreakdownItemVO;
|
|
|
+import shop.alien.entity.store.vo.TurnoverDetailItemVO;
|
|
|
+import shop.alien.entity.store.vo.TurnoverSummaryVO;
|
|
|
+import shop.alien.mapper.StoreOrderMapper;
|
|
|
+import shop.alien.store.service.StoreTurnoverService;
|
|
|
+
|
|
|
+import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
+import java.util.*;
|
|
|
+import java.util.stream.Collectors;
|
|
|
+
|
|
|
+@Service
|
|
|
+@RequiredArgsConstructor
|
|
|
+public class StoreTurnoverServiceImpl implements StoreTurnoverService {
|
|
|
+
|
|
|
+ private final StoreOrderMapper storeOrderMapper;
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<TurnoverSummaryVO> summary(Integer storeId, Date startTime, Date endTime) {
|
|
|
+ LambdaQueryWrapper<StoreOrder> qw = basePaidQuery(storeId, startTime, endTime);
|
|
|
+ List<StoreOrder> orders = storeOrderMapper.selectList(qw);
|
|
|
+
|
|
|
+ BigDecimal totalSales = orders.stream()
|
|
|
+ .map(StoreOrder::getPayAmount)
|
|
|
+ .filter(Objects::nonNull)
|
|
|
+ .reduce(BigDecimal.ZERO, BigDecimal::add);
|
|
|
+ int orderCount = orders.size();
|
|
|
+ BigDecimal avg = orderCount == 0 ? BigDecimal.ZERO :
|
|
|
+ totalSales.divide(BigDecimal.valueOf(orderCount), 2, RoundingMode.HALF_UP);
|
|
|
+
|
|
|
+ // 支付方式分布
|
|
|
+ Map<Integer, String> payTypeName = new HashMap<>();
|
|
|
+ payTypeName.put(1, "微信");
|
|
|
+ payTypeName.put(2, "支付宝");
|
|
|
+ payTypeName.put(4, "银行卡");
|
|
|
+ payTypeName.put(3, "现金");
|
|
|
+
|
|
|
+ List<TurnoverBreakdownItemVO> payTypeItems = buildBreakdown(
|
|
|
+ groupByAndSum(orders, StoreOrder::getPayType),
|
|
|
+ payTypeName,
|
|
|
+ totalSales
|
|
|
+ );
|
|
|
+
|
|
|
+ // 收款方式分布
|
|
|
+ Map<Integer, String> receiveName = new HashMap<>();
|
|
|
+ receiveName.put(1, "手机支付");
|
|
|
+ receiveName.put(2, "收银台");
|
|
|
+ List<TurnoverBreakdownItemVO> receiveItems = buildBreakdown(
|
|
|
+ groupByAndSum(orders, StoreOrder::getPaymentMethod),
|
|
|
+ receiveName,
|
|
|
+ totalSales
|
|
|
+ );
|
|
|
+
|
|
|
+ TurnoverSummaryVO vo = new TurnoverSummaryVO();
|
|
|
+ vo.setTotalSalesAmount(scale2(totalSales));
|
|
|
+ vo.setTotalOrderCount(orderCount);
|
|
|
+ vo.setAverageOrderAmount(scale2(avg));
|
|
|
+ vo.setPayTypeBreakdown(payTypeItems);
|
|
|
+ vo.setReceiveMethodBreakdown(receiveItems);
|
|
|
+ return R.data(vo);
|
|
|
+ }
|
|
|
+
|
|
|
+ @Override
|
|
|
+ public R<IPage<TurnoverDetailItemVO>> details(Integer storeId, Integer payType, Integer paymentMethod,
|
|
|
+ Date startTime, Date endTime, Integer pageNum, Integer pageSize) {
|
|
|
+ LambdaQueryWrapper<StoreOrder> qw = basePaidQuery(storeId, startTime, endTime);
|
|
|
+ if (payType != null) {
|
|
|
+ qw.eq(StoreOrder::getPayType, payType);
|
|
|
+ }
|
|
|
+ if (paymentMethod != null) {
|
|
|
+ qw.eq(StoreOrder::getPaymentMethod, paymentMethod);
|
|
|
+ }
|
|
|
+ qw.orderByDesc(StoreOrder::getPayTime).orderByDesc(StoreOrder::getId);
|
|
|
+ Page<StoreOrder> page = new Page<>(pageNum == null ? 1 : pageNum, pageSize == null ? 10 : pageSize);
|
|
|
+ IPage<StoreOrder> orderPage = storeOrderMapper.selectPage(page, qw);
|
|
|
+
|
|
|
+ Map<Integer, String> payTypeName = new HashMap<>();
|
|
|
+ payTypeName.put(1, "微信");
|
|
|
+ payTypeName.put(2, "支付宝");
|
|
|
+ payTypeName.put(4, "银行卡");
|
|
|
+ payTypeName.put(3, "现金");
|
|
|
+ Map<Integer, String> receiveName = new HashMap<>();
|
|
|
+ receiveName.put(1, "手机支付");
|
|
|
+ receiveName.put(2, "收银台");
|
|
|
+
|
|
|
+ List<TurnoverDetailItemVO> details = orderPage.getRecords().stream().map(o -> {
|
|
|
+ TurnoverDetailItemVO item = new TurnoverDetailItemVO();
|
|
|
+ item.setOrderNo(o.getOrderNo());
|
|
|
+ item.setPayAmount(scale2(o.getPayAmount() == null ? BigDecimal.ZERO : o.getPayAmount()));
|
|
|
+ item.setPayTime(o.getPayTime());
|
|
|
+ item.setPayType(o.getPayType());
|
|
|
+ item.setPayTypeName(payTypeName.getOrDefault(o.getPayType(), "-"));
|
|
|
+ item.setPaymentMethod(o.getPaymentMethod());
|
|
|
+ item.setPaymentMethodName(receiveName.getOrDefault(o.getPaymentMethod(), "-"));
|
|
|
+ return item;
|
|
|
+ }).collect(Collectors.toList());
|
|
|
+
|
|
|
+ Page<TurnoverDetailItemVO> result = new Page<>(orderPage.getCurrent(), orderPage.getSize(), orderPage.getTotal());
|
|
|
+ result.setRecords(details);
|
|
|
+ return R.data(result);
|
|
|
+ }
|
|
|
+
|
|
|
+ private LambdaQueryWrapper<StoreOrder> basePaidQuery(Integer storeId, Date startTime, Date endTime) {
|
|
|
+ LambdaQueryWrapper<StoreOrder> qw = new LambdaQueryWrapper<>();
|
|
|
+ qw.eq(StoreOrder::getStoreId, storeId)
|
|
|
+ .eq(StoreOrder::getPayStatus, 1)
|
|
|
+ .eq(StoreOrder::getOrderStatus, 1)
|
|
|
+ .eq(StoreOrder::getDeleteFlag, 0);
|
|
|
+ if (startTime != null) {
|
|
|
+ qw.ge(StoreOrder::getPayTime, startTime);
|
|
|
+ }
|
|
|
+ if (endTime != null) {
|
|
|
+ qw.le(StoreOrder::getPayTime, endTime);
|
|
|
+ }
|
|
|
+ return qw;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<Integer, List<StoreOrder>> groupBy(List<StoreOrder> orders, java.util.function.Function<StoreOrder, Integer> classifier) {
|
|
|
+ if (CollectionUtils.isEmpty(orders)) {
|
|
|
+ return Collections.emptyMap();
|
|
|
+ }
|
|
|
+ return orders.stream().collect(Collectors.groupingBy(classifier));
|
|
|
+ }
|
|
|
+
|
|
|
+ private List<TurnoverBreakdownItemVO> buildBreakdown(Map<Integer, SumCount> map, Map<Integer, String> nameMap, BigDecimal total) {
|
|
|
+ List<TurnoverBreakdownItemVO> list = new ArrayList<>();
|
|
|
+ for (Map.Entry<Integer, SumCount> e : map.entrySet()) {
|
|
|
+ Integer code = e.getKey();
|
|
|
+ SumCount sc = e.getValue();
|
|
|
+ TurnoverBreakdownItemVO item = new TurnoverBreakdownItemVO();
|
|
|
+ item.setCode(code);
|
|
|
+ item.setName(nameMap.getOrDefault(code, "-"));
|
|
|
+ item.setAmount(scale2(sc.amount));
|
|
|
+ item.setCount(sc.count);
|
|
|
+ item.setPercent(total.signum() == 0 ? BigDecimal.ZERO :
|
|
|
+ sc.amount.multiply(BigDecimal.valueOf(100)).divide(total, 2, RoundingMode.HALF_UP));
|
|
|
+ list.add(item);
|
|
|
+ }
|
|
|
+ // 保持稳定顺序:根据 code 升序
|
|
|
+ list.sort(Comparator.comparing(TurnoverBreakdownItemVO::getCode, Comparator.nullsLast(Integer::compareTo)));
|
|
|
+ return list;
|
|
|
+ }
|
|
|
+
|
|
|
+ private Map<Integer, SumCount> groupByAndSum(List<StoreOrder> orders, java.util.function.Function<StoreOrder, Integer> classifier) {
|
|
|
+ Map<Integer, SumCount> result = new HashMap<>();
|
|
|
+ for (StoreOrder o : orders) {
|
|
|
+ Integer key = classifier.apply(o);
|
|
|
+ if (key == null) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ SumCount sc = result.computeIfAbsent(key, k -> new SumCount());
|
|
|
+ BigDecimal amt = o.getPayAmount() == null ? BigDecimal.ZERO : o.getPayAmount();
|
|
|
+ sc.amount = sc.amount.add(amt);
|
|
|
+ sc.count++;
|
|
|
+ }
|
|
|
+ return result;
|
|
|
+ }
|
|
|
+
|
|
|
+ private BigDecimal scale2(BigDecimal v) {
|
|
|
+ return v == null ? BigDecimal.ZERO : v.setScale(2, RoundingMode.HALF_UP);
|
|
|
+ }
|
|
|
+
|
|
|
+ private static class SumCount {
|
|
|
+ private BigDecimal amount = BigDecimal.ZERO;
|
|
|
+ private int count = 0;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|