MrShi
3 小时以前 d54dc04ae25b73ee0c7c588c6523d94624f052e3
Merge branch 'master' of http://139.186.142.91:10010/r/productDev/gtzxinglijicun
已添加1个文件
已修改20个文件
527 ■■■■■ 文件已修改
server/admin/src/main/java/com/doumee/api/business/DataBoardController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/admin/src/main/java/com/doumee/api/business/DriverInfoController.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/admin/src/main/java/com/doumee/api/business/OrdersController.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/config/wx/WxPayV3Service.java 6 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/core/constants/Constants.java 12 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/DriverInfo.java 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/business/model/Orders.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/dto/DataBoardQueryDTO.java 28 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/FinanceOverviewVO.java 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/OrderTimelineVO.java 24 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/dao/vo/OrdersExportVO.java 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/DataBoardService.java 3 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/OrdersService.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/CategoryServiceImpl.java 4 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/DataBoardServiceImpl.java 183 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/DriverInfoServiceImpl.java 8 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/OrdersServiceImpl.java 135 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/PricingRuleServiceImpl.java 6 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/services/src/main/java/com/doumee/service/business/impl/ShopInfoServiceImpl.java 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/OrdersApi.java 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/web/src/main/java/com/doumee/api/web/PaymentCallback.java 8 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
server/admin/src/main/java/com/doumee/api/business/DataBoardController.java
@@ -53,6 +53,12 @@
        return ApiResponse.success(dataBoardService.revenueTrend(query));
    }
    @ApiOperation("营收趋势导出(按门店分组)")
    @PostMapping("/revenueTrendExport")
    public void revenueTrendExport(@RequestBody TrendQueryDTO query, HttpServletResponse response) {
        dataBoardService.revenueTrendExport(query, response);
    }
    @ApiOperation("门店业绩统计")
    @PostMapping("/shopPerformance")
    public ApiResponse<ShopPerformanceVO> shopPerformance(@RequestBody DataBoardQueryDTO query) {
server/admin/src/main/java/com/doumee/api/business/DriverInfoController.java
@@ -3,6 +3,7 @@
import com.doumee.api.BaseController;
import com.doumee.core.annotation.excel.ExcelExporter;
import com.doumee.core.annotation.pr.PreventRepeat;
import com.doumee.core.constants.Constants;
import com.doumee.core.model.ApiResponse;
import com.doumee.core.model.PageData;
import com.doumee.core.model.PageWrap;
@@ -85,6 +86,11 @@
    @RequiresPermissions("business:driverInfo:exportExcel")
    public void exportExcel(@RequestBody PageWrap<DriverInfo> pageWrap, HttpServletResponse response) {
        List<DriverInfo> driverInfoList = driverInfoService.findPage(pageWrap).getRecords();
        for (DriverInfo d : driverInfoList) {
            if (d.getMemberAmount() != null) {
                d.setMemberAmountStr(String.valueOf(Constants.getFormatMoney(d.getMemberAmount())));
            }
        }
        String fileName = "司机管理导出_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
        ExcelExporter.build(DriverInfo.class).export(driverInfoList, fileName, response);
    }
server/admin/src/main/java/com/doumee/api/business/OrdersController.java
@@ -96,6 +96,10 @@
            vo.setCode(o.getCode());
            vo.setRelationOrderCode(o.getRelationOrderCode());
            vo.setGoodsInfo(o.getGoodsInfo());
            vo.setTakeUser(o.getTakeUser());
            vo.setTakePhone(o.getTakePhone());
            vo.setDepositShopName(o.getDepositShopName());
            vo.setTakeShopName(o.getTakeShopName());
            vo.setTypeName(o.getType() != null ? (o.getType() == 0 ? "就地寄存" : "同城寄送") : "");
            vo.setOrderLevel(StringUtils.isNotBlank(o.getOrderLevel()) ? Constants.getDriverLevelName(Integer.valueOf(o.getOrderLevel())) : Constants.getDriverLevelName(Constants.ZERO));
            vo.setDeclaredFee(String.valueOf(Constants.getFormatMoney(o.getDeclaredFee())));
server/services/src/main/java/com/doumee/config/wx/WxPayV3Service.java
@@ -59,7 +59,7 @@
            com.wechat.pay.java.service.payments.jsapi.model.Amount amount =
                    new com.wechat.pay.java.service.payments.jsapi.model.Amount();
            amount.setTotal(totalCents.intValue());
            amount.setTotal(2);//totalCents.intValue());
            amount.setCurrency("CNY");
            request.setAmount(amount);
@@ -109,8 +109,8 @@
            request.setNotifyUrl(notifyUrl);
            AmountReq amount = new AmountReq();
            amount.setRefund(refundCents);
            amount.setTotal(totalCents);
            amount.setRefund(1L);//refundCents);
            amount.setTotal(2L);//totalCents);
            amount.setCurrency("CNY");
            request.setAmount(amount);
server/services/src/main/java/com/doumee/core/constants/Constants.java
@@ -43,8 +43,6 @@
    public static final String APPID ="APPID" ;
    public static final String SECRET ="SECRET" ;
    public static final String ACCESS_TOKEN ="ACCESS_TOKEN" ;
    public static final String SERVER_INTRODUCE ="SERVER_INTRODUCE" ;
    public static final String FEE_STANDARDS ="FEE_STANDARDS" ;
    public static final String ABOUT_US ="ABOUT_US" ;
    public static final String SERVER_PHONE ="SERVER_PHONE" ;
    public static final String ARRIVAL_PICK_UP_TIME ="ARRIVAL_PICK_UP_TIME" ;//即将到达取件时间配置(分钟)
@@ -385,10 +383,12 @@
    @Getter
    @AllArgsConstructor
    public enum OrdersAttach {
        STORAGE_ORDER("storageOrder", "寄存订单"),
        SHOP_DEPOSIT("shopDeposit", "店铺押金订单"),
        DRIVER_DEPOSIT("driverDeposit", "司机押金订单"),
        OVERDUE_FEE("overdueFee", "订单逾期费用")
        STORAGE_ORDER("storageOrder", "宁畅行轻松行-寄存订单"),
        DELIVERY_ORDER("deliveryOrder", "宁畅行轻松行-寄送订单"),
        SHOP_DEPOSIT("shopDeposit", "宁畅行轻松行-门店押金"),
        DRIVER_DEPOSIT("driverDeposit", "宁畅行轻松行-司机押金订单"),
        OVERDUE_FEE("overdueFee", "宁畅行轻松行-寄存逾期费用"),
        DELIVERY_OVERDUE_FEE("deliveryOverdueFee", "宁畅行轻松行-寄送逾期费用")
        ;
        private final String key;
server/services/src/main/java/com/doumee/dao/business/model/DriverInfo.java
@@ -182,10 +182,13 @@
    @TableField(exist = false)
    @ApiModelProperty(value = "当前余额(单位:分)", example = "10000")
    @ExcelColumn(name = "账户余额", index = 5, width = 12)
    private Long memberAmount;
    @TableField(exist = false)
    @ExcelColumn(name = "账户余额(元)", index = 5, width = 12)
    private String memberAmountStr;
    @TableField(exist = false)
    @ApiModelProperty(value = "驾驶车辆类型: æœºåŠ¨è½¦ = driving éžæœºåŠ¨è½¦ = bicycling")
    private String driverType;
server/services/src/main/java/com/doumee/dao/business/model/Orders.java
@@ -468,6 +468,10 @@
    @ApiModelProperty(value = "门店主键(同时查询存件/取件门店)")
    private Integer shopId;
    @TableField(exist = false)
    @ApiModelProperty(value = "收件人信息(模糊查询名称/手机号)")
    private String takeKeyword;
    @ApiModelProperty(value = "店铺订单序号")
    private Long autoNum;
server/services/src/main/java/com/doumee/dao/dto/DataBoardQueryDTO.java
@@ -14,21 +14,43 @@
@ApiModel("数据看板查询条件")
public class DataBoardQueryDTO implements Serializable {
    @ApiModelProperty(value = "日期段类型:0=今日,1=近七天,2=近30天,3=近半年,4=近一年", required = true)
    @ApiModelProperty(value = "日期段类型:0=今日,1=近七天,2=近30天,3=近半年,4=近一年,5=自定义", required = true)
    private Integer dateType;
    @JsonIgnore
    @JsonFormat(pattern = "yyyy-MM-dd")
    @ApiModelProperty(value = "自定义开始日期(dateType=5时必传)")
    private Date startDate;
    @JsonIgnore
    @JsonFormat(pattern = "yyyy-MM-dd")
    @ApiModelProperty(value = "自定义结束日期(dateType=5时必传)")
    private Date endDate;
    @ApiModelProperty(value = "门店主键(可选)")
    private Integer shopId;
    public void resolveDateRange() {
        if (dateType != null && dateType == 5) {
            if (startDate != null) {
                Calendar start = Calendar.getInstance();
                start.setTime(startDate);
                start.set(Calendar.HOUR_OF_DAY, 0);
                start.set(Calendar.MINUTE, 0);
                start.set(Calendar.SECOND, 0);
                start.set(Calendar.MILLISECOND, 0);
                this.startDate = start.getTime();
            }
            if (endDate != null) {
                Calendar end = Calendar.getInstance();
                end.setTime(endDate);
                end.set(Calendar.HOUR_OF_DAY, 23);
                end.set(Calendar.MINUTE, 59);
                end.set(Calendar.SECOND, 59);
                end.set(Calendar.MILLISECOND, 999);
                this.endDate = end.getTime();
            }
            return;
        }
        Calendar now = Calendar.getInstance();
        now.set(Calendar.MINUTE, 0);
        now.set(Calendar.SECOND, 0);
server/services/src/main/java/com/doumee/dao/vo/FinanceOverviewVO.java
@@ -40,11 +40,11 @@
    @ApiModelProperty(value = "退款总金额(元)")
    private BigDecimal refundAmount;
    @ExcelColumn(name = "逾期费用(元)", width = 18, index = 8)
//    @ExcelColumn(name = "逾期费用(元)", width = 18, index = 8)
    @ApiModelProperty(value = "逾期费用(元)")
    private BigDecimal overdueAmount;
    @ExcelColumn(name = "平台净营收(元)", width = 18, index = 9)
    @ExcelColumn(name = "平台净营收(元)", width = 18, index = 8)
    @ApiModelProperty(value = "平台净营收(元)")
    private BigDecimal netRevenue;
}
server/services/src/main/java/com/doumee/dao/vo/OrderTimelineVO.java
¶Ô±ÈÐÂÎļþ
@@ -0,0 +1,24 @@
package com.doumee.dao.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;
import java.util.List;
@Data
@ApiModel("订单时间轴节点")
public class OrderTimelineVO {
    @ApiModelProperty("标题内容")
    private String title;
    @ApiModelProperty("操作时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
    private Date time;
    @ApiModelProperty("附件图片URL列表")
    private List<String> images;
}
server/services/src/main/java/com/doumee/dao/vo/OrdersExportVO.java
@@ -17,54 +17,66 @@
    @ExcelColumn(name = "物品信息", index = 2)
    private String goodsInfo;
    @ExcelColumn(name = "类型", index = 3)
    @ExcelColumn(name = "收件人", index = 3)
    private String takeUser;
    @ExcelColumn(name = "收件人电话", index = 4)
    private String takePhone;
    @ExcelColumn(name = "寄件门店", index = 5)
    private String depositShopName;
    @ExcelColumn(name = "取件门店", index = 6)
    private String takeShopName;
    @ExcelColumn(name = "类型", index = 7)
    private String typeName;
    @ExcelColumn(name = "订单级别", index = 4)
    @ExcelColumn(name = "订单级别", index = 8)
    private String orderLevel;
    @ExcelColumn(name = "物品保费(元)", index = 5)
    @ExcelColumn(name = "物品保费(元)", index = 9)
    private String declaredFee;
    @ExcelColumn(name = "基础服务费(元)", index = 6)
    @ExcelColumn(name = "基础服务费(元)", index = 10)
    private String basicAmount;
    @ExcelColumn(name = "订单总价(元)", index = 7)
    @ExcelColumn(name = "订单总价(元)", index = 11)
    private String totalAmount;
    @ExcelColumn(name = "实付现金(元)", index = 8)
    @ExcelColumn(name = "实付现金(元)", index = 12)
    private String payAmount;
    @ExcelColumn(name = "加急费(元)", index = 9)
    @ExcelColumn(name = "加急费(元)", index = 13)
    private String urgentAmount;
    @ExcelColumn(name = "退款金额(元)", index = 10)
    @ExcelColumn(name = "退款金额(元)", index = 14)
    private String refundAmount;
    @ExcelColumn(name = "超时金额(元)", index = 11)
    @ExcelColumn(name = "超时金额(元)", index = 15)
    private String overdueAmount;
    @ExcelColumn(name = "异常金额(元)", index = 12)
    @ExcelColumn(name = "异常金额(元)", index = 16)
    private String exceptionAmount;
    @ExcelColumn(name = "优惠券折扣(元)", index = 13)
    @ExcelColumn(name = "优惠券折扣(元)", index = 17)
    private String deductionAmount;
    @ExcelColumn(name = "司机补偿费用(元)", index = 14)
    @ExcelColumn(name = "司机补偿费用(元)", index = 18)
    private String exceptionFee;
    @ExcelColumn(name = "门店保管补贴(元)", index = 15)
    @ExcelColumn(name = "门店保管补贴(元)", index = 19)
    private String shopCompensationAmount;
    @ExcelColumn(name = "订单状态", index = 16)
    @ExcelColumn(name = "订单状态", index = 20)
    private String statusDesc;
    @ExcelColumn(name = "结算状态", index = 17)
    @ExcelColumn(name = "结算状态", index = 21)
    private String settlementDesc;
    @ExcelColumn(name = "支付时间", index = 18, dateFormat = "yyyy-MM-dd HH:mm:ss", width = 16)
    @ExcelColumn(name = "支付时间", index = 22, dateFormat = "yyyy-MM-dd HH:mm:ss", width = 16)
    private Date payTime;
    @ExcelColumn(name = "创建时间", index = 19, dateFormat = "yyyy-MM-dd HH:mm:ss", width = 16)
    @ExcelColumn(name = "创建时间", index = 23, dateFormat = "yyyy-MM-dd HH:mm:ss", width = 16)
    private Date createTime;
}
server/services/src/main/java/com/doumee/service/business/DataBoardService.java
@@ -5,6 +5,7 @@
import com.doumee.dao.dto.TrendQueryDTO;
import com.doumee.dao.vo.*;
import javax.servlet.http.HttpServletResponse;
import java.util.List;
public interface DataBoardService {
@@ -27,6 +28,8 @@
    List<DriverTopVO> driverTop(TrendQueryDTO query);
    void revenueTrendExport(TrendQueryDTO query, HttpServletResponse response);
    /**
     * æ ¹æ®è®¢å•ID列表构建行李类型分布
     */
server/services/src/main/java/com/doumee/service/business/OrdersService.java
@@ -467,4 +467,6 @@
    Boolean checkOperationRadius(Integer orderId, Integer userId, Integer userType, Double lng, Double lat);
    ManualRefundDetailVO getManualRefundDetail(Integer orderId);
    List<OrderTimelineVO> getOrderTimeline(Integer orderId);
}
server/services/src/main/java/com/doumee/service/business/impl/CategoryServiceImpl.java
@@ -172,7 +172,9 @@
        pageWrap.getModel().setDeleted(Constants.ZERO);
        queryWrapper.selectAll(Category.class)
                .selectAs(SystemUser::getUsername, Category::getUpdateUserName)
                .leftJoin(SystemUser.class,SystemUser::getId,Category::getUpdateUser);
                .select("c2.name", Category::getRelationName)
                .leftJoin(SystemUser.class,SystemUser::getId,Category::getUpdateUser)
                .leftJoin("category c2 on c2.id = t.relation_id");
        if (pageWrap.getModel().getId() != null) {
            queryWrapper.eq(Category::getId, pageWrap.getModel().getId());
        }
server/services/src/main/java/com/doumee/service/business/impl/DataBoardServiceImpl.java
@@ -13,7 +13,11 @@
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;
@@ -430,6 +434,7 @@
            vo.setNetRevenue(BigDecimal.valueOf(netRev).divide(hundred, 2, RoundingMode.HALF_UP));
            result.add(vo);
        }
        Collections.reverse(result);
        return result;
    }
@@ -642,6 +647,184 @@
        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();
server/services/src/main/java/com/doumee/service/business/impl/DriverInfoServiceImpl.java
@@ -293,6 +293,14 @@
        if (pageWrap.getModel().getAuditStatus() != null) {
            queryWrapper.eq(DriverInfo::getAuditStatus, pageWrap.getModel().getAuditStatus());
        }
        // æ€§åˆ«ï¼ˆæ ¹æ®èº«ä»½è¯ç¬¬17位判断)
        if (pageWrap.getModel().getGender() != null) {
            if (pageWrap.getModel().getGender() == 1) {
                queryWrapper.apply("CAST(SUBSTRING(t.IDCARD, 17, 1) AS UNSIGNED) % 2 = 1");
            } else if (pageWrap.getModel().getGender() == 2) {
                queryWrapper.apply("CAST(SUBSTRING(t.IDCARD, 17, 1) AS UNSIGNED) % 2 = 0");
            }
        }
        // åˆ›å»ºæ—¥æœŸèŒƒå›´
        if (pageWrap.getModel().getCreateTimeStart() != null) {
            queryWrapper.ge(DriverInfo::getCreateTime, Utils.Date.getStart(pageWrap.getModel().getCreateTimeStart()));
server/services/src/main/java/com/doumee/service/business/impl/OrdersServiceImpl.java
@@ -237,6 +237,7 @@
                .selectAs(Category::getDetail, Orders::getOrderLevel)
                .select("s1.name", Orders::getDepositShopName)
                .select("o2.code", Orders::getRelationOrderCode)
                .select("s2.name", Orders::getTakeShopName)
                .leftJoin(Category.class, Category::getId, Orders::getGoodLevel)
                .leftJoin(DriverInfo.class, DriverInfo::getId, Orders::getAcceptDriver)
                .leftJoin("shop_info s1 on s1.id = t.DEPOSIT_SHOP_ID")
@@ -248,6 +249,7 @@
        queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getCode()), Orders::getCode, pageWrap.getModel().getCode());
        queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getDepositShopName()), "s1.name", pageWrap.getModel().getDepositShopName());
        queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getTakeShopName()),  "s2.name",  pageWrap.getModel().getTakeShopName());
        queryWrapper.eq(StringUtils.isNotBlank(pageWrap.getModel().getTakeShopName()),  Orders::getType,Constants.ONE);
        queryWrapper.like(StringUtils.isNotBlank(pageWrap.getModel().getGoodsInfo()), Orders::getGoodsInfo, pageWrap.getModel().getGoodsInfo());
        queryWrapper.ge(pageWrap.getModel().getCreateStartTime() != null, Orders::getCreateTime, pageWrap.getModel().getCreateStartTime());
        queryWrapper.le(pageWrap.getModel().getCreateEndTime() != null, Orders::getCreateTime, Utils.Date.getEnd(pageWrap.getModel().getCreateEndTime()));
@@ -259,6 +261,8 @@
                .or().like(DriverInfo::getTelephone, pageWrap.getModel().getDriverKeyword()));
        queryWrapper.eq(pageWrap.getModel().getSettlementStatus() != null, Orders::getSettlementStatus, pageWrap.getModel().getSettlementStatus());
        queryWrapper.eq(pageWrap.getModel().getAcceptDriver() != null, Orders::getAcceptDriver, pageWrap.getModel().getAcceptDriver());
        queryWrapper.and(pageWrap.getModel().getTakeKeyword() != null, i -> i.like(Orders::getTakeUser, pageWrap.getModel().getTakeKeyword())
                .or().like(Orders::getTakePhone, pageWrap.getModel().getTakeKeyword()));
        queryWrapper.and(pageWrap.getModel().getShopId() != null, i -> i.eq(Orders::getDepositShopId, pageWrap.getModel().getShopId())
                .or().eq(Orders::getTakeShopId, pageWrap.getModel().getShopId()));
        queryWrapper.orderByDesc(Orders::getId);
@@ -1049,7 +1053,8 @@
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户信息异常,无法发起支付");
        }
        PayResponse payResponse = wxPayV3(orders.getOutTradeNo(), orders.getTotalAmount(), orders.getId(),
                member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER);
                member.getOpenid(), Constants.equalsInteger(orders.getType(), Constants.ONE)
                        ? Constants.OrdersAttach.DELIVERY_ORDER : Constants.OrdersAttach.STORAGE_ORDER);
        payResponse.setLockKey(lockKey);
        return payResponse;
    }
@@ -1086,7 +1091,8 @@
            throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "用户信息异常,无法发起支付");
        }
        return wxPayV3(orders.getOutTradeNo(), orders.getTotalAmount(), orders.getId(),
                member.getOpenid(), Constants.OrdersAttach.STORAGE_ORDER);
                member.getOpenid(), Constants.equalsInteger(orders.getType(), Constants.ONE)
                        ? Constants.OrdersAttach.DELIVERY_ORDER : Constants.OrdersAttach.STORAGE_ORDER);
    }
    /**
@@ -1480,6 +1486,13 @@
        }
    }
    private void clearVerifyCodes(Integer orderId) {
        ordersMapper.update(null, new UpdateWrapper<Orders>().lambda()
                .eq(Orders::getId, orderId)
                .set(Orders::getMemberVerifyCode, null)
                .set(Orders::getDriverVerifyCode, null));
    }
    private String getOrdersPrefix() {
        try {
            return systemDictDataBiz.queryByCode(Constants.OSS, Constants.RESOURCE_PATH).getCode()
@@ -1576,9 +1589,9 @@
        }
        // è®¡ç®—薪酬(分)
        long driverFee = new BigDecimal(totalAmount).multiply(driverRata).longValue();
        long depositShopFee = new BigDecimal(totalAmount).multiply(depositShopRata).longValue();
        long takeShopFee = new BigDecimal(totalAmount).multiply(takeShopRata).longValue();
        long driverFee = new BigDecimal(totalAmount).multiply(driverRata).setScale(0, RoundingMode.HALF_UP).longValue();
        long depositShopFee = new BigDecimal(totalAmount).multiply(depositShopRata).setScale(0, RoundingMode.HALF_UP).longValue();
        long takeShopFee = new BigDecimal(totalAmount).multiply(takeShopRata).setScale(0, RoundingMode.HALF_UP).longValue();
        orders.setDriverFee(driverFee);
        orders.setDepositShopFee(depositShopFee);
@@ -2834,7 +2847,8 @@
        // 5. å”¤èµ·å¾®ä¿¡æ”¯ä»˜V3
        return wxPayV3(otherOrders.getOutTradeNo(), otherOrders.getPayAccount(), otherOrders.getId(),
                member.getOpenid(), Constants.OrdersAttach.OVERDUE_FEE);
                member.getOpenid(), Constants.equalsInteger(order.getType(), Constants.ONE)
                        ? Constants.OrdersAttach.DELIVERY_OVERDUE_FEE : Constants.OrdersAttach.OVERDUE_FEE);
    }
    @Override
@@ -3385,6 +3399,7 @@
                    originalOrder.setFinishTime(now);
                    originalOrder.setUpdateTime(now);
                    ordersMapper.updateById(originalOrder);
                    clearVerifyCodes(originalOrder.getId());
                    // è§¦å‘原订单收益计算
                    calculateAndSaveOrderFees(originalOrder.getId());
                    generateRevenueRecords(originalOrder.getId());
@@ -3428,7 +3443,7 @@
            order.setFinishTime(now);
            order.setInvoiceStatus(Constants.ONE);
            ordersMapper.updateById(order);
            // å°±åœ°å¯„å­˜(type=0)取件时图片不必填,其他类型取件必填
            clearVerifyCodes(order.getId());
            if (!Constants.equalsInteger(order.getType(), Constants.ZERO)) {
                if (images == null || images.isEmpty()) {
                    throw new BusinessException(ResponseStatus.BAD_REQUEST.getCode(), "请上传取件图片");
@@ -3597,6 +3612,7 @@
            order.setInvoiceStatus(Constants.ONE);
            order.setConfirmArriveTime(now);
            ordersMapper.updateById(order);
            clearVerifyCodes(order.getId());
            // è®¢å•完成,释放核销码
            releaseVerifyCode(verifyCode);
            // ä¿å­˜å‡ºåº“图片(obj_type=13 é—¨åº—出库图片,最多3张)
@@ -3675,6 +3691,7 @@
        order.setFinishTime(now);
        order.setUpdateTime(now);
        ordersMapper.updateById(order);
        clearVerifyCodes(order.getId());
        // 8. é‡Šæ”¾æ ¸é”€ç 
        if (StringUtils.isNotBlank(order.getMemberVerifyCode())) {
@@ -3773,6 +3790,7 @@
        order.setFinishTime(now);
        order.setUpdateTime(now);
        ordersMapper.updateById(order);
        clearVerifyCodes(order.getId());
        // 7. ç”Ÿæˆæ”¶ç›Šè®°å½•
        calculateAndSaveOrderFees(orderId);
@@ -4895,6 +4913,7 @@
                order.setFinishTime(now);
                order.setUpdateTime(now);
                ordersMapper.updateById(order);
                clearVerifyCodes(order.getId());
                // é‡Šæ”¾æ ¸é”€ç 
                if (StringUtils.isNotBlank(order.getMemberVerifyCode())) {
@@ -5476,4 +5495,106 @@
        return vo;
    }
    @Override
    public List<OrderTimelineVO> getOrderTimeline(Integer orderId) {
        Orders order = ordersMapper.selectById(orderId);
        if (order == null || Constants.equalsInteger(order.getDeleted(), Constants.ONE)) {
            throw new BusinessException(ResponseStatus.DATA_EMPTY);
        }
        List<OrderTimelineVO> timeline = new ArrayList<>();
        String imgPrefix = getOrdersPrefix();
        // é—¨åº—/司机名称
        String depositShopName = "";
        if (order.getDepositShopId() != null) {
            ShopInfo depositShop = shopInfoMapper.selectById(order.getDepositShopId());
            if (depositShop != null) {
                depositShopName = depositShop.getName();
            }
        }
        String takeShopName = "";
        if (order.getTakeShopId() != null) {
            ShopInfo takeShop = shopInfoMapper.selectById(order.getTakeShopId());
            if (takeShop != null) {
                takeShopName = takeShop.getName();
            }
        }
        String driverName = "";
        if (order.getAcceptDriver() != null) {
            DriverInfo driver = driverInfoMapper.selectById(order.getAcceptDriver());
            if (driver != null) {
                driverName = driver.getName();
            }
        }
        boolean isLocal = Constants.equalsInteger(order.getType(), Constants.ZERO);
        boolean hasTakeShop = order.getTakeShopId() != null;
        boolean isException = Constants.equalsInteger(order.getExceptionStatus(), Constants.ONE);
        int status = order.getStatus() != null ? order.getStatus() : 0;
        // 1. é—¨åº—寄存 (status >= 2)
        if (status >= Constants.OrderStatus.deposited.getKey() && order.getDepositTime() != null) {
            OrderTimelineVO node = new OrderTimelineVO();
            node.setTitle("门店【" + depositShopName + "】确认收件");
            node.setTime(order.getDepositTime());
            node.setImages(getFileUrls(orderId, Constants.FileType.ORDER_DEPOSIT.getKey(), imgPrefix));
            timeline.add(node);
        }
        if (!isLocal) {
            // 2. å¸æœºå–ä»¶ (status >= 4)
            if (status >= Constants.OrderStatus.delivering.getKey() && order.getDriverTakeTime() != null) {
                OrderTimelineVO node = new OrderTimelineVO();
                node.setTitle("司机【" + driverName + "】确认取件");
                node.setTime(order.getDriverTakeTime());
                node.setImages(getFileUrls(orderId, Constants.FileType.DRIVER_TAKE.getKey(), imgPrefix));
                timeline.add(node);
            }
            if (isException) {
                // å¼‚常:关联查询异常订单
                Orders exceptionOrder = ordersMapper.selectOne(new QueryWrapper<Orders>().lambda()
                        .eq(Orders::getRelationOrderId, orderId)
                        .eq(Orders::getExceptionStatus, Constants.ONE)
                        .last("limit 1"));
                if (exceptionOrder != null) {
                    OrderTimelineVO node = new OrderTimelineVO();
                    node.setTitle("行李已异常转存,新订单号【" + exceptionOrder.getCode() + "】");
                    node.setTime(exceptionOrder.getCreateTime());
                    timeline.add(node);
                }
            } else {
                // 3. æ­£å¸¸ï¼šå¸æœºé€è¾¾ (status >= 5)
                if(StringUtils.isBlank(takeShopName)){
                    if (status >= Constants.OrderStatus.arrived.getKey() && order.getArriveTime() != null) {
                        OrderTimelineVO node = new OrderTimelineVO();
                        node.setTitle("司机【" + driverName + "】确认送达");
                        node.setTime(order.getArriveTime());
                        node.setImages(getFileUrls(orderId, Constants.FileType.DRIVER_DONE.getKey(), imgPrefix));
                        timeline.add(node);
                    }
                }
                // 4. æœ‰é—¨åº—:门店收件 (status >= 5)
                if (hasTakeShop && status >= Constants.OrderStatus.arrived.getKey() && order.getArriveTime() != null) {
                    OrderTimelineVO node = new OrderTimelineVO();
                    node.setTitle("门店【" + takeShopName + "】确认收件");
                    node.setTime(order.getArriveTime());
                    node.setImages(getFileUrls(orderId, Constants.FileType.ORDER_TAKE.getKey(), imgPrefix));
                    timeline.add(node);
                }
            }
        }
        // æœ€åŽï¼šè®¢å•完成 (status = 7)
        if (status == Constants.OrderStatus.finished.getKey() && order.getFinishTime() != null) {
            OrderTimelineVO node = new OrderTimelineVO();
            node.setTitle("订单已完成");
            node.setTime(order.getFinishTime());
            timeline.add(node);
        }
        timeline.sort((a, b) -> b.getTime() != null && a.getTime() != null ? b.getTime().compareTo(a.getTime()) : 0);
        return timeline;
    }
}
server/services/src/main/java/com/doumee/service/business/impl/PricingRuleServiceImpl.java
@@ -228,7 +228,8 @@
        Category categoryQuery = new Category();
        categoryQuery.setType(Constants.FOUR);
        categoryQuery.setDeleted(Constants.ZERO);
        List<Category> allCategories = categoryMapper.selectList(new QueryWrapper<>(categoryQuery));
        List<Category> allCategories = categoryMapper.selectList(new QueryWrapper<>(categoryQuery)
                .lambda().orderByAsc(Category::getSortnum));
        Map<Integer, String> categoryNameMap = allCategories.stream()
                .collect(Collectors.toMap(Category::getId, Category::getName));
@@ -309,7 +310,8 @@
        Category categoryQuery = new Category();
        categoryQuery.setType(Constants.FOUR);
        categoryQuery.setDeleted(Constants.ZERO);
        List<Category> allCategories = categoryMapper.selectList(new QueryWrapper<>(categoryQuery));
        List<Category> allCategories = categoryMapper.selectList(new QueryWrapper<>(categoryQuery)
                .lambda().orderByAsc(Category::getSortnum));
        Map<Integer, String> categoryNameMap = allCategories.stream()
                .collect(Collectors.toMap(Category::getId, Category::getName));
server/services/src/main/java/com/doumee/service/business/impl/ShopInfoServiceImpl.java
@@ -1358,7 +1358,7 @@
        List<String> tagNames = new ArrayList<>();
        for (String tagId : locationTagIds.split(",")) {
            Category tag = categoryMapper.selectById(Integer.valueOf(tagId.trim()));
            if (tag != null) {
            if (tag != null && tag.getDeleted() == Constants.ZERO && tag.getStatus() == Constants.ZERO ) {
                tagNames.add(tag.getName());
            }
        }
server/web/src/main/java/com/doumee/api/web/OrdersApi.java
@@ -2,6 +2,7 @@
import com.doumee.core.annotation.LoginRequired;
import com.doumee.core.annotation.LoginShopRequired;
import com.doumee.core.annotation.LoginDriverRequired;
import com.doumee.core.annotation.trace.Trace;
import com.doumee.core.constants.Constants;
import com.doumee.core.model.ApiResponse;
@@ -216,4 +217,34 @@
        return ApiResponse.success("确认收货成功");
    }
    @LoginRequired
    @ApiOperation(value = "会员查询订单时间轴")
    @GetMapping("/timeline/{orderId}")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true)
    })
    public ApiResponse timelineForMember(@PathVariable Integer orderId) {
        return ApiResponse.success(ordersService.getOrderTimeline(orderId));
    }
    @LoginShopRequired
    @ApiOperation(value = "门店查询订单时间轴")
    @GetMapping("/shop/timeline/{orderId}")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true)
    })
    public ApiResponse timelineForShop(@PathVariable Integer orderId) {
        return ApiResponse.success(ordersService.getOrderTimeline(orderId));
    }
    @LoginDriverRequired
    @ApiOperation(value = "司机查询订单时间轴")
    @GetMapping("/driver/timeline/{orderId}")
    @ApiImplicitParams({
            @ApiImplicitParam(paramType = "header", dataType = "String", name = "token", value = "用户token值", required = true)
    })
    public ApiResponse timelineForDriver(@PathVariable Integer orderId) {
        return ApiResponse.success(ordersService.getOrderTimeline(orderId));
    }
}
server/web/src/main/java/com/doumee/api/web/PaymentCallback.java
@@ -81,7 +81,8 @@
            if (Constants.SUCCESS.equals(result.getReturnCode())) {
                switch (result.getAttach()) {
                    case "storageOrder": {
                    case "storageOrder":
                    case "deliveryOrder": {
                        ordersService.handleStorageOrderPayNotify(outTradeNo, paymentNo);
                        break;
                    }
@@ -89,7 +90,8 @@
                        ordersService.handleShopDepositPayNotify(outTradeNo, paymentNo);
                        break;
                    }
                    case "overdueFee": {
                    case "overdueFee":
                    case "deliveryOverdueFee": {
                        ordersService.handleOverdueFeePayNotify(outTradeNo, paymentNo);
                        break;
                    }
@@ -130,12 +132,14 @@
                if (StringUtils.isNotBlank(attach)) {
                    switch (attach) {
                        case "storageOrder":
                        case "deliveryOrder":
                            ordersService.handleStorageOrderPayNotify(outTradeNo, paymentNo);
                            break;
                        case "shopDeposit":
                            ordersService.handleShopDepositPayNotify(outTradeNo, paymentNo);
                            break;
                        case "overdueFee":
                        case "deliveryOverdueFee":
                            ordersService.handleOverdueFeePayNotify(outTradeNo, paymentNo);
                            break;
                    }