shinnosuke
2026-07-09 6dd6e95781c85398e366c4d468a6de83babfa206
feat(menu): 修复工作台权限菜单并完善发票开票功能
1个文件已添加
10个文件已修改
385 ■■■■■ 已修改文件
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserInfoDto.java 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserController.java 12 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TInvoiceInformationController.java 17 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-chargingPile/src/main/resources/mapper/chargingPile/SiteMapper.xml 4 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TOrderInvoiceController.java 25 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/OrderInvoiceList.java 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java 55 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TOrderInvoiceServiceImpl.java 193 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TShoppingOrderMapper.xml 2 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
sql/20260703_workbench_menu.sql 69 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
ruoyi-api/ruoyi-api-account/src/main/java/com/ruoyi/account/api/dto/AppUserInfoDto.java
@@ -31,6 +31,10 @@
    private Long vipCouponNum;
    @ApiModelProperty("是否第一次添加车")
    private Integer firstAdd;
    @ApiModelProperty("是否加盟商用户:1是0否")
    private Integer isPartnerUser;
    @ApiModelProperty("加盟商ID")
    private Integer partnerId;
    @ApiModelProperty("当前绑定的车辆")
    private List<TAppUserCar> userCars;
}
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TAppUserController.java
@@ -151,6 +151,8 @@
    private TInviteUserService inviteUserService;
    @Resource
    private GiveVipUtil giveVipUtil;
    @Resource
    private TAppUserWhitelistService appUserWhitelistService;
    @Autowired
    private RedissonClient redissonClient;
@@ -919,6 +921,16 @@
        appUserInfoDto.setFirstAdd(byId.getFirstAdd()!=null?byId.getFirstAdd():0);
        appUserInfoDto.setPoints(byId.getPoints()==null?0:byId.getPoints());
        appUserInfoDto.setBalance(byId.getBalance()==null?BigDecimal.ZERO:byId.getBalance());
        TAppUserWhitelist partnerWhitelist = appUserWhitelistService.lambdaQuery()
                .eq(TAppUserWhitelist::getAppUserId, userId)
                .eq(TAppUserWhitelist::getScopeType, 3)
                .eq(TAppUserWhitelist::getStatus, 1)
                .eq(TAppUserWhitelist::getDelFlag, false)
                .isNotNull(TAppUserWhitelist::getPartnerId)
                .last("limit 1")
                .one();
        appUserInfoDto.setIsPartnerUser(partnerWhitelist == null ? 0 : 1);
        appUserInfoDto.setPartnerId(partnerWhitelist == null ? null : partnerWhitelist.getPartnerId());
        return R.ok(appUserInfoDto);
    }
    
ruoyi-service/ruoyi-account/src/main/java/com/ruoyi/account/controller/TInvoiceInformationController.java
@@ -115,5 +115,22 @@
        return AjaxResult.success();
    }
    
    @Log(title = "【我的】删除开票抬头数据", businessType = BusinessType.DELETE,operatorType = OperatorType.MOBILE)
    @ResponseBody
    @DeleteMapping(value = "/deleteInvoiceInformation/{id}")
    @ApiOperation(value = "删除开票抬头数据", tags = {"小程序-充电发票"})
    public AjaxResult deleteInvoiceInformation(@PathVariable Long id){
        Long userId = tokenService.getLoginUserApplet().getUserId();
        TInvoiceInformation information = invoiceInformationService.getById(id);
        if(null == information || null != information.getDelFlag() && information.getDelFlag()){
            return AjaxResult.error("开票抬头不存在");
        }
        if(!information.getAppUserId().equals(userId)){
            return AjaxResult.error("权限不足");
        }
        invoiceInformationService.removeById(id);
        return AjaxResult.success();
    }
}
ruoyi-service/ruoyi-chargingPile/src/main/resources/mapper/chargingPile/SiteMapper.xml
@@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?>
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.chargingPile.mapper.SiteMapper">
    
@@ -213,7 +213,7 @@
        SELECT id, partner_id, venue_partner_id, code, `name`, site_type, business_category, status, construction_site, img_url, province, province_code,
               city, city_code, districts, districts_code, lon, lat, address, country_code, phone, service_phone, guide, start_service_time,
               end_service_time, service_description, vehicle_description, parking_space, rate_description, space_charge_explain,
               accounting_strategy_id, establishment_time, sort, remark, create_time, del_flag, mark,
               accounting_strategy_id, establishment_time, sort, remark, create_time, del_flag, mark, supportOrder,
            ROUND(
        6378.138 * 2 * ASIN(
        SQRT(
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/controller/TOrderInvoiceController.java
@@ -242,6 +242,28 @@
        List<OrderInvoiceList> myOrderInvoiceList = orderInvoiceService.getMyOrderInvoiceList(query);
        return AjaxResult.success(myOrderInvoiceList);
    }
    @ResponseBody
    @PostMapping("/cancelMyOrderInvoice/{id}")
    @ApiOperation(value = "取消我的开票申请", tags = {"小程序-充电发票"})
    public AjaxResult<String> cancelMyOrderInvoice(@PathVariable Long id){
        TOrderInvoice orderInvoice = orderInvoiceService.getById(id);
        if (orderInvoice == null) {
            return AjaxResult.error("开票申请不存在");
        }
        Long userId = tokenService.getLoginUserApplet().getUserId();
        if(!orderInvoice.getAppUserId().equals(userId)){
            return AjaxResult.error("权限不足");
        }
        if (!Integer.valueOf(1).equals(orderInvoice.getStatus())) {
            return AjaxResult.error("当前开票申请不能取消");
        }
        orderInvoice.setStatus(4);
        orderInvoice.setCancelTime(LocalDateTime.now());
        orderInvoiceService.updateById(orderInvoice);
        return AjaxResult.success();
    }
    
    
    
@@ -250,6 +272,9 @@
    @ApiOperation(value = "获取开票记录详情", tags = {"小程序-充电发票"})
    public AjaxResult<MyOrderInvoiceInfo> getMyOrderInvoiceInfo(@PathVariable String id){
        TOrderInvoice orderInvoice = orderInvoiceService.getById(id);
        if (orderInvoice == null) {
            return AjaxResult.error("开票申请不存在");
        }
        Long userId = tokenService.getLoginUserApplet().getUserId();
        if(!orderInvoice.getAppUserId().equals(userId)){
            return AjaxResult.error("权限不足");
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/dto/OrderInvoiceList.java
@@ -21,6 +21,8 @@
    private BigDecimal totalAmount;
    @ApiModelProperty("抬头名称")
    private String name;
    @ApiModelProperty("状态(1=已申请,2=开票中,3=开票成功,4=已取消)")
    private Integer status;
    @ApiModelProperty("申请时间")
    private String createTime;
}
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TChargingOrderServiceImpl.java
@@ -599,7 +599,12 @@
        }
        return new BigDecimal[]{electricDiscount, serviceDiscount};
    }
    private TVip getVipSnapshot(TAppUserVipDetail vipDetail, Integer vipId) {
        if (vipDetail != null && StringUtils.hasLength(vipDetail.getVipJson())) {
            return JSON.parseObject(vipDetail.getVipJson(), TVip.class);
        }
        return vipId == null ? null : vipClient.getInfo1(vipId).getData();
    }
    @Override
    public AjaxResult paymentChargingOrder(AddChargingOrder addChargingOrder) {
        Long userId = tokenService.getLoginUserApplet().getUserId();
@@ -778,8 +783,12 @@
        
        //会员折扣支持直营网点和加盟商站点
        if (!whitelistMatched && !isBalancePayMode(payMethod) && null != appUser.getVipId()) {
            TVip vip = vipClient.getInfo1(appUser.getVipId()).getData();
            if (null != vip) {
            GetAppUserVipDetail getAppUserVipDetail = new GetAppUserVipDetail();
            getAppUserVipDetail.setAppUserId(appUser.getId());
            getAppUserVipDetail.setVipId(appUser.getVipId());
            TAppUserVipDetail vipDetail = appUserVipDetailClient.getAppUserVipDetail(getAppUserVipDetail).getData();
            TVip vip = getVipSnapshot(vipDetail, appUser.getVipId());
            if (null != vip && null != vipDetail) {
                BigDecimal discount = null;
                if (Integer.valueOf(1).equals(vip.getType())) {
                    //普通会员折扣使用积分策略上的折扣,且有最高优惠金额
@@ -788,17 +797,9 @@
                    //内部会员使用会员信息设置的折扣,没有最高优惠金额
                    discount = vip.getDiscount();
                }
                if (null != discount) {
                    GetAppUserVipDetail getAppUserVipDetail = new GetAppUserVipDetail();
                    getAppUserVipDetail.setAppUserId(appUser.getId());
                    getAppUserVipDetail.setVipId(appUser.getVipId());
                    TAppUserVipDetail vipDetail = appUserVipDetailClient.getAppUserVipDetail(getAppUserVipDetail).getData();
                    if (null != vipDetail) {
                        Integer chargeNum = vipDetail.getChargeNum();
                        if (null != chargeNum && 0 < chargeNum) {
                            chargingOrder.setVipDiscount(discount);
                        }
                    }
                Integer chargeNum = vipDetail.getChargeNum();
                if (null != discount && null != chargeNum && 0 < chargeNum) {
                    chargingOrder.setVipDiscount(discount);
                }
            }
        }
@@ -986,10 +987,10 @@
                
                if (discountAmount.compareTo(BigDecimal.ZERO) >= 0) {
                    //计算会员最大优惠金额
                    TVip vip = vipClient.getInfo1(appUser.getVipId()).getData();
                    BigDecimal maximumDeduction = vip.getMaximumDeduction();
                    TVip vip = getVipSnapshot(data, appUser.getVipId());
                    BigDecimal maximumDeduction = vip == null ? null : vip.getMaximumDeduction();
                    //普通会员有最高优惠限制
                    if (vip.getType() == 1 && discountAmount.compareTo(maximumDeduction) > 0) {
                    if (vip != null && Integer.valueOf(1).equals(vip.getType()) && maximumDeduction != null && discountAmount.compareTo(maximumDeduction) > 0) {
                        discountAmount = maximumDeduction;
                    }
                }
@@ -1788,10 +1789,14 @@
            
            TAppUser appUser1 = appUserClient.getUserById(appUser.getInviteUserId()).getData();
            if (null != appUser1.getVipId()) {
                TVip vip = vipClient.getInfo1(appUser1.getVipId()).getData();
                Integer doubleIntegration = vip.getDoubleIntegration();
                GetAppUserVipDetail getAppUserVipDetail = new GetAppUserVipDetail();
                getAppUserVipDetail.setAppUserId(appUser1.getId());
                getAppUserVipDetail.setVipId(appUser1.getVipId());
                TAppUserVipDetail vipDetail = appUserVipDetailClient.getAppUserVipDetail(getAppUserVipDetail).getData();
                TVip vip = getVipSnapshot(vipDetail, appUser1.getVipId());
                Integer doubleIntegration = vip == null ? null : vip.getDoubleIntegration();
                //双倍积分
                if (1 == doubleIntegration) {
                if (Integer.valueOf(1).equals(doubleIntegration)) {
                    num1 *= 2;
                }
                
@@ -3485,10 +3490,14 @@
                        Integer num1 = JSON.parseObject(integralRule.getChargeCredit()).getInteger("num1");
                        Integer integral = chargingOrder.getServiceCharge().intValue() * num1;
                        if (null != appUser.getVipId()) {
                            TVip vip = vipClient.getInfo1(appUser.getVipId()).getData();
                            Integer doubleIntegration = vip.getDoubleIntegration();
                            GetAppUserVipDetail getAppUserVipDetail = new GetAppUserVipDetail();
                            getAppUserVipDetail.setAppUserId(appUser.getId());
                            getAppUserVipDetail.setVipId(appUser.getVipId());
                            TAppUserVipDetail vipDetail = appUserVipDetailClient.getAppUserVipDetail(getAppUserVipDetail).getData();
                            TVip vip = getVipSnapshot(vipDetail, appUser.getVipId());
                            Integer doubleIntegration = vip == null ? null : vip.getDoubleIntegration();
                            //双倍积分
                            if (1 == doubleIntegration) {
                            if (Integer.valueOf(1).equals(doubleIntegration)) {
                                integral *= 2;
                            }
                        }
ruoyi-service/ruoyi-order/src/main/java/com/ruoyi/order/service/impl/TOrderInvoiceServiceImpl.java
@@ -32,9 +32,11 @@
import com.ruoyi.other.api.feignClient.InvoiceTypeClient;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.interceptor.TransactionAspectSupport;
import org.springframework.util.CollectionUtils;
import javax.annotation.Resource;
@@ -102,76 +104,144 @@
     * @return
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public AjaxResult addOrderInvoice(AddOrderInvoice addOrderInvoice) {
        Long userId = tokenService.getLoginUserApplet().getUserId();
        RLock lock = redissonClient.getLock("ADD_ORDER_INVOICE:" + userId);
        boolean locked = false;
        try {
            boolean tryLock = lock.tryLock(5, 5, TimeUnit.SECONDS);
            if(tryLock){
                String orders = addOrderInvoice.getOrders();
                if(StringUtils.isEmpty(orders)){
                    return AjaxResult.error("请选择有效的订单");
            locked = lock.tryLock(5, 5, TimeUnit.SECONDS);
            if (!locked) {
                return AjaxResult.error("开票申请正在处理中,请勿重复提交");
            }
            String orders = addOrderInvoice.getOrders();
            if (StringUtils.isEmpty(orders)) {
                return AjaxResult.error("请选择有效的订单");
            }
            JSONArray parse = JSONArray.parse(orders);
            if (parse == null || parse.isEmpty()) {
                return AjaxResult.error("请选择有效的订单");
            }
            List<Long> orderIds = new ArrayList<>();
            Map<Long, BigDecimal> amountMap = new HashMap<>();
            for (int i = 0; i < parse.size(); i++) {
                JSONObject jsonObject = parse.getJSONObject(i);
                Long id = jsonObject.getLong("id");
                BigDecimal amount = jsonObject.getBigDecimal("amount");
                if (id == null || amount == null || amount.compareTo(BigDecimal.ZERO) <= 0) {
                    return AjaxResult.error("订单金额异常,请刷新后重试");
                }
                JSONArray parse = JSONArray.parse(orders);
                List<Long> orderIds = new ArrayList<>();
                Map<Long, BigDecimal> map = new HashMap<>();
                for (int i = 0; i < parse.size(); i++) {
                    JSONObject jsonObject = parse.getJSONObject(i);
                    Long id = jsonObject.getLong("id");
                    BigDecimal amount = jsonObject.getBigDecimal("amount");
                    orderIds.add(id);
                    map.put(id, amount);
                }
                long count = orderInvoiceDetailService.count(new LambdaQueryWrapper<TOrderInvoiceDetail>().eq(TOrderInvoiceDetail::getOrderType, addOrderInvoice.getOrderType())
                        .in(TOrderInvoiceDetail::getOrderId, orderIds));
                if(count > 0){
                orderIds.add(id);
                amountMap.put(id, amount);
            }
            List<TOrderInvoiceDetail> existingDetails = orderInvoiceDetailService.list(new LambdaQueryWrapper<TOrderInvoiceDetail>()
                    .eq(TOrderInvoiceDetail::getOrderType, addOrderInvoice.getOrderType())
                    .in(TOrderInvoiceDetail::getOrderId, orderIds));
            if (!CollectionUtils.isEmpty(existingDetails)) {
                List<Long> invoiceIds = existingDetails.stream().map(TOrderInvoiceDetail::getOrderInvoiceId).distinct().collect(Collectors.toList());
                long count = this.count(new LambdaQueryWrapper<TOrderInvoice>().in(TOrderInvoice::getId, invoiceIds).ne(TOrderInvoice::getStatus, 4));
                if (count > 0) {
                    return AjaxResult.error("不能重复申请开票,请刷新数据后重试");
                }
                SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
                String code = Double.valueOf(Math.random() * 1000).intValue() + sdf.format(new Date());
                //获取开票类型
                TInvoiceType invoiceType = invoiceTypeClient.getInvoiceType(addOrderInvoice.getInvoiceTypeId()).getData();
                addOrderInvoice.setInvoicingCompany(invoiceType.getInvoicingCompany());
                addOrderInvoice.setInvoiceType(invoiceType.getName());
                addOrderInvoice.setInvoiceMaterial(2);
                addOrderInvoice.setInvoicingMethod(invoiceType.getInvoicingMethod());
                addOrderInvoice.setAppUserId(userId);
                addOrderInvoice.setCode(code);
                addOrderInvoice.setStatus(1);
                addOrderInvoice.setCreateTime(LocalDateTime.now());
                fillInvoiceSite(addOrderInvoice, orderIds);
                if (Integer.valueOf(2).equals(addOrderInvoice.getInvoicingMethod())) {
                    addOrderInvoice.setStatus(3);
                    addOrderInvoice.setBillingTime(LocalDateTime.now());
                }
                this.save(addOrderInvoice);
                for (Long orderId : orderIds) {
                    List<TChargingOrderAccountingStrategy> list = chargingOrderAccountingStrategyService.list(new LambdaQueryWrapper<TChargingOrderAccountingStrategy>()
                            .eq(TChargingOrderAccountingStrategy::getChargingOrderId, orderId));
                    BigDecimal electrovalence = list.stream().map(TChargingOrderAccountingStrategy::getElectrovalence).reduce(BigDecimal.ZERO, BigDecimal::add);
                    BigDecimal serviceCharge = list.stream().map(TChargingOrderAccountingStrategy::getServiceCharge).reduce(BigDecimal.ZERO, BigDecimal::add);
                    TOrderInvoiceDetail orderInvoiceDetail = new TOrderInvoiceDetail();
                    orderInvoiceDetail.setOrderInvoiceId(addOrderInvoice.getId());
                    orderInvoiceDetail.setInvoiceAmount(map.get(orderId));
                    orderInvoiceDetail.setOrderType(addOrderInvoice.getOrderType());
                    orderInvoiceDetail.setOrderId(orderId);
                    orderInvoiceDetail.setElectricityTariff(invoiceType.getElectricityTariff());
                    orderInvoiceDetail.setServiceTariff(invoiceType.getServiceTariff());
                    orderInvoiceDetail.setAddedServiceTariff(invoiceType.getAddedServiceTariff());
                    orderInvoiceDetail.setElectrovalence(electrovalence);
                    orderInvoiceDetail.setServiceCharge(serviceCharge);
                    orderInvoiceDetail.setAddedService(serviceCharge.multiply(null == invoiceType.getAddedServiceTariff()
                            ? new BigDecimal(1) : invoiceType.getAddedServiceTariff().divide(new BigDecimal(100))));
                    orderInvoiceDetailService.save(orderInvoiceDetail);
                }
            }
        }catch (Exception e){
            //获取开票类型
            TInvoiceType invoiceType = invoiceTypeClient.getInvoiceType(addOrderInvoice.getInvoiceTypeId()).getData();
            if (invoiceType == null) {
                return AjaxResult.error("请选择有效的发票类型");
            }
            Map<Integer, List<Long>> invoiceOrderGroups = buildInvoiceOrderGroups(addOrderInvoice, orderIds);
            for (List<Long> groupOrderIds : invoiceOrderGroups.values()) {
                TOrderInvoice invoice = buildOrderInvoice(addOrderInvoice, userId, invoiceType, groupOrderIds, amountMap);
                this.save(invoice);
                saveInvoiceDetails(invoice, invoiceType, groupOrderIds, amountMap);
            }
            return AjaxResult.success();
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
            TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
            return AjaxResult.error("开票申请失败:" + (StringUtils.isEmpty(e.getMessage()) ? "请稍后重试" : e.getMessage()));
        } finally {
            if (locked && lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
        return AjaxResult.success();
    }
    private Map<Integer, List<Long>> buildInvoiceOrderGroups(AddOrderInvoice addOrderInvoice, List<Long> orderIds) {
        Map<Integer, List<Long>> groups = new LinkedHashMap<>();
        if (!Integer.valueOf(1).equals(addOrderInvoice.getOrderType())) {
            groups.put(0, orderIds);
            return groups;
        }
        List<TChargingOrder> chargingOrders = chargingOrderService.listByIds(orderIds);
        Map<Long, Integer> siteIdMap = chargingOrders.stream().collect(Collectors.toMap(TChargingOrder::getId, TChargingOrder::getSiteId, (a, b) -> a));
        for (Long orderId : orderIds) {
            Integer siteId = siteIdMap.get(orderId);
            if (siteId == null) {
                throw new IllegalArgumentException("订单所属电站异常,请刷新后重试");
            }
            groups.computeIfAbsent(siteId, key -> new ArrayList<>()).add(orderId);
        }
        return groups;
    }
    private TOrderInvoice buildOrderInvoice(AddOrderInvoice source, Long userId, TInvoiceType invoiceType, List<Long> orderIds, Map<Long, BigDecimal> amountMap) {
        TOrderInvoice invoice = new TOrderInvoice();
        BeanUtils.copyProperties(source, invoice);
        invoice.setId(null);
        invoice.setCode(generateInvoiceCode());
        invoice.setAppUserId(userId);
        invoice.setInvoicingCompany(invoiceType.getInvoicingCompany());
        invoice.setInvoiceType(invoiceType.getName());
        invoice.setInvoiceMaterial(2);
        invoice.setInvoicingMethod(invoiceType.getInvoicingMethod());
        invoice.setTotalAmount(orderIds.stream().map(amountMap::get).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add));
        invoice.setInvoiceUrl(null);
        invoice.setBillingUserId(null);
        invoice.setCancelTime(null);
        invoice.setStatus(1);
        invoice.setCreateTime(LocalDateTime.now());
        invoice.setBillingTime(null);
        invoice.setSiteId(null);
        invoice.setPartnerId(null);
        fillInvoiceSite(invoice, orderIds);
        if (Integer.valueOf(2).equals(invoice.getInvoicingMethod())) {
            invoice.setStatus(3);
            invoice.setBillingTime(LocalDateTime.now());
        }
        return invoice;
    }
    private void saveInvoiceDetails(TOrderInvoice invoice, TInvoiceType invoiceType, List<Long> orderIds, Map<Long, BigDecimal> amountMap) {
        for (Long orderId : orderIds) {
            BigDecimal electrovalence = BigDecimal.ZERO;
            BigDecimal serviceCharge = BigDecimal.ZERO;
            if (Integer.valueOf(1).equals(invoice.getOrderType())) {
                List<TChargingOrderAccountingStrategy> list = chargingOrderAccountingStrategyService.list(new LambdaQueryWrapper<TChargingOrderAccountingStrategy>()
                        .eq(TChargingOrderAccountingStrategy::getChargingOrderId, orderId));
                electrovalence = list.stream().map(TChargingOrderAccountingStrategy::getElectrovalence).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
                serviceCharge = list.stream().map(TChargingOrderAccountingStrategy::getServiceCharge).filter(Objects::nonNull).reduce(BigDecimal.ZERO, BigDecimal::add);
            }
            TOrderInvoiceDetail orderInvoiceDetail = new TOrderInvoiceDetail();
            orderInvoiceDetail.setOrderInvoiceId(invoice.getId());
            orderInvoiceDetail.setInvoiceAmount(amountMap.get(orderId));
            orderInvoiceDetail.setOrderType(invoice.getOrderType());
            orderInvoiceDetail.setOrderId(orderId);
            orderInvoiceDetail.setElectricityTariff(invoiceType.getElectricityTariff());
            orderInvoiceDetail.setServiceTariff(invoiceType.getServiceTariff());
            orderInvoiceDetail.setAddedServiceTariff(invoiceType.getAddedServiceTariff());
            orderInvoiceDetail.setElectrovalence(electrovalence);
            orderInvoiceDetail.setServiceCharge(serviceCharge);
            orderInvoiceDetail.setAddedService(serviceCharge.multiply(null == invoiceType.getAddedServiceTariff()
                    ? new BigDecimal(1) : invoiceType.getAddedServiceTariff().divide(new BigDecimal(100))));
            orderInvoiceDetailService.save(orderInvoiceDetail);
        }
    }
    private String generateInvoiceCode() {
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmssSSS");
        return Double.valueOf(Math.random() * 1000).intValue() + sdf.format(new Date());
    }
    private void fillInvoiceSite(TOrderInvoice invoice, List<Long> orderIds) {
@@ -208,7 +278,7 @@
        Long userId = tokenService.getLoginUserApplet().getUserId();
        Integer pageCurr = (query.getPageCurr() - 1) * query.getPageSize();
        LambdaQueryWrapper<TOrderInvoice> wrapper = new LambdaQueryWrapper<TOrderInvoice>().eq(TOrderInvoice::getAppUserId, userId);
        if(query.getStatus() != 1){
        if(query.getStatus() != null){
            wrapper.eq(TOrderInvoice::getStatus, query.getStatus());
        }
        List<TOrderInvoice> list = this.list(wrapper.orderByDesc(TOrderInvoice::getCreateTime).last(" limit " + pageCurr + ", " + query.getPageSize()));
@@ -219,6 +289,7 @@
            orderInvoiceList.setOrderType(tOrderInvoice.getOrderType());
            orderInvoiceList.setTotalAmount(tOrderInvoice.getTotalAmount());
            orderInvoiceList.setName(tOrderInvoice.getName());
            orderInvoiceList.setStatus(tOrderInvoice.getStatus());
            orderInvoiceList.setCreateTime(tOrderInvoice.getCreateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
            pageList.add(orderInvoiceList);
        }
ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TChargingOrderMapper.xml
@@ -83,7 +83,7 @@
        <if test="null != month and '' != month">
            and DATE_FORMAT(end_time, '%Y-%m') = #{month}
        </if>
        and id not in (select order_id from t_order_invoice_detail where order_type = 1)
        and id not in (select tod.order_id from t_order_invoice_detail tod inner join t_order_invoice toi on toi.id = tod.order_invoice_id where tod.order_type = 1 and toi.status != 4)
        order by create_time desc
        <if test="null != pageCurr and null != pageSize">
            limit #{pageCurr}, #{pageSize}
ruoyi-service/ruoyi-order/src/main/resources/mapper/order/TShoppingOrderMapper.xml
@@ -204,7 +204,7 @@
        <if test="null != month and '' != month">
            and DATE_FORMAT(create_time, '%Y-%m') = #{month}
        </if>
        and id not in (select order_id from t_order_invoice_detail where order_type = 2)
        and id not in (select tod.order_id from t_order_invoice_detail tod inner join t_order_invoice toi on toi.id = tod.order_invoice_id where tod.order_type = 2 and toi.status != 4)
        order by create_time desc
        <if test="null != pageCurr and null != pageSize">
            limit #{pageCurr}, #{pageSize}
sql/20260703_workbench_menu.sql
New file
@@ -0,0 +1,69 @@
-- 工作台权限菜单修复
-- 作用:让角色管理的“添加/编辑角色”弹窗显示 工作台 -> 工作台 -> 数据驾驶舱/视频监控。
-- 注意:当前库里 menu_id = 1 已经是工作台入口,但 menu_type 曾是 C,角色弹窗只展示 M 作为模块。
UPDATE sys_menu
SET menu_name = '工作台',
    parent_id = 0,
    order_num = 1,
    path = '/workbench',
    component = NULL,
    menu_type = 'M',
    visible = '0',
    status = 0,
    perms = NULL,
    icon = '#',
    update_time = NOW()
WHERE menu_id = 1;
SET @workbench_page_id := (
    SELECT menu_id FROM sys_menu
    WHERE parent_id = 1 AND path = '/workbench/index'
    ORDER BY menu_id ASC
    LIMIT 1
);
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT '工作台', 1, 1, '/workbench/index', 'stat/index', 1, 0, 'C', '0', 0, NULL, '#', 'admin', NOW(), '工作台页面'
WHERE @workbench_page_id IS NULL;
SET @workbench_page_id := (
    SELECT menu_id FROM sys_menu
    WHERE parent_id = 1 AND path = '/workbench/index'
    ORDER BY menu_id ASC
    LIMIT 1
);
UPDATE sys_menu
SET menu_name = '工作台',
    parent_id = 1,
    order_num = 1,
    path = '/workbench/index',
    component = 'stat/index',
    menu_type = 'C',
    visible = '0',
    status = 0,
    perms = NULL,
    update_time = NOW()
WHERE menu_id = @workbench_page_id;
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT '数据驾驶舱', @workbench_page_id, 1, '/workbench/dashboard', NULL, 1, 0, 'F', '0', 0, NULL, '#', 'admin', NOW(), '工作台数据驾驶舱权限'
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE parent_id = @workbench_page_id AND path = '/workbench/dashboard');
INSERT INTO sys_menu (menu_name, parent_id, order_num, path, component, is_frame, is_cache, menu_type, visible, status, perms, icon, create_by, create_time, remark)
SELECT '视频监控', @workbench_page_id, 2, '/workbench/video', NULL, 1, 0, 'F', '0', 0, NULL, '#', 'admin', NOW(), '工作台视频监控权限'
WHERE NOT EXISTS (SELECT 1 FROM sys_menu WHERE parent_id = @workbench_page_id AND path = '/workbench/video');
-- 修正早期 FAQ 脚本误挂到 menu_id = 1 的情况。
UPDATE sys_menu
SET parent_id = 164,
    order_num = 100,
    component = 'contentSetting/faq',
    update_time = NOW()
WHERE path = '/faq' AND parent_id = 1;
-- 管理员角色补上新权限。
INSERT IGNORE INTO sys_role_menu (role_id, menu_id)
SELECT 1, menu_id FROM sys_menu
WHERE menu_id = 1 OR parent_id = 1 OR parent_id = @workbench_page_id;