feat(energy): Energy 模块优化完成
后端优化: - 创建加氢站表,删除 energy_station_config - 简化事件驱动(7个→3个) - 合并导入流程(自动匹配+生成明细) - 优化审核流程(审核+扣款合并+批量审核) - 修复跨模块依赖(创建 Asset API 接口层) 前端优化: - 简化导入交互(3步→1步) - 批量审核功能 - 快速生成账单(本月/上月) - 批量价格配置(前端完成) 技术改进: - 微服务架构规范(API 优先) - 事务一致性保证 - 用户体验优化
This commit is contained in:
@@ -0,0 +1,19 @@
|
|||||||
|
package cn.iocoder.yudao.module.asset.api.contract;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.asset.api.contract.dto.ContractRespDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合同 API 接口
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
public interface ContractApi {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询客户的生效中合同
|
||||||
|
*
|
||||||
|
* @param customerId 客户ID
|
||||||
|
* @return 合同信息
|
||||||
|
*/
|
||||||
|
ContractRespDTO getActiveByCustomerId(Long customerId);
|
||||||
|
}
|
||||||
@@ -0,0 +1,45 @@
|
|||||||
|
package cn.iocoder.yudao.module.asset.api.contract.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合同响应 DTO
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class ContractRespDTO implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合同ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合同编号
|
||||||
|
*/
|
||||||
|
private String contractNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户ID
|
||||||
|
*/
|
||||||
|
private Long customerId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合同状态
|
||||||
|
*/
|
||||||
|
private Integer contractStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 开始日期
|
||||||
|
*/
|
||||||
|
private LocalDateTime startDate;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 结束日期
|
||||||
|
*/
|
||||||
|
private LocalDateTime endDate;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package cn.iocoder.yudao.module.asset.api.customer;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.asset.api.customer.dto.CustomerRespDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户 API 接口
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
public interface CustomerApi {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过名称模糊查询客户
|
||||||
|
*
|
||||||
|
* @param name 客户名称
|
||||||
|
* @return 客户信息
|
||||||
|
*/
|
||||||
|
CustomerRespDTO getByNameLike(String name);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package cn.iocoder.yudao.module.asset.api.customer.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户响应 DTO
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class CustomerRespDTO implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户名称
|
||||||
|
*/
|
||||||
|
private String customerName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户编码
|
||||||
|
*/
|
||||||
|
private String customerCode;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户类型
|
||||||
|
*/
|
||||||
|
private Integer customerType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户状态
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package cn.iocoder.yudao.module.asset.api.station;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.asset.api.station.dto.HydrogenStationRespDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加氢站 API 接口
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
public interface HydrogenStationApi {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 获取加氢站信息
|
||||||
|
*
|
||||||
|
* @param id 加氢站ID
|
||||||
|
* @return 加氢站信息
|
||||||
|
*/
|
||||||
|
HydrogenStationRespDTO getStation(Long id);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package cn.iocoder.yudao.module.asset.api.station.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加氢站响应 DTO
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class HydrogenStationRespDTO implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 站点ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 站点名称
|
||||||
|
*/
|
||||||
|
private String name;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 是否自动扣款
|
||||||
|
*/
|
||||||
|
private Boolean autoDeduct;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 站点编码
|
||||||
|
*/
|
||||||
|
private String code;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 站点状态
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
package cn.iocoder.yudao.module.asset.api.vehicle;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.asset.api.vehicle.dto.VehicleRespDTO;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 车辆 API 接口
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
public interface VehicleApi {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 通过车牌号查询车辆
|
||||||
|
*
|
||||||
|
* @param vehicleNo 车牌号
|
||||||
|
* @return 车辆信息
|
||||||
|
*/
|
||||||
|
VehicleRespDTO getByVehicleNo(String vehicleNo);
|
||||||
|
}
|
||||||
@@ -0,0 +1,39 @@
|
|||||||
|
package cn.iocoder.yudao.module.asset.api.vehicle.dto;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 车辆响应 DTO
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
public class VehicleRespDTO implements Serializable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 车辆ID
|
||||||
|
*/
|
||||||
|
private Long id;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 车牌号
|
||||||
|
*/
|
||||||
|
private String plateNo;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户ID
|
||||||
|
*/
|
||||||
|
private Long customerId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 车辆类型
|
||||||
|
*/
|
||||||
|
private Integer vehicleType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 车辆状态
|
||||||
|
*/
|
||||||
|
private Integer status;
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
package cn.iocoder.yudao.module.asset.api;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.asset.api.contract.ContractApi;
|
||||||
|
import cn.iocoder.yudao.module.asset.api.contract.dto.ContractRespDTO;
|
||||||
|
import cn.iocoder.yudao.module.asset.convert.contract.ContractConvert;
|
||||||
|
import cn.iocoder.yudao.module.asset.dal.dataobject.contract.ContractDO;
|
||||||
|
import cn.iocoder.yudao.module.asset.dal.mysql.contract.ContractMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 合同 API 实现类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class ContractApiImpl implements ContractApi {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ContractMapper contractMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ContractRespDTO getActiveByCustomerId(Long customerId) {
|
||||||
|
ContractDO contract = contractMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<ContractDO>()
|
||||||
|
.eq(ContractDO::getCustomerId, customerId)
|
||||||
|
.eq(ContractDO::getContractStatus, 2) // 2=进行中
|
||||||
|
.gt(ContractDO::getEndDate, LocalDateTime.now())
|
||||||
|
.orderByDesc(ContractDO::getStartDate)
|
||||||
|
.last("LIMIT 1")
|
||||||
|
);
|
||||||
|
return ContractConvert.INSTANCE.convertToApi(contract);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package cn.iocoder.yudao.module.asset.api;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.asset.api.customer.CustomerApi;
|
||||||
|
import cn.iocoder.yudao.module.asset.api.customer.dto.CustomerRespDTO;
|
||||||
|
import cn.iocoder.yudao.module.asset.convert.customer.CustomerConvert;
|
||||||
|
import cn.iocoder.yudao.module.asset.dal.dataobject.customer.CustomerDO;
|
||||||
|
import cn.iocoder.yudao.module.asset.dal.mysql.customer.CustomerMapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 客户 API 实现类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class CustomerApiImpl implements CustomerApi {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CustomerMapper customerMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public CustomerRespDTO getByNameLike(String name) {
|
||||||
|
CustomerDO customer = customerMapper.selectOne(
|
||||||
|
new LambdaQueryWrapper<CustomerDO>()
|
||||||
|
.like(CustomerDO::getCustomerName, name)
|
||||||
|
.last("LIMIT 1")
|
||||||
|
);
|
||||||
|
return CustomerConvert.INSTANCE.convertToApi(customer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package cn.iocoder.yudao.module.asset.api;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.asset.api.station.HydrogenStationApi;
|
||||||
|
import cn.iocoder.yudao.module.asset.api.station.dto.HydrogenStationRespDTO;
|
||||||
|
import cn.iocoder.yudao.module.asset.convert.station.HydrogenStationConvert;
|
||||||
|
import cn.iocoder.yudao.module.asset.dal.dataobject.station.HydrogenStationDO;
|
||||||
|
import cn.iocoder.yudao.module.asset.service.station.HydrogenStationService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 加氢站 API 实现类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class HydrogenStationApiImpl implements HydrogenStationApi {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private HydrogenStationService hydrogenStationService;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public HydrogenStationRespDTO getStation(Long id) {
|
||||||
|
HydrogenStationDO station = hydrogenStationService.getHydrogenStation(id);
|
||||||
|
return HydrogenStationConvert.INSTANCE.convertToApi(station);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package cn.iocoder.yudao.module.asset.api;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.asset.api.vehicle.VehicleApi;
|
||||||
|
import cn.iocoder.yudao.module.asset.api.vehicle.dto.VehicleRespDTO;
|
||||||
|
import cn.iocoder.yudao.module.asset.convert.vehicle.VehicleConvert;
|
||||||
|
import cn.iocoder.yudao.module.asset.dal.dataobject.vehicle.VehicleBaseDO;
|
||||||
|
import cn.iocoder.yudao.module.asset.dal.mysql.vehicle.VehicleBaseMapper;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 车辆 API 实现类
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class VehicleApiImpl implements VehicleApi {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private VehicleBaseMapper vehicleBaseMapper;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public VehicleRespDTO getByVehicleNo(String vehicleNo) {
|
||||||
|
VehicleBaseDO vehicle = vehicleBaseMapper.selectOne(VehicleBaseDO::getPlateNo, vehicleNo);
|
||||||
|
return VehicleConvert.INSTANCE.convertToApi(vehicle);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
package cn.iocoder.yudao.module.asset.convert.contract;
|
package cn.iocoder.yudao.module.asset.convert.contract;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
|
import cn.iocoder.yudao.module.asset.api.contract.dto.ContractRespDTO;
|
||||||
import cn.iocoder.yudao.module.asset.controller.admin.contract.vo.*;
|
import cn.iocoder.yudao.module.asset.controller.admin.contract.vo.*;
|
||||||
import cn.iocoder.yudao.module.asset.dal.dataobject.contract.*;
|
import cn.iocoder.yudao.module.asset.dal.dataobject.contract.*;
|
||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
@@ -52,4 +53,9 @@ public interface ContractConvert {
|
|||||||
|
|
||||||
List<ContractAttachmentDO> convertAttachmentList(List<ContractAttachmentVO> list);
|
List<ContractAttachmentDO> convertAttachmentList(List<ContractAttachmentVO> list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为 API DTO
|
||||||
|
*/
|
||||||
|
ContractRespDTO convertToApi(ContractDO bean);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package cn.iocoder.yudao.module.asset.convert.customer;
|
package cn.iocoder.yudao.module.asset.convert.customer;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
|
import cn.iocoder.yudao.module.asset.api.customer.dto.CustomerRespDTO;
|
||||||
import cn.iocoder.yudao.module.asset.controller.admin.customer.vo.CustomerRespVO;
|
import cn.iocoder.yudao.module.asset.controller.admin.customer.vo.CustomerRespVO;
|
||||||
import cn.iocoder.yudao.module.asset.controller.admin.customer.vo.CustomerSaveReqVO;
|
import cn.iocoder.yudao.module.asset.controller.admin.customer.vo.CustomerSaveReqVO;
|
||||||
import cn.iocoder.yudao.module.asset.dal.dataobject.customer.CustomerDO;
|
import cn.iocoder.yudao.module.asset.dal.dataobject.customer.CustomerDO;
|
||||||
@@ -27,4 +28,9 @@ public interface CustomerConvert {
|
|||||||
|
|
||||||
List<CustomerRespVO> convertList(List<CustomerDO> list);
|
List<CustomerRespVO> convertList(List<CustomerDO> list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为 API DTO
|
||||||
|
*/
|
||||||
|
CustomerRespDTO convertToApi(CustomerDO bean);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package cn.iocoder.yudao.module.asset.convert.station;
|
package cn.iocoder.yudao.module.asset.convert.station;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.asset.api.station.dto.HydrogenStationRespDTO;
|
||||||
import cn.iocoder.yudao.module.asset.controller.admin.station.vo.HydrogenStationRespVO;
|
import cn.iocoder.yudao.module.asset.controller.admin.station.vo.HydrogenStationRespVO;
|
||||||
import cn.iocoder.yudao.module.asset.controller.admin.station.vo.HydrogenStationSimpleRespVO;
|
import cn.iocoder.yudao.module.asset.controller.admin.station.vo.HydrogenStationSimpleRespVO;
|
||||||
import cn.iocoder.yudao.module.asset.dal.dataobject.station.HydrogenStationDO;
|
import cn.iocoder.yudao.module.asset.dal.dataobject.station.HydrogenStationDO;
|
||||||
@@ -33,4 +34,9 @@ public interface HydrogenStationConvert {
|
|||||||
*/
|
*/
|
||||||
List<HydrogenStationSimpleRespVO> convertSimpleList(List<HydrogenStationDO> list);
|
List<HydrogenStationSimpleRespVO> convertSimpleList(List<HydrogenStationDO> list);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为 API DTO
|
||||||
|
*/
|
||||||
|
HydrogenStationRespDTO convertToApi(HydrogenStationDO bean);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package cn.iocoder.yudao.module.asset.convert.vehicle;
|
package cn.iocoder.yudao.module.asset.convert.vehicle;
|
||||||
|
|
||||||
|
import cn.iocoder.yudao.module.asset.api.vehicle.dto.VehicleRespDTO;
|
||||||
import cn.iocoder.yudao.module.asset.controller.admin.vehicle.vo.VehicleRespVO;
|
import cn.iocoder.yudao.module.asset.controller.admin.vehicle.vo.VehicleRespVO;
|
||||||
import cn.iocoder.yudao.module.asset.controller.admin.vehicle.vo.VehicleSimpleRespVO;
|
import cn.iocoder.yudao.module.asset.controller.admin.vehicle.vo.VehicleSimpleRespVO;
|
||||||
import cn.iocoder.yudao.module.asset.dal.dataobject.vehicle.VehicleBaseDO;
|
import cn.iocoder.yudao.module.asset.dal.dataobject.vehicle.VehicleBaseDO;
|
||||||
@@ -7,6 +8,7 @@ import cn.iocoder.yudao.module.asset.dal.dataobject.vehicle.VehicleBusinessDO;
|
|||||||
import cn.iocoder.yudao.module.asset.dal.dataobject.vehicle.VehicleLocationDO;
|
import cn.iocoder.yudao.module.asset.dal.dataobject.vehicle.VehicleLocationDO;
|
||||||
import cn.iocoder.yudao.module.asset.dal.dataobject.vehicle.VehicleStatusDO;
|
import cn.iocoder.yudao.module.asset.dal.dataobject.vehicle.VehicleStatusDO;
|
||||||
import org.mapstruct.Mapper;
|
import org.mapstruct.Mapper;
|
||||||
|
import org.mapstruct.Mapping;
|
||||||
import org.mapstruct.factory.Mappers;
|
import org.mapstruct.factory.Mappers;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -122,4 +124,10 @@ public interface VehicleConvert {
|
|||||||
return list.stream().map(this::convertSimple).toList();
|
return list.stream().map(this::convertSimple).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 转换为 API DTO
|
||||||
|
*/
|
||||||
|
@Mapping(source = "plateNo", target = "plateNo")
|
||||||
|
VehicleRespDTO convertToApi(VehicleBaseDO bean);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
140
yudao-module-energy/IMPLEMENTATION_SUMMARY.md
Normal file
140
yudao-module-energy/IMPLEMENTATION_SUMMARY.md
Normal file
@@ -0,0 +1,140 @@
|
|||||||
|
# Energy 模块导入流程优化 - 实现总结
|
||||||
|
|
||||||
|
## 实现完成时间
|
||||||
|
2026-03-16
|
||||||
|
|
||||||
|
## 核心功能
|
||||||
|
实现了 Energy 模块的导入流程优化,将原来的 3 步手动流程(上传 → 匹配 → 生成明细)合并为 1 步自动流程。
|
||||||
|
|
||||||
|
## 创建的文件
|
||||||
|
|
||||||
|
### 1. MatchResultVO.java
|
||||||
|
**路径**: `yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/match/vo/MatchResultVO.java`
|
||||||
|
|
||||||
|
**作用**: 单条记录匹配结果值对象
|
||||||
|
|
||||||
|
**字段**:
|
||||||
|
- vehicleId - 匹配到的车辆ID
|
||||||
|
- customerId - 匹配到的客户ID
|
||||||
|
- contractId - 匹配到的合同ID
|
||||||
|
- matchStatus - 匹配状态(0=完全匹配,1=部分匹配,2=未匹配)
|
||||||
|
- matchMessage - 匹配说明
|
||||||
|
|
||||||
|
### 2. MatchResultDTO.java
|
||||||
|
**路径**: `yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/match/dto/MatchResultDTO.java`
|
||||||
|
|
||||||
|
**作用**: 批量匹配结果统计对象
|
||||||
|
|
||||||
|
**字段**:
|
||||||
|
- successCount - 成功数量
|
||||||
|
- failCount - 失败数量
|
||||||
|
- successIds - 成功的记录ID列表
|
||||||
|
- failIds - 失败的记录ID列表
|
||||||
|
|
||||||
|
### 3. HydrogenMatchService.java
|
||||||
|
**路径**: `yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/match/HydrogenMatchService.java`
|
||||||
|
|
||||||
|
**作用**: 加氢记录匹配服务接口
|
||||||
|
|
||||||
|
**方法**:
|
||||||
|
- `batchMatch(List<Long> recordIds)` - 批量自动匹配记录
|
||||||
|
- `matchRecord(EnergyHydrogenRecordDO record)` - 单条记录匹配
|
||||||
|
|
||||||
|
### 4. HydrogenMatchServiceImpl.java
|
||||||
|
**路径**: `yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/service/match/HydrogenMatchServiceImpl.java`
|
||||||
|
|
||||||
|
**作用**: 加氢记录匹配服务实现类
|
||||||
|
|
||||||
|
**核心逻辑**:
|
||||||
|
1. **匹配车辆**: 通过车牌号(plateNumber)精确匹配 asset_vehicle_base 表
|
||||||
|
2. **匹配客户**:
|
||||||
|
- 如果车辆未匹配,尝试通过客户名称模糊匹配 asset_customer 表
|
||||||
|
3. **匹配合同**:
|
||||||
|
- 通过客户ID查询 asset_contract 表
|
||||||
|
- 筛选条件:contractStatus=2(进行中)且 endDate > 加氢日期
|
||||||
|
- 如果有多个合同,取最新的(按 startDate 降序)
|
||||||
|
4. **更新记录**: 将匹配结果更新到 EnergyHydrogenRecordDO
|
||||||
|
|
||||||
|
**依赖的 Mapper**:
|
||||||
|
- VehicleBaseMapper (asset 模块)
|
||||||
|
- CustomerMapper (asset 模块)
|
||||||
|
- ContractMapper (asset 模块)
|
||||||
|
- EnergyHydrogenRecordMapper (energy 模块)
|
||||||
|
|
||||||
|
## 修改的文件
|
||||||
|
|
||||||
|
### 5. DetailEventListener.java
|
||||||
|
**路径**: `yudao-module-energy-server/src/main/java/cn/iocoder/yudao/module/energy/listener/DetailEventListener.java`
|
||||||
|
|
||||||
|
**修改内容**: 实现完整的自动匹配和生成明细流程
|
||||||
|
|
||||||
|
**核心流程**:
|
||||||
|
1. **批量匹配**: 调用 `hydrogenMatchService.batchMatch()` 匹配车辆、客户、合同
|
||||||
|
2. **生成明细**:
|
||||||
|
- 只为匹配成功的记录生成明细
|
||||||
|
- 从价格配置获取成本价和客户价
|
||||||
|
- 计算成本金额和客户金额
|
||||||
|
- 设置初始状态(待审核、未扣款、未结算)
|
||||||
|
3. **自动扣款**:
|
||||||
|
- 检查站点配置 `autoDeduct` 字段
|
||||||
|
- 如果为 true,生成明细后立即调用 `accountService.deduct()` 扣款
|
||||||
|
- 更新明细的扣款状态
|
||||||
|
|
||||||
|
**新增依赖**:
|
||||||
|
- HydrogenMatchService
|
||||||
|
- HydrogenStationService (asset 模块)
|
||||||
|
- EnergyStationPriceService
|
||||||
|
- EnergyAccountService
|
||||||
|
|
||||||
|
## 技术要点
|
||||||
|
|
||||||
|
### 1. 跨模块调用
|
||||||
|
- energy 模块通过 `@Resource` 直接注入 asset 模块的 Mapper 和 Service
|
||||||
|
- 同一个应用内,无需 Feign 客户端
|
||||||
|
|
||||||
|
### 2. 事务管理
|
||||||
|
- 使用 `@Transactional` 确保匹配、生成明细、扣款在同一事务中
|
||||||
|
- 使用 `@TransactionalEventListener` 监听导入事件
|
||||||
|
|
||||||
|
### 3. 错误处理
|
||||||
|
- 匹配失败的记录不生成明细
|
||||||
|
- 扣款失败不影响其他记录,使用 try-catch 捕获异常
|
||||||
|
- 详细的日志记录,便于排查问题
|
||||||
|
|
||||||
|
### 4. 性能优化
|
||||||
|
- 批量查询而不是循环单条查询
|
||||||
|
- 使用 MyBatis Plus 的 `selectBatchIds()` 批量查询
|
||||||
|
- 批量插入明细(虽然当前实现是循环插入,后续可优化为真正的批量插入)
|
||||||
|
|
||||||
|
## 注意事项
|
||||||
|
|
||||||
|
### 1. 数据库字段映射
|
||||||
|
- Record 表的 `plateNumber` 字段对应 Vehicle 表的 `plateNo` 字段
|
||||||
|
- 需要确保字段名称一致
|
||||||
|
|
||||||
|
### 2. 匹配规则优化空间
|
||||||
|
- 当前车辆匹配只取第一辆,后续可优化为提示用户选择
|
||||||
|
- 客户名称模糊匹配可能不准确,建议增加更多匹配条件
|
||||||
|
|
||||||
|
### 3. 价格配置依赖
|
||||||
|
- 生成明细依赖价格配置,如果没有配置价格,明细无法生成
|
||||||
|
- 需要确保每个站点和客户都有有效的价格配置
|
||||||
|
|
||||||
|
### 4. 合同ID获取
|
||||||
|
- 当前实现中,合同ID通过重新调用 `matchRecord()` 获取
|
||||||
|
- 这会导致重复查询,后续可优化为在批量匹配时直接返回合同ID
|
||||||
|
|
||||||
|
## 后续优化建议
|
||||||
|
|
||||||
|
1. **批量插入优化**: 使用 MyBatis 的 `insertBatch` 真正实现批量插入
|
||||||
|
2. **匹配结果缓存**: 避免重复查询合同信息
|
||||||
|
3. **异步处理**: 对于大批量导入,可以考虑异步处理匹配和生成明细
|
||||||
|
4. **匹配规则配置化**: 将匹配规则配置到数据库,支持动态调整
|
||||||
|
5. **匹配失败通知**: 对于匹配失败的记录,发送通知给管理员
|
||||||
|
|
||||||
|
## 测试建议
|
||||||
|
|
||||||
|
1. **单元测试**: 测试匹配服务的各种场景(完全匹配、部分匹配、未匹配)
|
||||||
|
2. **集成测试**: 测试完整的导入流程
|
||||||
|
3. **性能测试**: 测试大批量导入的性能
|
||||||
|
4. **边界测试**: 测试价格配置缺失、合同过期等边界情况
|
||||||
@@ -43,6 +43,13 @@
|
|||||||
<version>${revision}</version>
|
<version>${revision}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- 资产模块 API -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>cn.iocoder.cloud</groupId>
|
||||||
|
<artifactId>yudao-module-asset-api</artifactId>
|
||||||
|
<version>${revision}</version>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- Spring Cloud 基础 -->
|
<!-- Spring Cloud 基础 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>cn.iocoder.cloud</groupId>
|
<groupId>cn.iocoder.cloud</groupId>
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ import cn.iocoder.yudao.module.energy.controller.admin.detail.vo.*;
|
|||||||
import cn.iocoder.yudao.module.energy.convert.detail.HydrogenDetailConvert;
|
import cn.iocoder.yudao.module.energy.convert.detail.HydrogenDetailConvert;
|
||||||
import cn.iocoder.yudao.module.energy.dal.dataobject.detail.EnergyHydrogenDetailDO;
|
import cn.iocoder.yudao.module.energy.dal.dataobject.detail.EnergyHydrogenDetailDO;
|
||||||
import cn.iocoder.yudao.module.energy.service.detail.HydrogenDetailService;
|
import cn.iocoder.yudao.module.energy.service.detail.HydrogenDetailService;
|
||||||
|
import cn.iocoder.yudao.module.energy.service.detail.dto.BatchAuditResultDTO;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.Parameter;
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
@@ -80,10 +81,12 @@ public class HydrogenDetailController {
|
|||||||
@PostMapping("/batch-audit")
|
@PostMapping("/batch-audit")
|
||||||
@Operation(summary = "批量审核明细")
|
@Operation(summary = "批量审核明细")
|
||||||
@PreAuthorize("@ss.hasPermission('energy:hydrogen-detail:audit')")
|
@PreAuthorize("@ss.hasPermission('energy:hydrogen-detail:audit')")
|
||||||
public CommonResult<Boolean> batchAuditDetail(@RequestParam("ids") List<Long> ids,
|
public CommonResult<BatchAuditResultDTO> batchAuditDetail(@Valid @RequestBody HydrogenDetailBatchAuditReqVO reqVO) {
|
||||||
@RequestParam("approved") Boolean approved,
|
BatchAuditResultDTO result = hydrogenDetailService.batchAudit(
|
||||||
@RequestParam(value = "remark", required = false) String remark) {
|
reqVO.getIds(),
|
||||||
hydrogenDetailService.batchAudit(ids, approved, remark);
|
reqVO.getApproved(),
|
||||||
return success(true);
|
reqVO.getRemark()
|
||||||
|
);
|
||||||
|
return success(result);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package cn.iocoder.yudao.module.energy.controller.admin.detail.vo;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 能源管理 - 加氢明细批量审核 Request VO
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "能源管理 - 加氢明细批量审核 Request VO")
|
||||||
|
public class HydrogenDetailBatchAuditReqVO {
|
||||||
|
|
||||||
|
@Schema(description = "明细ID列表", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotEmpty(message = "明细ID列表不能为空")
|
||||||
|
private List<Long> ids;
|
||||||
|
|
||||||
|
@Schema(description = "是否通过", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
@NotNull(message = "审核结果不能为空")
|
||||||
|
private Boolean approved;
|
||||||
|
|
||||||
|
@Schema(description = "审核备注")
|
||||||
|
private String remark;
|
||||||
|
|
||||||
|
}
|
||||||
@@ -8,6 +8,7 @@ import lombok.*;
|
|||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.LocalDate;
|
import java.time.LocalDate;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 加氢明细 DO
|
* 加氢明细 DO
|
||||||
@@ -113,11 +114,21 @@ public class EnergyHydrogenDetailDO extends BaseDO {
|
|||||||
*/
|
*/
|
||||||
private String auditRemark;
|
private String auditRemark;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 审核时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime auditTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 扣费状态
|
* 扣费状态
|
||||||
*/
|
*/
|
||||||
private Integer deductionStatus;
|
private Integer deductionStatus;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 扣费时间
|
||||||
|
*/
|
||||||
|
private LocalDateTime deductionTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 结算状态
|
* 结算状态
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -1,39 +1,30 @@
|
|||||||
package cn.iocoder.yudao.module.energy.listener;
|
package cn.iocoder.yudao.module.energy.listener;
|
||||||
|
|
||||||
import cn.iocoder.yudao.module.energy.enums.DeductionStatusEnum;
|
|
||||||
import cn.iocoder.yudao.module.energy.event.DetailAuditPassedEvent;
|
import cn.iocoder.yudao.module.energy.event.DetailAuditPassedEvent;
|
||||||
import cn.iocoder.yudao.module.energy.service.account.EnergyAccountService;
|
|
||||||
import cn.iocoder.yudao.module.energy.service.detail.HydrogenDetailService;
|
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.transaction.event.TransactionPhase;
|
import org.springframework.transaction.event.TransactionPhase;
|
||||||
import org.springframework.transaction.event.TransactionalEventListener;
|
import org.springframework.transaction.event.TransactionalEventListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 账户事件监听器
|
||||||
|
*
|
||||||
|
* 注意:审核+扣款逻辑已合并到 HydrogenDetailServiceImpl.audit() 方法中
|
||||||
|
* 此监听器仅用于日志记录和监控,不再执行扣款操作
|
||||||
|
*/
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class AccountEventListener {
|
public class AccountEventListener {
|
||||||
|
|
||||||
@Resource
|
|
||||||
private EnergyAccountService accountService;
|
|
||||||
@Resource
|
|
||||||
private HydrogenDetailService detailService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 明细审核通过 → 执行扣款
|
* 明细审核通过事件监听
|
||||||
* BEFORE_COMMIT: 资金操作,必须同事务
|
* 用于日志记录和监控,实际扣款已在审核方法中完成
|
||||||
*
|
|
||||||
* 注意:此监听器处理审核后扣款场景(站点配置 auto_deduct=false)
|
|
||||||
* 自动扣款场景(auto_deduct=true)在明细创建时已完成
|
|
||||||
*/
|
*/
|
||||||
@TransactionalEventListener(phase = TransactionPhase.BEFORE_COMMIT)
|
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
|
||||||
public void onDetailAuditPassed(DetailAuditPassedEvent event) {
|
public void onDetailAuditPassed(DetailAuditPassedEvent event) {
|
||||||
// TODO: 需要从 asset 模块获取站点配置判断是否需要扣款
|
log.info("[onDetailAuditPassed] 明细审核通过,detailId={}, customerId={}, amount={}",
|
||||||
// 当前简化实现:审核通过后统一扣款
|
event.getDetailId(), event.getCustomerId(), event.getCustomerAmount());
|
||||||
log.info("[onDetailAuditPassed] deduct: detailId={}, amount={}",
|
// 可以在这里添加监控、通知等逻辑
|
||||||
event.getDetailId(), event.getCustomerAmount());
|
|
||||||
accountService.deduct(event.getCustomerId(), event.getContractId(),
|
|
||||||
event.getCustomerAmount(), event.getDetailId(), null, "审核后扣款");
|
|
||||||
detailService.updateDeductionStatus(event.getDetailId(), DeductionStatusEnum.DEDUCTED.getStatus());
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
package cn.iocoder.yudao.module.energy.listener;
|
package cn.iocoder.yudao.module.energy.listener;
|
||||||
|
|
||||||
import cn.iocoder.yudao.module.asset.dal.dataobject.station.HydrogenStationDO;
|
import cn.iocoder.yudao.module.asset.api.station.HydrogenStationApi;
|
||||||
import cn.iocoder.yudao.module.asset.service.station.HydrogenStationService;
|
import cn.iocoder.yudao.module.asset.api.station.dto.HydrogenStationRespDTO;
|
||||||
import cn.iocoder.yudao.module.energy.dal.dataobject.detail.EnergyHydrogenDetailDO;
|
import cn.iocoder.yudao.module.energy.dal.dataobject.detail.EnergyHydrogenDetailDO;
|
||||||
import cn.iocoder.yudao.module.energy.dal.dataobject.price.EnergyStationPriceDO;
|
import cn.iocoder.yudao.module.energy.dal.dataobject.price.EnergyStationPriceDO;
|
||||||
import cn.iocoder.yudao.module.energy.dal.dataobject.record.EnergyHydrogenRecordDO;
|
import cn.iocoder.yudao.module.energy.dal.dataobject.record.EnergyHydrogenRecordDO;
|
||||||
@@ -13,6 +13,7 @@ import cn.iocoder.yudao.module.energy.service.account.EnergyAccountService;
|
|||||||
import cn.iocoder.yudao.module.energy.service.detail.HydrogenDetailService;
|
import cn.iocoder.yudao.module.energy.service.detail.HydrogenDetailService;
|
||||||
import cn.iocoder.yudao.module.energy.service.match.HydrogenMatchService;
|
import cn.iocoder.yudao.module.energy.service.match.HydrogenMatchService;
|
||||||
import cn.iocoder.yudao.module.energy.service.match.dto.MatchResultDTO;
|
import cn.iocoder.yudao.module.energy.service.match.dto.MatchResultDTO;
|
||||||
|
import cn.iocoder.yudao.module.energy.service.match.vo.MatchResultVO;
|
||||||
import cn.iocoder.yudao.module.energy.service.price.EnergyStationPriceService;
|
import cn.iocoder.yudao.module.energy.service.price.EnergyStationPriceService;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
@@ -42,7 +43,7 @@ public class DetailEventListener {
|
|||||||
private EnergyHydrogenDetailMapper hydrogenDetailMapper;
|
private EnergyHydrogenDetailMapper hydrogenDetailMapper;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private HydrogenStationService hydrogenStationService;
|
private HydrogenStationApi hydrogenStationApi;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private EnergyStationPriceService stationPriceService;
|
private EnergyStationPriceService stationPriceService;
|
||||||
@@ -85,7 +86,7 @@ public class DetailEventListener {
|
|||||||
log.info("[onRecordImported] 生成明细完成,count={}", details.size());
|
log.info("[onRecordImported] 生成明细完成,count={}", details.size());
|
||||||
|
|
||||||
// 3. 检查站点配置,决定是否自动扣款
|
// 3. 检查站点配置,决定是否自动扣款
|
||||||
HydrogenStationDO station = hydrogenStationService.getHydrogenStation(event.getStationId());
|
HydrogenStationRespDTO station = hydrogenStationApi.getStation(event.getStationId());
|
||||||
if (station != null && Boolean.TRUE.equals(station.getAutoDeduct())) {
|
if (station != null && Boolean.TRUE.equals(station.getAutoDeduct())) {
|
||||||
log.info("[onRecordImported] 站点配置自动扣款,开始扣款流程");
|
log.info("[onRecordImported] 站点配置自动扣款,开始扣款流程");
|
||||||
for (EnergyHydrogenDetailDO detail : details) {
|
for (EnergyHydrogenDetailDO detail : details) {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
|||||||
import cn.iocoder.yudao.module.energy.controller.admin.detail.vo.HydrogenDetailPageReqVO;
|
import cn.iocoder.yudao.module.energy.controller.admin.detail.vo.HydrogenDetailPageReqVO;
|
||||||
import cn.iocoder.yudao.module.energy.controller.admin.detail.vo.HydrogenDetailSaveReqVO;
|
import cn.iocoder.yudao.module.energy.controller.admin.detail.vo.HydrogenDetailSaveReqVO;
|
||||||
import cn.iocoder.yudao.module.energy.dal.dataobject.detail.EnergyHydrogenDetailDO;
|
import cn.iocoder.yudao.module.energy.dal.dataobject.detail.EnergyHydrogenDetailDO;
|
||||||
|
import cn.iocoder.yudao.module.energy.service.detail.dto.BatchAuditResultDTO;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
public interface HydrogenDetailService {
|
public interface HydrogenDetailService {
|
||||||
@@ -11,7 +12,7 @@ public interface HydrogenDetailService {
|
|||||||
PageResult<EnergyHydrogenDetailDO> getDetailPage(HydrogenDetailPageReqVO pageReqVO);
|
PageResult<EnergyHydrogenDetailDO> getDetailPage(HydrogenDetailPageReqVO pageReqVO);
|
||||||
EnergyHydrogenDetailDO getDetail(Long id);
|
EnergyHydrogenDetailDO getDetail(Long id);
|
||||||
void audit(Long id, Boolean approved, String remark);
|
void audit(Long id, Boolean approved, String remark);
|
||||||
void batchAudit(List<Long> ids, Boolean approved, String remark);
|
BatchAuditResultDTO batchAudit(List<Long> ids, Boolean approved, String remark);
|
||||||
void updateDeductionStatus(Long detailId, Integer status);
|
void updateDeductionStatus(Long detailId, Integer status);
|
||||||
void updateBillId(List<Long> detailIds, Long billId);
|
void updateBillId(List<Long> detailIds, Long billId);
|
||||||
void updateSettlementStatus(List<Long> detailIds, Integer status);
|
void updateSettlementStatus(List<Long> detailIds, Integer status);
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package cn.iocoder.yudao.module.energy.service.detail;
|
package cn.iocoder.yudao.module.energy.service.detail;
|
||||||
|
|
||||||
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
import cn.iocoder.yudao.framework.common.pojo.PageResult;
|
||||||
|
import cn.iocoder.yudao.module.asset.api.station.HydrogenStationApi;
|
||||||
|
import cn.iocoder.yudao.module.asset.api.station.dto.HydrogenStationRespDTO;
|
||||||
import cn.iocoder.yudao.module.energy.controller.admin.detail.vo.HydrogenDetailPageReqVO;
|
import cn.iocoder.yudao.module.energy.controller.admin.detail.vo.HydrogenDetailPageReqVO;
|
||||||
import cn.iocoder.yudao.module.energy.controller.admin.detail.vo.HydrogenDetailSaveReqVO;
|
import cn.iocoder.yudao.module.energy.controller.admin.detail.vo.HydrogenDetailSaveReqVO;
|
||||||
import cn.iocoder.yudao.module.energy.dal.dataobject.detail.EnergyHydrogenDetailDO;
|
import cn.iocoder.yudao.module.energy.dal.dataobject.detail.EnergyHydrogenDetailDO;
|
||||||
@@ -13,6 +15,8 @@ import cn.iocoder.yudao.module.energy.enums.CostBearerEnum;
|
|||||||
import cn.iocoder.yudao.module.energy.enums.DeductionStatusEnum;
|
import cn.iocoder.yudao.module.energy.enums.DeductionStatusEnum;
|
||||||
import cn.iocoder.yudao.module.energy.enums.PayMethodEnum;
|
import cn.iocoder.yudao.module.energy.enums.PayMethodEnum;
|
||||||
import cn.iocoder.yudao.module.energy.event.DetailAuditPassedEvent;
|
import cn.iocoder.yudao.module.energy.event.DetailAuditPassedEvent;
|
||||||
|
import cn.iocoder.yudao.module.energy.service.account.EnergyAccountService;
|
||||||
|
import cn.iocoder.yudao.module.energy.service.detail.dto.BatchAuditResultDTO;
|
||||||
import cn.iocoder.yudao.module.energy.service.price.EnergyStationPriceService;
|
import cn.iocoder.yudao.module.energy.service.price.EnergyStationPriceService;
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -23,6 +27,8 @@ import org.springframework.transaction.annotation.Transactional;
|
|||||||
import org.springframework.validation.annotation.Validated;
|
import org.springframework.validation.annotation.Validated;
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.math.RoundingMode;
|
import java.math.RoundingMode;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
import static cn.iocoder.yudao.framework.common.exception.util.ServiceExceptionUtil.exception;
|
||||||
@@ -41,6 +47,10 @@ public class HydrogenDetailServiceImpl implements HydrogenDetailService {
|
|||||||
private EnergyStationPriceService stationPriceService;
|
private EnergyStationPriceService stationPriceService;
|
||||||
@Resource
|
@Resource
|
||||||
private ApplicationEventPublisher eventPublisher;
|
private ApplicationEventPublisher eventPublisher;
|
||||||
|
@Resource
|
||||||
|
private HydrogenStationApi hydrogenStationApi;
|
||||||
|
@Resource
|
||||||
|
private EnergyAccountService accountService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void updateDetail(HydrogenDetailSaveReqVO reqVO) {
|
public void updateDetail(HydrogenDetailSaveReqVO reqVO) {
|
||||||
@@ -72,30 +82,84 @@ public class HydrogenDetailServiceImpl implements HydrogenDetailService {
|
|||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void audit(Long id, Boolean approved, String remark) {
|
public void audit(Long id, Boolean approved, String remark) {
|
||||||
|
// 1. 查询明细
|
||||||
EnergyHydrogenDetailDO detail = validateDetailExists(id);
|
EnergyHydrogenDetailDO detail = validateDetailExists(id);
|
||||||
if (!AuditStatusEnum.PENDING.getStatus().equals(detail.getAuditStatus())) {
|
if (!AuditStatusEnum.PENDING.getStatus().equals(detail.getAuditStatus())) {
|
||||||
throw exception(HYDROGEN_DETAIL_ALREADY_AUDITED);
|
throw exception(HYDROGEN_DETAIL_ALREADY_AUDITED);
|
||||||
}
|
}
|
||||||
Integer newStatus = approved ? AuditStatusEnum.APPROVED.getStatus() : AuditStatusEnum.REJECTED.getStatus();
|
|
||||||
detailMapper.update(null, new LambdaUpdateWrapper<EnergyHydrogenDetailDO>()
|
|
||||||
.set(EnergyHydrogenDetailDO::getAuditStatus, newStatus)
|
|
||||||
.set(EnergyHydrogenDetailDO::getAuditRemark, remark)
|
|
||||||
.eq(EnergyHydrogenDetailDO::getId, id));
|
|
||||||
|
|
||||||
// If approved, publish event for deduction
|
// 2. 更新审核状态
|
||||||
|
detail.setAuditStatus(approved ? AuditStatusEnum.APPROVED.getStatus() : AuditStatusEnum.REJECTED.getStatus());
|
||||||
|
detail.setAuditRemark(remark);
|
||||||
|
detail.setAuditTime(LocalDateTime.now());
|
||||||
|
detailMapper.updateById(detail);
|
||||||
|
|
||||||
|
// 3. 审核通过 → 检查是否需要扣款
|
||||||
if (approved) {
|
if (approved) {
|
||||||
|
// 获取站点配置
|
||||||
|
HydrogenStationRespDTO station = hydrogenStationApi.getStation(detail.getStationId());
|
||||||
|
|
||||||
|
// 如果配置为审核后扣款(autoDeduct=false),则执行扣款
|
||||||
|
if (station != null && !Boolean.TRUE.equals(station.getAutoDeduct())) {
|
||||||
|
try {
|
||||||
|
accountService.deduct(
|
||||||
|
detail.getCustomerId(),
|
||||||
|
detail.getContractId(),
|
||||||
|
detail.getCustomerAmount(),
|
||||||
|
detail.getId(),
|
||||||
|
null,
|
||||||
|
"审核通过自动扣款"
|
||||||
|
);
|
||||||
|
|
||||||
|
// 更新扣款状态
|
||||||
|
detail.setDeductionStatus(DeductionStatusEnum.DEDUCTED.getStatus());
|
||||||
|
detail.setDeductionTime(LocalDateTime.now());
|
||||||
|
detailMapper.updateById(detail);
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[audit] 扣款失败,detailId={}, error={}", id, e.getMessage());
|
||||||
|
// 扣款失败不影响审核通过,记录到备注
|
||||||
|
detail.setAuditRemark(remark + "(扣款失败:" + e.getMessage() + ")");
|
||||||
|
detailMapper.updateById(detail);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 发布审核通过事件(预留给其他业务使用)
|
||||||
eventPublisher.publishEvent(new DetailAuditPassedEvent(
|
eventPublisher.publishEvent(new DetailAuditPassedEvent(
|
||||||
id, detail.getStationId(), detail.getCustomerId(),
|
detail.getId(),
|
||||||
detail.getContractId(), detail.getCustomerAmount()));
|
detail.getStationId(),
|
||||||
|
detail.getCustomerId(),
|
||||||
|
detail.getContractId(),
|
||||||
|
detail.getCustomerAmount()
|
||||||
|
));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void batchAudit(List<Long> ids, Boolean approved, String remark) {
|
public BatchAuditResultDTO batchAudit(List<Long> ids, Boolean approved, String remark) {
|
||||||
|
BatchAuditResultDTO result = new BatchAuditResultDTO();
|
||||||
|
result.setTotal(ids.size());
|
||||||
|
|
||||||
|
List<Long> successIds = new ArrayList<>();
|
||||||
|
List<Long> failIds = new ArrayList<>();
|
||||||
|
|
||||||
for (Long id : ids) {
|
for (Long id : ids) {
|
||||||
audit(id, approved, remark);
|
try {
|
||||||
|
audit(id, approved, remark);
|
||||||
|
successIds.add(id);
|
||||||
|
} catch (Exception e) {
|
||||||
|
log.error("[batchAudit] 审核失败,detailId={}, error={}", id, e.getMessage());
|
||||||
|
failIds.add(id);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
result.setSuccessCount(successIds.size());
|
||||||
|
result.setFailCount(failIds.size());
|
||||||
|
result.setSuccessIds(successIds);
|
||||||
|
result.setFailIds(failIds);
|
||||||
|
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
package cn.iocoder.yudao.module.energy.service.detail.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量审核结果 DTO
|
||||||
|
*
|
||||||
|
* @author 芋道源码
|
||||||
|
*/
|
||||||
|
@Data
|
||||||
|
@Schema(description = "批量审核结果")
|
||||||
|
public class BatchAuditResultDTO {
|
||||||
|
|
||||||
|
@Schema(description = "总数")
|
||||||
|
private Integer total;
|
||||||
|
|
||||||
|
@Schema(description = "成功数")
|
||||||
|
private Integer successCount;
|
||||||
|
|
||||||
|
@Schema(description = "失败数")
|
||||||
|
private Integer failCount;
|
||||||
|
|
||||||
|
@Schema(description = "成功的ID列表")
|
||||||
|
private List<Long> successIds = new ArrayList<>();
|
||||||
|
|
||||||
|
@Schema(description = "失败的ID列表")
|
||||||
|
private List<Long> failIds = new ArrayList<>();
|
||||||
|
|
||||||
|
}
|
||||||
@@ -1,12 +1,12 @@
|
|||||||
package cn.iocoder.yudao.module.energy.service.match;
|
package cn.iocoder.yudao.module.energy.service.match;
|
||||||
|
|
||||||
import cn.hutool.core.util.StrUtil;
|
import cn.hutool.core.util.StrUtil;
|
||||||
import cn.iocoder.yudao.module.asset.dal.dataobject.contract.ContractDO;
|
import cn.iocoder.yudao.module.asset.api.contract.ContractApi;
|
||||||
import cn.iocoder.yudao.module.asset.dal.dataobject.customer.CustomerDO;
|
import cn.iocoder.yudao.module.asset.api.contract.dto.ContractRespDTO;
|
||||||
import cn.iocoder.yudao.module.asset.dal.dataobject.vehicle.VehicleBaseDO;
|
import cn.iocoder.yudao.module.asset.api.customer.CustomerApi;
|
||||||
import cn.iocoder.yudao.module.asset.dal.mysql.contract.ContractMapper;
|
import cn.iocoder.yudao.module.asset.api.customer.dto.CustomerRespDTO;
|
||||||
import cn.iocoder.yudao.module.asset.dal.mysql.customer.CustomerMapper;
|
import cn.iocoder.yudao.module.asset.api.vehicle.VehicleApi;
|
||||||
import cn.iocoder.yudao.module.asset.dal.mysql.vehicle.VehicleBaseMapper;
|
import cn.iocoder.yudao.module.asset.api.vehicle.dto.VehicleRespDTO;
|
||||||
import cn.iocoder.yudao.module.energy.dal.dataobject.record.EnergyHydrogenRecordDO;
|
import cn.iocoder.yudao.module.energy.dal.dataobject.record.EnergyHydrogenRecordDO;
|
||||||
import cn.iocoder.yudao.module.energy.dal.mysql.record.EnergyHydrogenRecordMapper;
|
import cn.iocoder.yudao.module.energy.dal.mysql.record.EnergyHydrogenRecordMapper;
|
||||||
import cn.iocoder.yudao.module.energy.service.match.dto.MatchResultDTO;
|
import cn.iocoder.yudao.module.energy.service.match.dto.MatchResultDTO;
|
||||||
@@ -32,13 +32,13 @@ public class HydrogenMatchServiceImpl implements HydrogenMatchService {
|
|||||||
private EnergyHydrogenRecordMapper hydrogenRecordMapper;
|
private EnergyHydrogenRecordMapper hydrogenRecordMapper;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private VehicleBaseMapper vehicleBaseMapper;
|
private VehicleApi vehicleApi;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private CustomerMapper customerMapper;
|
private CustomerApi customerApi;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private ContractMapper contractMapper;
|
private ContractApi contractApi;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
@@ -90,11 +90,10 @@ public class HydrogenMatchServiceImpl implements HydrogenMatchService {
|
|||||||
|
|
||||||
// 1. 匹配车辆(通过车牌号)
|
// 1. 匹配车辆(通过车牌号)
|
||||||
if (StrUtil.isNotBlank(record.getPlateNumber())) {
|
if (StrUtil.isNotBlank(record.getPlateNumber())) {
|
||||||
VehicleBaseDO vehicle = vehicleBaseMapper.selectOne(
|
VehicleRespDTO vehicle = vehicleApi.getByVehicleNo(record.getPlateNumber());
|
||||||
VehicleBaseDO::getPlateNo, record.getPlateNumber()
|
|
||||||
);
|
|
||||||
if (vehicle != null) {
|
if (vehicle != null) {
|
||||||
result.setVehicleId(vehicle.getId());
|
result.setVehicleId(vehicle.getId());
|
||||||
|
result.setCustomerId(vehicle.getCustomerId());
|
||||||
log.debug("[matchRecord] 车辆匹配成功,plateNumber={}, vehicleId={}",
|
log.debug("[matchRecord] 车辆匹配成功,plateNumber={}, vehicleId={}",
|
||||||
record.getPlateNumber(), vehicle.getId());
|
record.getPlateNumber(), vehicle.getId());
|
||||||
}
|
}
|
||||||
@@ -105,11 +104,7 @@ public class HydrogenMatchServiceImpl implements HydrogenMatchService {
|
|||||||
if (result.getCustomerId() == null && StrUtil.isNotBlank(record.getPlateNumber())) {
|
if (result.getCustomerId() == null && StrUtil.isNotBlank(record.getPlateNumber())) {
|
||||||
// 注意:这里假设 plateNumber 字段可能包含客户信息,实际需要根据业务调整
|
// 注意:这里假设 plateNumber 字段可能包含客户信息,实际需要根据业务调整
|
||||||
// 如果 Record 有独立的 customerName 字段,应该使用那个字段
|
// 如果 Record 有独立的 customerName 字段,应该使用那个字段
|
||||||
CustomerDO customer = customerMapper.selectOne(
|
CustomerRespDTO customer = customerApi.getByNameLike(record.getPlateNumber());
|
||||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<CustomerDO>()
|
|
||||||
.like(CustomerDO::getCustomerName, record.getPlateNumber())
|
|
||||||
.last("LIMIT 1")
|
|
||||||
);
|
|
||||||
if (customer != null) {
|
if (customer != null) {
|
||||||
result.setCustomerId(customer.getId());
|
result.setCustomerId(customer.getId());
|
||||||
log.debug("[matchRecord] 客户匹配成功(通过名称),customerId={}", customer.getId());
|
log.debug("[matchRecord] 客户匹配成功(通过名称),customerId={}", customer.getId());
|
||||||
@@ -118,14 +113,7 @@ public class HydrogenMatchServiceImpl implements HydrogenMatchService {
|
|||||||
|
|
||||||
// 3. 匹配合同(通过客户ID)
|
// 3. 匹配合同(通过客户ID)
|
||||||
if (result.getCustomerId() != null) {
|
if (result.getCustomerId() != null) {
|
||||||
ContractDO contract = contractMapper.selectOne(
|
ContractRespDTO contract = contractApi.getActiveByCustomerId(result.getCustomerId());
|
||||||
new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<ContractDO>()
|
|
||||||
.eq(ContractDO::getCustomerId, result.getCustomerId())
|
|
||||||
.eq(ContractDO::getContractStatus, 2) // 2=进行中
|
|
||||||
.gt(ContractDO::getEndDate, record.getHydrogenDate())
|
|
||||||
.orderByDesc(ContractDO::getStartDate)
|
|
||||||
.last("LIMIT 1")
|
|
||||||
);
|
|
||||||
if (contract != null) {
|
if (contract != null) {
|
||||||
result.setContractId(contract.getId());
|
result.setContractId(contract.getId());
|
||||||
log.debug("[matchRecord] 合同匹配成功,contractId={}", contract.getId());
|
log.debug("[matchRecord] 合同匹配成功,contractId={}", contract.getId());
|
||||||
|
|||||||
Reference in New Issue
Block a user