| | |
| | | import lombok.extern.slf4j.Slf4j; |
| | | import org.apache.commons.lang3.StringUtils; |
| | | import org.springframework.beans.factory.annotation.Autowired; |
| | | import org.apache.poi.ss.usermodel.*; |
| | | import org.apache.poi.xssf.streaming.SXSSFWorkbook; |
| | | import org.springframework.stereotype.Service; |
| | | |
| | | import javax.servlet.http.HttpServletResponse; |
| | | |
| | | import java.math.BigDecimal; |
| | | import java.math.RoundingMode; |
| | |
| | | Map<String, Long> map = members.stream() |
| | | .collect(Collectors.groupingBy(m -> sdf.format(m.getCreateTime()), Collectors.counting())); |
| | | |
| | | return buildTrendList(range, (date) -> { |
| | | return buildTrendList(range, (key, label) -> { |
| | | MemberTrendVO vo = new MemberTrendVO(); |
| | | vo.setDate(date); |
| | | vo.setCount(map.getOrDefault(date, 0L)); |
| | | vo.setDate(label); |
| | | vo.setCount(map.getOrDefault(key, 0L)); |
| | | return vo; |
| | | }); |
| | | } |
| | |
| | | Map<String, List<Orders>> grouped = orders.stream() |
| | | .collect(Collectors.groupingBy(o -> sdf.format(o.getCreateTime()))); |
| | | |
| | | return buildTrendList(range, (date) -> { |
| | | List<Orders> dayOrders = grouped.getOrDefault(date, Collections.emptyList()); |
| | | return buildTrendList(range, (key, label) -> { |
| | | List<Orders> dayOrders = grouped.getOrDefault(key, Collections.emptyList()); |
| | | OrderTrendVO vo = new OrderTrendVO(); |
| | | vo.setDate(date); |
| | | vo.setDate(label); |
| | | 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; |
| | |
| | | } |
| | | } |
| | | |
| | | return buildTrendList(range, (date) -> { |
| | | return buildTrendList(range, (key, label) -> { |
| | | RevenueTrendVO vo = new RevenueTrendVO(); |
| | | vo.setDate(date); |
| | | long local = localOrderRevenue.getOrDefault(date, 0L); |
| | | long remote = remoteOrderRevenue.getOrDefault(date, 0L); |
| | | vo.setDate(label); |
| | | long local = localOrderRevenue.getOrDefault(key, 0L); |
| | | long remote = remoteOrderRevenue.getOrDefault(key, 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; |
| | |
| | | vo.setNetRevenue(BigDecimal.valueOf(netRev).divide(hundred, 2, RoundingMode.HALF_UP)); |
| | | result.add(vo); |
| | | } |
| | | Collections.reverse(result); |
| | | return result; |
| | | } |
| | | |
| | |
| | | return qw; |
| | | } |
| | | |
| | | @Override |
| | | public void revenueTrendExport(TrendQueryDTO query, HttpServletResponse response) { |
| | | TrendDateRange range = parseTrendDateRange(query); |
| | | SimpleDateFormat sdf = new SimpleDateFormat(range.pattern); |
| | | |
| | | // 查询订单 |
| | | 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(), |
| | | Constants.OrderStatus.finished.getKey()) |
| | | .ge(Orders::getCreateTime, range.startDate) |
| | | .le(Orders::getCreateTime, range.endDate)); |
| | | |
| | | // 查询逾期费用 |
| | | List<Integer> orderIds = orders.stream().map(Orders::getId).collect(Collectors.toList()); |
| | | Map<Integer, Long> overduePaidMap = new HashMap<>(); |
| | | if (!orderIds.isEmpty()) { |
| | | List<OtherOrders> overdueOrders = otherOrdersMapper.selectList(new QueryWrapper<OtherOrders>().lambda() |
| | | .eq(OtherOrders::getType, 2) |
| | | .eq(OtherOrders::getPayStatus, Constants.ONE) |
| | | .eq(OtherOrders::getDeleted, Constants.ZERO) |
| | | .in(OtherOrders::getOrderId, orderIds)); |
| | | for (OtherOrders oo : overdueOrders) { |
| | | long amt = oo.getPayAccount() != null ? oo.getPayAccount() : 0L; |
| | | overduePaidMap.merge(oo.getOrderId(), amt, Long::sum); |
| | | } |
| | | } |
| | | |
| | | // 生成日期列头 |
| | | List<String> dateKeys = new ArrayList<>(); |
| | | List<String> dateLabels = new ArrayList<>(); |
| | | if (range.byMonth) { |
| | | Calendar loop = Calendar.getInstance(); |
| | | loop.setTime(range.startDate); |
| | | Calendar end = Calendar.getInstance(); |
| | | end.setTime(range.endDate); |
| | | while (!loop.after(end)) { |
| | | dateKeys.add(sdf.format(loop.getTime())); |
| | | dateLabels.add(sdf.format(loop.getTime())); |
| | | loop.add(Calendar.DAY_OF_MONTH, 1); |
| | | } |
| | | } else { |
| | | Calendar endCal = Calendar.getInstance(); |
| | | endCal.setTime(range.endDate); |
| | | int endMonth = endCal.get(Calendar.MONTH); |
| | | for (int m = 0; m <= endMonth; m++) { |
| | | dateKeys.add(String.format("%02d", m + 1)); |
| | | dateLabels.add((m + 1) + "月"); |
| | | } |
| | | } |
| | | |
| | | // 收集所有门店 |
| | | Map<Integer, String> shopNameMap = new LinkedHashMap<>(); |
| | | for (Orders o : orders) { |
| | | if (o.getDepositShopId() != null && !shopNameMap.containsKey(o.getDepositShopId())) { |
| | | ShopInfo shop = shopInfoMapper.selectById(o.getDepositShopId()); |
| | | shopNameMap.put(o.getDepositShopId(), shop != null ? shop.getName() : "未知门店"); |
| | | } |
| | | } |
| | | |
| | | // 按门店+日期分组计算营收 |
| | | // key = shopId + "_" + dateKey |
| | | Map<String, Long> revenueMap = 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 = overduePaidMap.getOrDefault(o.getId(), 0L); |
| | | long rev = pay - refund + overdue; |
| | | Integer shopId = o.getDepositShopId(); |
| | | if (shopId != null) { |
| | | String key = shopId + "_" + date; |
| | | revenueMap.merge(key, rev, Long::sum); |
| | | } |
| | | } |
| | | |
| | | // 构建Excel |
| | | try (SXSSFWorkbook wb = new SXSSFWorkbook()) { |
| | | Sheet sheet = wb.createSheet("营收趋势"); |
| | | |
| | | // 创建样式 |
| | | CellStyle titleStyle = wb.createCellStyle(); |
| | | Font titleFont = wb.createFont(); |
| | | titleFont.setBold(true); |
| | | titleFont.setFontHeightInPoints((short) 16); |
| | | titleStyle.setFont(titleFont); |
| | | titleStyle.setAlignment(HorizontalAlignment.CENTER); |
| | | |
| | | CellStyle headerStyle = wb.createCellStyle(); |
| | | Font headerFont = wb.createFont(); |
| | | headerFont.setBold(true); |
| | | headerFont.setFontHeightInPoints((short) 12); |
| | | headerStyle.setFont(headerFont); |
| | | headerStyle.setAlignment(HorizontalAlignment.CENTER); |
| | | headerStyle.setFillForegroundColor(IndexedColors.GREY_25_PERCENT.getIndex()); |
| | | headerStyle.setFillPattern(FillPatternType.SOLID_FOREGROUND); |
| | | |
| | | CellStyle dataStyle = wb.createCellStyle(); |
| | | dataStyle.setAlignment(HorizontalAlignment.RIGHT); |
| | | |
| | | // 生成标题 |
| | | String title; |
| | | int totalCols = dateLabels.size() + 1; |
| | | if (range.byMonth) { |
| | | String yearMonth = query.getMonth(); // yyyy-MM |
| | | String[] ym = yearMonth.split("-"); |
| | | title = "行李寄存门店" + ym[0] + "年" + Integer.valueOf(ym[1]) + "月日报表"; |
| | | } else { |
| | | title = "行李寄存门店" + query.getYear() + "年月报表"; |
| | | } |
| | | |
| | | // 写标题行(第0行,合并所有列) |
| | | Row titleRow = sheet.createRow(0); |
| | | titleRow.setHeight((short) 600); |
| | | Cell titleCell = titleRow.createCell(0); |
| | | titleCell.setCellValue(title); |
| | | titleCell.setCellStyle(titleStyle); |
| | | sheet.addMergedRegion(new org.apache.poi.ss.util.CellRangeAddress(0, 0, 0, totalCols - 1)); |
| | | |
| | | // 写列头(第1行) |
| | | Row headerRow = sheet.createRow(1); |
| | | Cell shopCell = headerRow.createCell(0); |
| | | shopCell.setCellValue("门店名称"); |
| | | shopCell.setCellStyle(headerStyle); |
| | | sheet.setColumnWidth(0, 20 * 256); |
| | | |
| | | for (int i = 0; i < dateLabels.size(); i++) { |
| | | Cell cell = headerRow.createCell(i + 1); |
| | | cell.setCellValue(dateLabels.get(i)); |
| | | cell.setCellStyle(headerStyle); |
| | | sheet.setColumnWidth(i + 1, 14 * 256); |
| | | } |
| | | |
| | | // 写数据行(第2行起) |
| | | BigDecimal hundred = BigDecimal.valueOf(100); |
| | | int rowIndex = 2; |
| | | for (Map.Entry<Integer, String> entry : shopNameMap.entrySet()) { |
| | | Row row = sheet.createRow(rowIndex++); |
| | | row.createCell(0).setCellValue(entry.getValue()); |
| | | for (int i = 0; i < dateKeys.size(); i++) { |
| | | String mapKey = entry.getKey() + "_" + dateKeys.get(i); |
| | | long rev = revenueMap.getOrDefault(mapKey, 0L); |
| | | Cell cell = row.createCell(i + 1); |
| | | if (rev > 0) { |
| | | cell.setCellValue(BigDecimal.valueOf(rev).divide(hundred, 2, RoundingMode.HALF_UP).toPlainString()); |
| | | } else { |
| | | cell.setCellValue(""); |
| | | } |
| | | cell.setCellStyle(dataStyle); |
| | | } |
| | | } |
| | | |
| | | // 输出 |
| | | String fileName; |
| | | if (StringUtils.isNotBlank(query.getMonth())) { |
| | | fileName = "营收趋势导出_" + query.getMonth(); |
| | | } else { |
| | | fileName = "营收趋势导出_" + query.getYear(); |
| | | } |
| | | String encodeFileName = java.net.URLEncoder.encode(fileName, "UTF-8") + ".xlsx"; |
| | | response.setContentType("application/octet-stream"); |
| | | response.setHeader("Content-Disposition", "attachment;filename=" + encodeFileName); |
| | | response.setHeader("doumee-opera-type", "download"); |
| | | response.setHeader("doumee-download-filename", encodeFileName); |
| | | wb.write(response.getOutputStream()); |
| | | response.getOutputStream().flush(); |
| | | } catch (Exception e) { |
| | | log.error("营收趋势导出异常", e); |
| | | throw new RuntimeException("导出失败: " + e.getMessage()); |
| | | } |
| | | } |
| | | |
| | | private TrendDateRange parseTrendDateRange(TrendQueryDTO query) { |
| | | Calendar now = Calendar.getInstance(); |
| | | TrendDateRange range = new TrendDateRange(); |
| | |
| | | |
| | | @FunctionalInterface |
| | | private interface TrendVOBuilder<T> { |
| | | T build(String date); |
| | | T build(String key, String label); |
| | | } |
| | | |
| | | private <T> List<T> buildTrendList(TrendDateRange range, TrendVOBuilder<T> builder) { |
| | |
| | | Calendar end = Calendar.getInstance(); |
| | | end.setTime(range.endDate); |
| | | while (!loop.after(end)) { |
| | | result.add(builder.build(sdf.format(loop.getTime()))); |
| | | String dateStr = sdf.format(loop.getTime()); |
| | | result.add(builder.build(dateStr, dateStr)); |
| | | loop.add(Calendar.DAY_OF_MONTH, 1); |
| | | } |
| | | } else { |
| | |
| | | endCal.setTime(range.endDate); |
| | | int endMonth = endCal.get(Calendar.MONTH); // 0-based |
| | | for (int m = 0; m <= endMonth; m++) { |
| | | String label = String.format("%02d", m + 1); |
| | | result.add(builder.build(label)); |
| | | String key = String.format("%02d", m + 1); |
| | | String label = (m + 1) + "月"; |
| | | result.add(builder.build(key, label)); |
| | | } |
| | | } |
| | | return result; |