|
|
@@ -10,6 +10,7 @@ import com.zsElectric.boot.business.mapper.PolicyFeeMapper;
|
|
|
import com.zsElectric.boot.business.mapper.ThirdPartyStationInfoMapper;
|
|
|
import com.zsElectric.boot.business.mapper.UserAccountMapper;
|
|
|
import com.zsElectric.boot.business.mapper.UserFirmMapper;
|
|
|
+import com.zsElectric.boot.system.mapper.DictItemMapper;
|
|
|
import com.zsElectric.boot.charging.entity.ThirdPartyChargeStatus;
|
|
|
import com.zsElectric.boot.charging.entity.ThirdPartyConnectorInfo;
|
|
|
import com.zsElectric.boot.charging.entity.ThirdPartyEquipmentInfo;
|
|
|
@@ -27,6 +28,7 @@ import com.zsElectric.boot.business.model.entity.DiscountsActivity;
|
|
|
import com.zsElectric.boot.business.model.entity.PolicyFee;
|
|
|
import com.zsElectric.boot.business.model.entity.UserAccount;
|
|
|
import com.zsElectric.boot.business.model.entity.UserFirm;
|
|
|
+import com.zsElectric.boot.system.model.entity.DictItem;
|
|
|
import com.zsElectric.boot.business.model.query.StationInfoQuery;
|
|
|
import com.zsElectric.boot.business.model.vo.AppletConnectorListVO;
|
|
|
import com.zsElectric.boot.business.model.vo.AppletStationDetailVO;
|
|
|
@@ -45,6 +47,7 @@ import lombok.extern.slf4j.Slf4j;
|
|
|
import org.springframework.stereotype.Service;
|
|
|
|
|
|
import java.math.BigDecimal;
|
|
|
+import java.math.RoundingMode;
|
|
|
import java.time.LocalTime;
|
|
|
import java.time.format.DateTimeFormatter;
|
|
|
import java.util.ArrayList;
|
|
|
@@ -71,6 +74,7 @@ public class AppletHomeServiceImpl implements AppletHomeService {
|
|
|
private final ThirdPartyChargeStatusMapper thirdPartyChargeStatusMapper;
|
|
|
private final ChargeOrderInfoMapper chargeOrderInfoMapper;
|
|
|
private final DiscountsActivityMapper discountsActivityMapper;
|
|
|
+ private final DictItemMapper dictItemMapper;
|
|
|
|
|
|
/**
|
|
|
* 时间格式化器 HHmmss
|
|
|
@@ -750,5 +754,225 @@ public class AppletHomeServiceImpl implements AppletHomeService {
|
|
|
return result;
|
|
|
}
|
|
|
|
|
|
+ @Override
|
|
|
+ public BigDecimal calculateAvailableChargingAmount(Long connectorId) {
|
|
|
+ // 获取当前登录用户ID
|
|
|
+ Long userId = SecurityUtils.getUserId();
|
|
|
+ if (userId == null) {
|
|
|
+ log.warn("未登录用户,无法计算可用充电金额");
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 1. 查询当前用户余额
|
|
|
+ UserAccount userAccount = userAccountMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<UserAccount>()
|
|
|
+ .eq(UserAccount::getUserId, userId)
|
|
|
+ .eq(UserAccount::getIsDeleted, 0)
|
|
|
+ );
|
|
|
+ if (userAccount == null || userAccount.getBalance() == null) {
|
|
|
+ log.warn("用户账户不存在或余额为空 - userId: {}", userId);
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+ BigDecimal userBalance = userAccount.getBalance();
|
|
|
+
|
|
|
+ // 2. 从字典表查询安全价(safety_fee)
|
|
|
+ DictItem safetyFeeItem = dictItemMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<DictItem>()
|
|
|
+ .eq(DictItem::getDictCode, "safety_fee")
|
|
|
+ .eq(DictItem::getStatus, 1)
|
|
|
+ .last("LIMIT 1")
|
|
|
+ );
|
|
|
+ BigDecimal safetyFee = safetyFeeItem != null && safetyFeeItem.getValue() != null
|
|
|
+ ? new BigDecimal(safetyFeeItem.getValue())
|
|
|
+ : BigDecimal.ZERO;
|
|
|
+
|
|
|
+ // 计算扣除安全价后的余额
|
|
|
+ BigDecimal balanceAfterSafety = userBalance.subtract(safetyFee);
|
|
|
+ if (balanceAfterSafety.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ log.info("用户余额不足 - userId: {}, balance: {}, safetyFee: {}", userId, userBalance, safetyFee);
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 3. 查询充电接口、设备、站点信息
|
|
|
+ ThirdPartyConnectorInfo connectorInfo = thirdPartyConnectorInfoMapper.selectById(connectorId);
|
|
|
+ if (connectorInfo == null) {
|
|
|
+ log.warn("充电接口不存在 - connectorId: {}", connectorId);
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+
|
|
|
+ ThirdPartyEquipmentInfo equipmentInfo = thirdPartyEquipmentInfoMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<ThirdPartyEquipmentInfo>()
|
|
|
+ .eq(ThirdPartyEquipmentInfo::getEquipmentId, connectorInfo.getEquipmentId())
|
|
|
+ .eq(ThirdPartyEquipmentInfo::getIsDeleted, 0)
|
|
|
+ .last("LIMIT 1")
|
|
|
+ );
|
|
|
+ if (equipmentInfo == null) {
|
|
|
+ log.warn("设备信息不存在 - equipmentId: {}", connectorInfo.getEquipmentId());
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+
|
|
|
+ ThirdPartyStationInfo stationInfo = thirdPartyStationInfoMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<ThirdPartyStationInfo>()
|
|
|
+ .eq(ThirdPartyStationInfo::getStationId, equipmentInfo.getStationId())
|
|
|
+ .eq(ThirdPartyStationInfo::getIsDeleted, 0)
|
|
|
+ .last("LIMIT 1")
|
|
|
+ );
|
|
|
+ if (stationInfo == null) {
|
|
|
+ log.warn("站点信息不存在 - stationId: {}", equipmentInfo.getStationId());
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 4. 查询价格策略
|
|
|
+ ThirdPartyEquipmentPricePolicy pricePolicy = thirdPartyEquipmentPricePolicyMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<ThirdPartyEquipmentPricePolicy>()
|
|
|
+ .eq(ThirdPartyEquipmentPricePolicy::getConnectorId, connectorInfo.getConnectorId())
|
|
|
+ .eq(ThirdPartyEquipmentPricePolicy::getIsDeleted, 0)
|
|
|
+ .last("LIMIT 1")
|
|
|
+ );
|
|
|
+ if (pricePolicy == null) {
|
|
|
+ log.warn("价格策略不存在 - connectorId: {}", connectorInfo.getConnectorId());
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 5. 查询所有时段的价格信息(按开始时间排序)
|
|
|
+ List<ThirdPartyPolicyInfo> policyInfoList = thirdPartyPolicyInfoMapper.selectList(
|
|
|
+ new LambdaQueryWrapper<ThirdPartyPolicyInfo>()
|
|
|
+ .eq(ThirdPartyPolicyInfo::getPricePolicyId, pricePolicy.getId())
|
|
|
+ .eq(ThirdPartyPolicyInfo::getIsDeleted, 0)
|
|
|
+ .orderByAsc(ThirdPartyPolicyInfo::getStartTime)
|
|
|
+ );
|
|
|
+ if (policyInfoList.isEmpty()) {
|
|
|
+ log.warn("价格策略明细不存在 - pricePolicyId: {}", pricePolicy.getId());
|
|
|
+ return BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 6. 查询用户是否为企业用户
|
|
|
+ UserFirm userFirm = userFirmMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<UserFirm>()
|
|
|
+ .eq(UserFirm::getUserId, userId)
|
|
|
+ .eq(UserFirm::getIsDeleted, 0)
|
|
|
+ .last("LIMIT 1")
|
|
|
+ );
|
|
|
+
|
|
|
+ // 7. 跨时段计算可用充电金额
|
|
|
+ // 公式:可用余额 = 当前用户余额 - 安全价 - [(度数 × 运营费) + (度数 × 增值费用)]
|
|
|
+ // 其中:度数 = (余额 - 安全价) / (电费 + 服务费)
|
|
|
+
|
|
|
+ BigDecimal remainingBalance = balanceAfterSafety; // 剩余可用余额(用于计算度数)
|
|
|
+ BigDecimal totalOpFeeCost = BigDecimal.ZERO; // 总运营费
|
|
|
+ BigDecimal totalValueAddedCost = BigDecimal.ZERO; // 总增值费用
|
|
|
+
|
|
|
+ // 遍历每个时段,计算在该时段能充多少度电及对应的费用
|
|
|
+ for (ThirdPartyPolicyInfo policyInfo : policyInfoList) {
|
|
|
+ // 获取电费、服务费
|
|
|
+ BigDecimal elecPrice = policyInfo.getElecPrice() != null ? policyInfo.getElecPrice() : BigDecimal.ZERO;
|
|
|
+ BigDecimal servicePrice = policyInfo.getServicePrice() != null ? policyInfo.getServicePrice() : BigDecimal.ZERO;
|
|
|
+ BigDecimal basePrice = elecPrice.add(servicePrice); // 基础价格 = 电费 + 服务费
|
|
|
+
|
|
|
+ if (basePrice.compareTo(BigDecimal.ZERO) == 0) {
|
|
|
+ continue; // 跳过基础价格为0的时段
|
|
|
+ }
|
|
|
+
|
|
|
+ // 查询该时段的运营费
|
|
|
+ PolicyFee policyFee;
|
|
|
+ if (userFirm != null) {
|
|
|
+ policyFee = policyFeeMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<PolicyFee>()
|
|
|
+ .eq(PolicyFee::getStationInfoId, stationInfo.getId())
|
|
|
+ .eq(PolicyFee::getStartTime, policyInfo.getStartTime())
|
|
|
+ .eq(PolicyFee::getSalesType, 1)
|
|
|
+ .eq(PolicyFee::getFirmId, userFirm.getFirmId())
|
|
|
+ .eq(PolicyFee::getIsDeleted, 0)
|
|
|
+ .last("LIMIT 1")
|
|
|
+ );
|
|
|
+ } else {
|
|
|
+ policyFee = policyFeeMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<PolicyFee>()
|
|
|
+ .eq(PolicyFee::getStationInfoId, stationInfo.getId())
|
|
|
+ .eq(PolicyFee::getStartTime, policyInfo.getStartTime())
|
|
|
+ .eq(PolicyFee::getSalesType, 0)
|
|
|
+ .eq(PolicyFee::getIsDeleted, 0)
|
|
|
+ .last("LIMIT 1")
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ BigDecimal opFee = (policyFee != null && policyFee.getOpFee() != null)
|
|
|
+ ? policyFee.getOpFee()
|
|
|
+ : BigDecimal.ZERO;
|
|
|
+
|
|
|
+ // 查询增值费用
|
|
|
+ BigDecimal valueAddedFee = BigDecimal.ZERO;
|
|
|
+ if (policyInfo.getPeriodFlag() != null) {
|
|
|
+ String periodLabel = "";
|
|
|
+ switch (policyInfo.getPeriodFlag()) {
|
|
|
+ case 1: periodLabel = "尖"; break;
|
|
|
+ case 2: periodLabel = "峰"; break;
|
|
|
+ case 3: periodLabel = "平"; break;
|
|
|
+ case 4: periodLabel = "谷"; break;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!periodLabel.isEmpty()) {
|
|
|
+ DictItem valueAddedItem = dictItemMapper.selectOne(
|
|
|
+ new LambdaQueryWrapper<DictItem>()
|
|
|
+ .eq(DictItem::getDictCode, "time_period_flag")
|
|
|
+ .eq(DictItem::getLabel, periodLabel)
|
|
|
+ .eq(DictItem::getStatus, 1)
|
|
|
+ .last("LIMIT 1")
|
|
|
+ );
|
|
|
+ if (valueAddedItem != null && valueAddedItem.getValue() != null) {
|
|
|
+ valueAddedFee = new BigDecimal(valueAddedItem.getValue());
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算在当前时段能充多少度电(使用剩余余额)
|
|
|
+ // 度数 = 剩余余额 / (电费 + 服务费)
|
|
|
+ BigDecimal kwh = remainingBalance.divide(basePrice, 4, RoundingMode.HALF_UP);
|
|
|
+
|
|
|
+ // 如果剩余余额不足以充电,结束计算
|
|
|
+ if (kwh.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 计算该时段的运营费和增值费用
|
|
|
+ BigDecimal periodOpFeeCost = kwh.multiply(opFee);
|
|
|
+ BigDecimal periodValueAddedCost = kwh.multiply(valueAddedFee);
|
|
|
+
|
|
|
+ totalOpFeeCost = totalOpFeeCost.add(periodOpFeeCost);
|
|
|
+ totalValueAddedCost = totalValueAddedCost.add(periodValueAddedCost);
|
|
|
+
|
|
|
+ // 更新剩余余额(减去当前时段的所有费用)
|
|
|
+ // 当前时段总费用 = (电费 + 服务费) * 度数 + 运营费 + 增值费用
|
|
|
+ BigDecimal periodBaseCost = kwh.multiply(basePrice);
|
|
|
+ BigDecimal periodTotalCost = periodBaseCost.add(periodOpFeeCost).add(periodValueAddedCost);
|
|
|
+ remainingBalance = remainingBalance.subtract(periodTotalCost);
|
|
|
+
|
|
|
+ log.debug("时段计算 - startTime: {}, 电费: {}, 服务费: {}, 运营费: {}, 增值费: {}, 度数: {}, 该时段费用: {}, 剩余余额: {}",
|
|
|
+ policyInfo.getStartTime(), elecPrice, servicePrice, opFee, valueAddedFee, kwh, periodTotalCost, remainingBalance);
|
|
|
+
|
|
|
+ // 如果剩余余额小于等于0,结束计算
|
|
|
+ if (remainingBalance.compareTo(BigDecimal.ZERO) <= 0) {
|
|
|
+ break;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ // 8. 计算最终可用余额
|
|
|
+ BigDecimal availableAmount = userBalance.subtract(safetyFee).subtract(totalOpFeeCost).subtract(totalValueAddedCost);
|
|
|
+
|
|
|
+ // 确保不为负数
|
|
|
+ if (availableAmount.compareTo(BigDecimal.ZERO) < 0) {
|
|
|
+ availableAmount = BigDecimal.ZERO;
|
|
|
+ }
|
|
|
+
|
|
|
+ // 保留 2 位小数
|
|
|
+ availableAmount = availableAmount.setScale(2, RoundingMode.HALF_UP);
|
|
|
+
|
|
|
+ log.info("可用充电金额计算完成 - userId: {}, connectorId: {}, userBalance: {}, safetyFee: {}, " +
|
|
|
+ "totalOpFeeCost: {}, totalValueAddedCost: {}, availableAmount: {}",
|
|
|
+ userId, connectorId, userBalance, safetyFee, totalOpFeeCost, totalValueAddedCost, availableAmount);
|
|
|
+
|
|
|
+ return availableAmount;
|
|
|
+ }
|
|
|
+
|
|
|
|
|
|
}
|