package com.doumee.service.business.impl;
|
|
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
import com.doumee.core.constants.Constants;
|
import com.doumee.core.utils.Utils;
|
import com.doumee.dao.business.*;
|
import com.doumee.dao.business.model.*;
|
import com.doumee.dao.dto.DataBoardQueryDTO;
|
import com.doumee.dao.vo.*;
|
import com.doumee.service.business.DataBoardService;
|
import lombok.extern.slf4j.Slf4j;
|
import org.apache.commons.lang3.StringUtils;
|
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.stereotype.Service;
|
|
import java.math.BigDecimal;
|
import java.math.RoundingMode;
|
import java.text.SimpleDateFormat;
|
import java.util.*;
|
import java.util.stream.Collectors;
|
|
@Slf4j
|
@Service
|
public class DataBoardServiceImpl implements DataBoardService {
|
|
@Autowired
|
private MemberMapper memberMapper;
|
@Autowired
|
private ShopInfoMapper shopInfoMapper;
|
@Autowired
|
private DriverInfoMapper driverInfoMapper;
|
@Autowired
|
private OrdersMapper ordersMapper;
|
@Autowired
|
private OrdersDetailMapper ordersDetailMapper;
|
@Autowired
|
private OtherOrdersMapper otherOrdersMapper;
|
@Autowired
|
private OrdersRefundMapper ordersRefundMapper;
|
|
@Override
|
public DataBoardVO overview(DataBoardQueryDTO query) {
|
DataBoardVO vo = new DataBoardVO();
|
// 会员总数(不受时间/门店影响)
|
vo.setMemberCount(memberMapper.selectCount(new QueryWrapper<Member>().lambda()
|
.eq(Member::getDeleted, Constants.ZERO)
|
.eq(Member::getStatus, Constants.ZERO)));
|
// 门店总数(auditStatus=3 正式版本)
|
vo.setShopCount(shopInfoMapper.selectCount(new QueryWrapper<ShopInfo>().lambda()
|
.eq(ShopInfo::getAuditStatus, Constants.THREE)
|
.eq(ShopInfo::getVersionType, Constants.ZERO)
|
.eq(ShopInfo::getDeleted, Constants.ZERO)));
|
// 司机总数(auditStatus=3 正式版本)
|
vo.setDriverCount(driverInfoMapper.selectCount(new QueryWrapper<DriverInfo>().lambda()
|
.eq(DriverInfo::getAuditStatus, Constants.THREE)
|
.eq(DriverInfo::getVersionType, Constants.ZERO)
|
.eq(DriverInfo::getDeleted, Constants.ZERO)));
|
|
// 周期总订单数
|
vo.setOrderCount(ordersMapper.selectCount(buildOrderQueryWrapper(query)));
|
|
// 周期营收
|
List<Orders> orders = ordersMapper.selectList(buildOrderQueryWrapper(query));
|
long orderRevenue = 0L;
|
for (Orders o : orders) {
|
long pay = o.getPayAmount() != null ? o.getPayAmount() : 0L;
|
long refund = o.getRefundAmount() != null ? o.getRefundAmount() : 0L;
|
long overdue = (o.getOverdueStatus() != null && o.getOverdueStatus() == 2 && o.getOverdueAmount() != null)
|
? o.getOverdueAmount() : 0L;
|
orderRevenue += pay - refund + overdue;
|
}
|
// 加上逾期费用订单(OtherOrders type=2)
|
QueryWrapper<OtherOrders> otherQw = new QueryWrapper<>();
|
otherQw.lambda()
|
.eq(OtherOrders::getType, 2)
|
.eq(OtherOrders::getPayStatus, Constants.ONE)
|
.eq(OtherOrders::getDeleted, Constants.ZERO);
|
if (query.getStartDate() != null) {
|
otherQw.lambda().ge(OtherOrders::getPayTime, query.getStartDate());
|
}
|
if (query.getEndDate() != null) {
|
otherQw.lambda().le(OtherOrders::getPayTime, Utils.Date.getEnd(query.getEndDate()));
|
}
|
List<OtherOrders> otherOrders = otherOrdersMapper.selectList(otherQw);
|
long otherRevenue = otherOrders.stream()
|
.mapToLong(o -> o.getPayAccount() != null ? o.getPayAccount() : 0L).sum();
|
vo.setTotalRevenue(BigDecimal.valueOf(orderRevenue + otherRevenue)
|
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
|
|
// 行李类型占比
|
List<Integer> orderIds = orders.stream().map(Orders::getId).collect(Collectors.toList());
|
List<LuggageTypeItem> luggageTypeList = new ArrayList<>();
|
if (!orderIds.isEmpty()) {
|
List<OrdersDetail> details = ordersDetailMapper.selectList(new QueryWrapper<OrdersDetail>().lambda()
|
.in(OrdersDetail::getOrderId, orderIds)
|
.eq(OrdersDetail::getDeleted, Constants.ZERO));
|
Map<String, List<OrdersDetail>> grouped = details.stream()
|
.filter(d -> StringUtils.isNotBlank(d.getLuggageName()))
|
.collect(Collectors.groupingBy(OrdersDetail::getLuggageName));
|
for (Map.Entry<String, List<OrdersDetail>> entry : grouped.entrySet()) {
|
LuggageTypeItem item = new LuggageTypeItem();
|
item.setLuggageName(entry.getKey());
|
item.setOrderCount((long) entry.getValue().stream().map(OrdersDetail::getOrderId).distinct().count());
|
item.setLuggageCount((long) entry.getValue().stream()
|
.mapToInt(d -> d.getNum() != null ? d.getNum() : 0).sum());
|
luggageTypeList.add(item);
|
}
|
}
|
vo.setLuggageTypeList(luggageTypeList);
|
return vo;
|
}
|
|
@Override
|
public List<MemberTrendVO> memberTrend() {
|
Date startDate = get30DaysAgoStart();
|
List<Member> members = memberMapper.selectList(new QueryWrapper<Member>().lambda()
|
.eq(Member::getDeleted, Constants.ZERO)
|
.eq(Member::getStatus, Constants.ZERO)
|
.ge(Member::getCreateTime, startDate));
|
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
Map<String, Long> map = members.stream()
|
.collect(Collectors.groupingBy(m -> sdf.format(m.getCreateTime()), Collectors.counting()));
|
|
return buildDailyList(startDate, (date) -> {
|
MemberTrendVO vo = new MemberTrendVO();
|
vo.setDate(date);
|
vo.setCount(map.getOrDefault(date, 0L));
|
return vo;
|
});
|
}
|
|
@Override
|
public List<OrderTrendVO> orderTrend() {
|
Date startDate = get30DaysAgoStart();
|
List<Orders> orders = ordersMapper.selectList(new QueryWrapper<Orders>().lambda()
|
.eq(Orders::getDeleted, Constants.ZERO)
|
.in(Orders::getStatus,
|
Constants.OrderStatus.waitDeposit.getKey(),
|
Constants.OrderStatus.deposited.getKey(),
|
Constants.OrderStatus.accepted.getKey(),
|
Constants.OrderStatus.delivering.getKey(),
|
Constants.OrderStatus.arrived.getKey(),
|
Constants.OrderStatus.overdue.getKey())
|
.ge(Orders::getCreateTime, startDate));
|
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
Map<String, List<Orders>> grouped = orders.stream()
|
.collect(Collectors.groupingBy(o -> sdf.format(o.getCreateTime())));
|
|
return buildDailyList(startDate, (date) -> {
|
List<Orders> dayOrders = grouped.getOrDefault(date, Collections.emptyList());
|
OrderTrendVO vo = new OrderTrendVO();
|
vo.setDate(date);
|
vo.setLocalCount(dayOrders.stream().filter(o -> Constants.equalsInteger(o.getType(), Constants.ZERO)).count());
|
vo.setRemoteCount(dayOrders.stream().filter(o -> Constants.equalsInteger(o.getType(), Constants.ONE)).count());
|
return vo;
|
});
|
}
|
|
@Override
|
public List<RevenueTrendVO> revenueTrend() {
|
Date startDate = get30DaysAgoStart();
|
|
List<Orders> orders = ordersMapper.selectList(new QueryWrapper<Orders>().lambda()
|
.eq(Orders::getDeleted, Constants.ZERO)
|
.in(Orders::getStatus,
|
Constants.OrderStatus.waitDeposit.getKey(),
|
Constants.OrderStatus.deposited.getKey(),
|
Constants.OrderStatus.accepted.getKey(),
|
Constants.OrderStatus.delivering.getKey(),
|
Constants.OrderStatus.arrived.getKey(),
|
Constants.OrderStatus.overdue.getKey())
|
.ge(Orders::getCreateTime, startDate));
|
|
List<OtherOrders> otherOrders = otherOrdersMapper.selectList(new QueryWrapper<OtherOrders>().lambda()
|
.eq(OtherOrders::getType, 2)
|
.eq(OtherOrders::getPayStatus, Constants.ONE)
|
.eq(OtherOrders::getDeleted, Constants.ZERO)
|
.ge(OtherOrders::getPayTime, startDate));
|
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
Map<String, Long> localOrderRevenue = new HashMap<>();
|
Map<String, Long> remoteOrderRevenue = new HashMap<>();
|
for (Orders o : orders) {
|
String date = sdf.format(o.getCreateTime());
|
long pay = o.getPayAmount() != null ? o.getPayAmount() : 0L;
|
long refund = o.getRefundAmount() != null ? o.getRefundAmount() : 0L;
|
long overdue = (o.getOverdueStatus() != null && o.getOverdueStatus() == 2 && o.getOverdueAmount() != null)
|
? o.getOverdueAmount() : 0L;
|
long rev = pay - refund + overdue;
|
if (Constants.equalsInteger(o.getType(), Constants.ZERO)) {
|
localOrderRevenue.merge(date, rev, Long::sum);
|
} else {
|
remoteOrderRevenue.merge(date, rev, Long::sum);
|
}
|
}
|
|
Map<String, Long> otherRevenueMap = new HashMap<>();
|
for (OtherOrders oo : otherOrders) {
|
String date = sdf.format(oo.getPayTime());
|
long amt = oo.getPayAccount() != null ? oo.getPayAccount() : 0L;
|
otherRevenueMap.merge(date, amt, Long::sum);
|
}
|
|
return buildDailyList(startDate, (date) -> {
|
RevenueTrendVO vo = new RevenueTrendVO();
|
vo.setDate(date);
|
long local = localOrderRevenue.getOrDefault(date, 0L) + otherRevenueMap.getOrDefault(date, 0L);
|
long remote = remoteOrderRevenue.getOrDefault(date, 0L);
|
vo.setLocalRevenue(BigDecimal.valueOf(local).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
|
vo.setRemoteRevenue(BigDecimal.valueOf(remote).divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
|
return vo;
|
});
|
}
|
|
@Override
|
public ShopPerformanceVO shopPerformance(DataBoardQueryDTO query) {
|
ShopPerformanceVO vo = new ShopPerformanceVO();
|
|
// 1. 总订单数 + 营收(status 1-6)
|
QueryWrapper<Orders> qw = buildOrderQueryWrapper(query);
|
List<Orders> orders = ordersMapper.selectList(qw);
|
vo.setTotalOrderCount((long) orders.size());
|
|
long revenue = 0L;
|
for (Orders o : orders) {
|
long pay = o.getPayAmount() != null ? o.getPayAmount() : 0L;
|
long overdue = o.getOverdueAmount() != null ? o.getOverdueAmount() : 0L;
|
long refund = o.getRefundAmount() != null ? o.getRefundAmount() : 0L;
|
revenue += pay + overdue - refund;
|
}
|
vo.setTotalRevenue(BigDecimal.valueOf(Math.max(revenue, 0L))
|
.divide(BigDecimal.valueOf(100), 2, RoundingMode.HALF_UP));
|
|
// 2. 已完成订单数(status=7,同等筛选条件)
|
QueryWrapper<Orders> completedQw = new QueryWrapper<>();
|
completedQw.lambda()
|
.eq(Orders::getDeleted, Constants.ZERO)
|
.eq(Orders::getStatus, Constants.OrderStatus.finished.getKey());
|
if (query.getStartDate() != null) {
|
completedQw.lambda().ge(Orders::getCreateTime, query.getStartDate());
|
}
|
if (query.getEndDate() != null) {
|
completedQw.lambda().le(Orders::getCreateTime, Utils.Date.getEnd(query.getEndDate()));
|
}
|
if (query.getShopId() != null) {
|
completedQw.lambda().and(w -> w.eq(Orders::getDepositShopId, query.getShopId())
|
.or().eq(Orders::getTakeShopId, query.getShopId()));
|
}
|
vo.setCompletedCount(ordersMapper.selectCount(completedQw));
|
|
// 3. 已退款订单数(orders_refund status=1 的去重订单数)
|
List<Integer> orderIds = orders.stream().map(Orders::getId).collect(Collectors.toList());
|
if (!orderIds.isEmpty()) {
|
QueryWrapper<OrdersRefund> refundQw = new QueryWrapper<>();
|
refundQw.lambda()
|
.in(OrdersRefund::getOrderId, orderIds)
|
.eq(OrdersRefund::getStatus, Constants.ONE)
|
.eq(OrdersRefund::getDeleted, Constants.ZERO);
|
List<OrdersRefund> refundRecords = ordersRefundMapper.selectList(refundQw);
|
long refundedOrderCount = refundRecords.stream()
|
.map(OrdersRefund::getOrderId).distinct().count();
|
vo.setRefundedCount(refundedOrderCount);
|
} else {
|
vo.setRefundedCount(0L);
|
}
|
|
// 4. 订单完成率 & 退款率(分母 = status 1-6 + status 7)
|
long totalCount = vo.getTotalOrderCount() + vo.getCompletedCount();
|
if (totalCount > 0) {
|
BigDecimal hundred = BigDecimal.valueOf(100);
|
vo.setCompletionRate(BigDecimal.valueOf(vo.getCompletedCount())
|
.multiply(hundred).divide(BigDecimal.valueOf(totalCount), 2, RoundingMode.HALF_UP));
|
vo.setRefundRate(BigDecimal.valueOf(vo.getRefundedCount())
|
.multiply(hundred).divide(BigDecimal.valueOf(totalCount), 2, RoundingMode.HALF_UP));
|
} else {
|
vo.setCompletionRate(BigDecimal.ZERO);
|
vo.setRefundRate(BigDecimal.ZERO);
|
}
|
|
return vo;
|
}
|
|
// ========== 私有方法 ==========
|
|
private QueryWrapper<Orders> buildOrderQueryWrapper(DataBoardQueryDTO query) {
|
QueryWrapper<Orders> qw = new QueryWrapper<>();
|
qw.lambda()
|
.eq(Orders::getDeleted, Constants.ZERO)
|
.in(Orders::getStatus,
|
Constants.OrderStatus.waitDeposit.getKey(),
|
Constants.OrderStatus.deposited.getKey(),
|
Constants.OrderStatus.accepted.getKey(),
|
Constants.OrderStatus.delivering.getKey(),
|
Constants.OrderStatus.arrived.getKey(),
|
Constants.OrderStatus.overdue.getKey());
|
if (query.getStartDate() != null) {
|
qw.lambda().ge(Orders::getCreateTime, query.getStartDate());
|
}
|
if (query.getEndDate() != null) {
|
qw.lambda().le(Orders::getCreateTime, Utils.Date.getEnd(query.getEndDate()));
|
}
|
if (query.getShopId() != null) {
|
qw.lambda().and(w -> w.eq(Orders::getDepositShopId, query.getShopId())
|
.or().eq(Orders::getTakeShopId, query.getShopId()));
|
}
|
return qw;
|
}
|
|
private Date get30DaysAgoStart() {
|
Calendar cal = Calendar.getInstance();
|
cal.set(Calendar.HOUR_OF_DAY, 0);
|
cal.set(Calendar.MINUTE, 0);
|
cal.set(Calendar.SECOND, 0);
|
cal.set(Calendar.MILLISECOND, 0);
|
cal.add(Calendar.DAY_OF_MONTH, -29);
|
return cal.getTime();
|
}
|
|
@FunctionalInterface
|
private interface DailyVOBuilder<T> {
|
T build(String date);
|
}
|
|
private <T> List<T> buildDailyList(Date startDate, DailyVOBuilder<T> builder) {
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
|
List<T> result = new ArrayList<>();
|
Calendar loop = Calendar.getInstance();
|
loop.setTime(startDate);
|
Calendar end = Calendar.getInstance();
|
while (!loop.after(end)) {
|
result.add(builder.build(sdf.format(loop.getTime())));
|
loop.add(Calendar.DAY_OF_MONTH, 1);
|
}
|
return result;
|
}
|
}
|