diff --git a/smart-switch-service/src/main/java/com/ruoyi/iot/util/IotUtil.java b/smart-switch-service/src/main/java/com/ruoyi/iot/util/IotUtil.java index 94caaff1..a1f0e230 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/iot/util/IotUtil.java +++ b/smart-switch-service/src/main/java/com/ruoyi/iot/util/IotUtil.java @@ -2,7 +2,9 @@ package com.ruoyi.iot.util; import cn.hutool.json.JSONObject; import com.ruoyi.common.exception.ServiceException; +import com.ruoyi.common.utils.StringUtils; import com.ruoyi.iot.domain.ObjBody; +import lombok.extern.slf4j.Slf4j; import org.apache.commons.codec.binary.Base64; import org.bouncycastle.jce.provider.BouncyCastleProvider; import org.slf4j.Logger; @@ -14,6 +16,8 @@ import javax.crypto.spec.SecretKeySpec; import java.io.UnsupportedEncodingException; import java.nio.charset.StandardCharsets; import java.security.*; +import java.util.HashMap; +import java.util.Map; /** @@ -22,6 +26,7 @@ import java.security.*; * Created by Roy on 2017/5/17. * */ +@Slf4j public class IotUtil { private static Logger logger = LoggerFactory.getLogger(IotUtil.class); @@ -142,4 +147,44 @@ public class IotUtil { len += (arrays[3] & 0xFF); return len; } + + /** + * 解析蓝牙设备状态字符串为Map + * @param statusStr 状态字符串 例如:V0.00@S0@A0.00@W0.00@P0.00@M0.00@T0.00@SET0@POW0 + * @return Map 解析结果 + */ + public static Map parseBltInfo(String statusStr) { + Map result = new HashMap<>(); + + if (StringUtils.isEmpty(statusStr)) { + return result; + } + + try { + // 按@分割字符串 + String[] items = statusStr.split("@"); + + for (String item : items) { + // 找到第一个数字或小数点的位置 + int splitIndex = 0; + for (int i = 0; i < item.length(); i++) { + char c = item.charAt(i); + if (Character.isDigit(c) || c == '.') { + splitIndex = i; + break; + } + } + + // 分割键值 + String key = item.substring(0, splitIndex); + String value = item.substring(splitIndex); + + result.put(key, value); + } + } catch (Exception e) { + log.error("解析状态字符串失败: {}", statusStr, e); + } + + return result; + } } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/device/domain/vo/DeviceVO.java b/smart-switch-service/src/main/java/com/ruoyi/ss/device/domain/vo/DeviceVO.java index e830ee42..60c878eb 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/device/domain/vo/DeviceVO.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/device/domain/vo/DeviceVO.java @@ -7,6 +7,7 @@ import com.ruoyi.iot.interfaces.IotDevice; import com.ruoyi.ss.bonus.domain.Bonus; import com.ruoyi.ss.device.domain.Device; import com.ruoyi.ss.device.domain.DeviceView; +import com.ruoyi.ss.device.utils.DeviceUtils; import com.ruoyi.ss.suit.domain.SuitVO; import io.swagger.annotations.ApiModelProperty; import lombok.Data; @@ -126,13 +127,7 @@ public class DeviceVO extends Device implements IotDevice { // 获取数据库剩余电量(度) public BigDecimal getSurplusEleDb() { - if (getExpireEle() == null) { - return BigDecimal.ZERO; - } - if (getTotalElectriQuantity() == null) { - return getExpireEle(); - } - return getExpireEle().subtract(getTotalElectriQuantity()); + return DeviceUtils.getSyncEle(this.getExpireEle(), this.getTotalElectriQuantity()); } // 获取数据库剩余时长(秒) diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/device/service/DeviceService.java b/smart-switch-service/src/main/java/com/ruoyi/ss/device/service/DeviceService.java index 3091f13a..bfce4e02 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/device/service/DeviceService.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/device/service/DeviceService.java @@ -433,4 +433,9 @@ public interface DeviceService CommandResponse syncEle(Long deviceId, String reason); void monitor(List deviceIds); + + /** + * 蓝牙同步设备信息 + */ + int bltSyncIot(String sn, String info); } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/device/service/impl/DeviceServiceImpl.java b/smart-switch-service/src/main/java/com/ruoyi/ss/device/service/impl/DeviceServiceImpl.java index 22073f4c..14d1f918 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/device/service/impl/DeviceServiceImpl.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/device/service/impl/DeviceServiceImpl.java @@ -16,6 +16,7 @@ import com.ruoyi.iot.domain.IotDeviceInfo; import com.ruoyi.iot.domain.response.CommandResponse; import com.ruoyi.iot.enums.IotHttpStatus; import com.ruoyi.iot.service.IotService; +import com.ruoyi.iot.util.IotUtil; import com.ruoyi.ss.commandLog.service.ICommandLogService; import com.ruoyi.ss.device.domain.Device; import com.ruoyi.ss.device.domain.DeviceBO; @@ -31,6 +32,7 @@ import com.ruoyi.ss.device.mapper.DeviceMapper; import com.ruoyi.ss.device.service.DeviceAssembler; import com.ruoyi.ss.device.service.DeviceService; import com.ruoyi.ss.device.service.DeviceValidator; +import com.ruoyi.ss.device.utils.DeviceUtils; import com.ruoyi.ss.deviceBindRecord.domain.enums.BindRecordType; import com.ruoyi.ss.deviceBindRecord.domain.enums.BindRecordUserType; import com.ruoyi.ss.deviceBindRecord.service.DeviceBindRecordService; @@ -1285,13 +1287,14 @@ public class DeviceServiceImpl implements DeviceService if (deviceId == null) { return null; } - if (now == null) { - now = LocalDateTime.now(); - } DeviceVO device = selectById(deviceId); - long betweenSeconds = Duration.between(now, device.getExpireTime()).getSeconds(); - if (betweenSeconds > 0) { - return iotService.setTime(device, betweenSeconds, reason); + if (device == null) { + return null; + } + + long syncSeconds = DeviceUtils.getSyncSeconds(device.getExpireTime(), now); + if (syncSeconds > 0) { + return iotService.setTime(device, syncSeconds, reason); } return null; } @@ -1365,6 +1368,33 @@ public class DeviceServiceImpl implements DeviceService } } + @Override + public int bltSyncIot(String sn, String info) { + if (StringUtils.isAnyBlank(sn, info)) { + return 0; + } + // 解析蓝牙信息 + Map map = IotUtil.parseBltInfo(info); + + // 查询设备 + DeviceVO device = selectByDeviceNo(sn); + ServiceUtil.assertion(device == null, "设备不存在"); + + // 更新数据库总用电量 + String w = map.get("W"); + if (StringUtils.hasText(w)) { + BigDecimal totalEle = new BigDecimal(w); + if (device.getTotalElectriQuantity() == null || totalEle.compareTo(device.getTotalElectriQuantity()) > 0) { + Device data = new Device(); + data.setDeviceId(device.getDeviceId()); + data.setTotalElectriQuantity(totalEle); + return updateByIot(data); + } + } + + return 1; + } + private List selectIds(DeviceQuery query) { return deviceMapper.selectIds(query); } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/device/utils/DeviceUtils.java b/smart-switch-service/src/main/java/com/ruoyi/ss/device/utils/DeviceUtils.java new file mode 100644 index 00000000..30effabe --- /dev/null +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/device/utils/DeviceUtils.java @@ -0,0 +1,36 @@ +package com.ruoyi.ss.device.utils; + +import java.math.BigDecimal; +import java.time.Duration; +import java.time.LocalDateTime; + +/** + * @author wjh + * 2025/1/3 + */ +public class DeviceUtils { + + + public static long getSyncSeconds(LocalDateTime expireTime, LocalDateTime now) { + if (expireTime == null) { + return 0L; + } + if (now == null) { + now = LocalDateTime.now(); + } + return Duration.between(now, expireTime).getSeconds(); + } + + public static BigDecimal getSyncEle(BigDecimal expireEle, BigDecimal totalEle) { + if (expireEle == null) { + return BigDecimal.ZERO; + } + if (totalEle == null) { + return expireEle; + } + if (totalEle.compareTo(expireEle) >= 0) { + return BigDecimal.ZERO; + } + return expireEle.subtract(totalEle); + } +} diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/suit/domain/enums/SuitFeeType.java b/smart-switch-service/src/main/java/com/ruoyi/ss/suit/domain/enums/SuitFeeType.java index 203803c8..f7e435bd 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/suit/domain/enums/SuitFeeType.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/suit/domain/enums/SuitFeeType.java @@ -65,7 +65,7 @@ public enum SuitFeeType { /** * 充值电量的类型 */ - public static List rechargeCountList() { + public static List rechargeEleList() { return asList(COUNT, TIMING_COUNT); } } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/suit/service/impl/SuitValidatorImpl.java b/smart-switch-service/src/main/java/com/ruoyi/ss/suit/service/impl/SuitValidatorImpl.java index f0ce84fd..298dc16c 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/suit/service/impl/SuitValidatorImpl.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/suit/service/impl/SuitValidatorImpl.java @@ -150,8 +150,15 @@ public class SuitValidatorImpl extends BaseValidator implements SuitValidator { SuitService suitService = SpringUtils.getBean(SuitService.class); SuitVO old = suitService.selectSuitBySuitId(suit.getSuitId()); + // 判断金额是否合理 + if (SuitFeeType.timingList().contains(suit.getFeeType())) { + for (BigDecimal amount : suit.getGearAmount()) { + ServiceUtil.assertion(amount == null || amount.compareTo(new BigDecimal("0.01")) < 0, "档位金额不允许小于0.01元"); + } + } + // 判断设备是否支持电量 - if (SuitFeeType.rechargeCountList().contains(suit.getFeeType()) && CollectionUtils.isNotEmptyElement(suit.getDeviceIds())) { + if (SuitFeeType.rechargeEleList().contains(suit.getFeeType()) && CollectionUtils.isNotEmptyElement(suit.getDeviceIds())) { List deviceList = deviceService.selectByIds(new ArrayList<>(suit.getDeviceIds())); List unSupport = new ArrayList<>(); for (DeviceVO device : deviceList) { @@ -201,7 +208,7 @@ public class SuitValidatorImpl extends BaseValidator implements SuitValidator { } // 若开启最低功率阈值,则需设置最低功率 - if (SuitFeeType.rechargeCountList().contains(feeType)) { + if (SuitFeeType.rechargeEleList().contains(feeType)) { ServiceUtil.assertion (vo.getEnabledLowPowerClose() != null && vo.getEnabledLowPowerClose() && vo.getLowPower() == null, "最低功率阈值不允许为空"); } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/domain/vo/TransactionBillVO.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/domain/vo/TransactionBillVO.java index a81839b3..aad0bb77 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/domain/vo/TransactionBillVO.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/domain/vo/TransactionBillVO.java @@ -1,11 +1,13 @@ package com.ruoyi.ss.transactionBill.domain.vo; +import com.fasterxml.jackson.annotation.JsonIgnore; import com.fasterxml.jackson.annotation.JsonView; import com.ruoyi.common.annotation.Sensitive; import com.ruoyi.common.core.domain.JsonViewProfile; import com.ruoyi.common.enums.DesensitizedType; import com.ruoyi.iot.interfaces.IotDevice; import com.ruoyi.ss.bonus.domain.BonusVO; +import com.ruoyi.ss.device.utils.DeviceUtils; import com.ruoyi.ss.transactionBill.domain.TransactionBill; import com.ruoyi.ss.transactionBill.utils.RechargeUtils; import io.swagger.annotations.ApiModel; @@ -45,6 +47,10 @@ public class TransactionBillVO extends TransactionBill implements IotDevice { @JsonView(JsonViewProfile.App.class) private BigDecimal deviceTotalEle; + @ApiModelProperty("设备结束电量") + @JsonView(JsonViewProfile.App.class) + private BigDecimal deviceExpireEle; + @ApiModelProperty("设备结束时间") @JsonView(JsonViewProfile.App.class) private LocalDateTime deviceExpireTime; @@ -71,6 +77,12 @@ public class TransactionBillVO extends TransactionBill implements IotDevice { @JsonView(JsonViewProfile.App.class) private TransactionBillOperatorVO operator; + private BigDecimal rechargeEle; + + private Long rechargeSeconds; + + private Long suitSeconds; + @Override public String iotMac1() { return getDeviceMac(); @@ -86,24 +98,52 @@ public class TransactionBillVO extends TransactionBill implements IotDevice { return getDeviceProductId(); } + /** + * 获取同步给设备需要的时长(秒) + */ + @JsonView(JsonViewProfile.App.class) + public long getSyncDeviceSeconds() { + return DeviceUtils.getSyncSeconds(deviceExpireTime, LocalDateTime.now()); + } + + /** + * 获取给设备同步需要的电量(度) + */ + @JsonView(JsonViewProfile.App.class) + public BigDecimal getSyncDeviceEle() { + return DeviceUtils.getSyncEle(deviceExpireEle, deviceTotalEle); + } + /** * 获取充值的电量(度) */ + @JsonIgnore public BigDecimal getRechargeEle() { - return RechargeUtils.toRechargeEle(this); + if (this.rechargeEle == null) { + this.rechargeEle = RechargeUtils.toRechargeEle(this); + } + return this.rechargeEle; } /** * 获取充值的时长(秒) */ + @JsonIgnore public long getRechargeSeconds() { - return RechargeUtils.toRechargeSeconds(this); + if (this.rechargeSeconds == null) { + this.rechargeSeconds = RechargeUtils.toRechargeSeconds(this); + } + return rechargeSeconds; } /** * 获取套餐时长(秒) */ + @JsonIgnore public long getSuitSeconds() { - return RechargeUtils.toSecondSuitTime(this); + if (this.suitSeconds == null) { + this.suitSeconds = RechargeUtils.toSecondSuitTime(this); + } + return this.suitSeconds; } } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/mapper/TransactionBillMapper.xml b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/mapper/TransactionBillMapper.xml index 0664c21a..ef17cad0 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/mapper/TransactionBillMapper.xml +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/mapper/TransactionBillMapper.xml @@ -95,6 +95,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" sd.device_no as device_no, sd.total_electri_quantity as device_total_ele, sd.expire_time as device_expire_time, + sd.expire_ele as decvice_expire_ele, su.phonenumber as user_mobile from diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/RechargePayHandler.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/RechargePayHandler.java index 8cddeffc..1e1132c4 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/RechargePayHandler.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/RechargePayHandler.java @@ -80,11 +80,7 @@ public class RechargePayHandler implements AfterPay, AfterRefund { // 拼接分成列表 transactionAssembler.assembleBonusList(bill); - if (SuitFeeType.singleList().contains(bill.getSuitFeeType())) { - return this.handleSinglePaySuccess(bo); - } else { - return this.handleTimingPaySuccess(bo); - } + return this.handleSinglePaySuccess(bo); } /** @@ -129,13 +125,13 @@ public class RechargePayHandler implements AfterPay, AfterRefund { BigDecimal startEle = bill.getDeviceTotalEle(); data.setSuitStartTime(startTime); data.setSuitStartEle(startEle); - if (SuitFeeType.TIME.getType().equals(bill.getSuitFeeType())) { + if (SuitFeeType.rechargeTimeList().contains(bill.getSuitFeeType())) { // 充值时长,计算结束时间 LocalDateTime endTime = startTime.plusSeconds(bill.getRechargeSeconds()); data.setSuitEndTime(endTime); data.setSuitExpireTime(endTime); } - else if (SuitFeeType.COUNT.getType().equals(bill.getSuitFeeType())) { + else if (SuitFeeType.rechargeEleList().contains(bill.getSuitFeeType())) { // 充值电量,计算结束电量 BigDecimal endEle = startEle.add(bill.getRechargeEle()); data.setSuitEndEle(endEle); @@ -154,10 +150,10 @@ public class RechargePayHandler implements AfterPay, AfterRefund { ServiceUtil.assertion(received != 1, "商户获取用户手机号扣款失败"); // 修改设备数据库余额 - if (SuitFeeType.TIME.getType().equals(bill.getSuitFeeType())) { + if (SuitFeeType.rechargeTimeList().contains(bill.getSuitFeeType())) { int addDb = deviceService.addTimeDb(bill.getDeviceId(), bill.getRechargeSeconds()); ServiceUtil.assertion(addDb != 1, "设备数据库余额增加失败"); - } else if (SuitFeeType.COUNT.getType().equals(bill.getSuitFeeType())) { + } else if (SuitFeeType.rechargeEleList().contains(bill.getSuitFeeType())) { int addDb = deviceService.addEleDb(bill.getDeviceId(), bill.getRechargeEle()); ServiceUtil.assertion(addDb != 1, "设备数据库余额增加失败"); } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/TransactionAssemblerImpl.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/TransactionAssemblerImpl.java index b6e6ee6b..fb42e3cc 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/TransactionAssemblerImpl.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/TransactionAssemblerImpl.java @@ -86,7 +86,7 @@ public class TransactionAssemblerImpl implements TransactionAssembler { } } // 充值时长 - else if (SuitFeeType.rechargeCountList().contains(bill.getSuitFeeType())) { + else if (SuitFeeType.rechargeEleList().contains(bill.getSuitFeeType())) { BigDecimal endEle = bill.getSuitEndEle() == null ? bill.getDeviceTotalEle() : bill.getSuitEndEle(); if (endEle == null) { bill.setSuitSurplus(BigDecimal.ZERO); @@ -141,7 +141,7 @@ public class TransactionAssemblerImpl implements TransactionAssembler { if (SuitFeeType.rechargeTimeList().contains(bill.getSuitFeeType()) && bill.getSuitStartTime() != null && bill.getSuitEndTime() != null) { Duration between = Duration.between(bill.getSuitStartTime(), bill.getSuitEndTime()); bill.setTotalUse(new BigDecimal(between.getSeconds())); - } else if (SuitFeeType.rechargeCountList().contains(bill.getSuitFeeType()) && bill.getSuitStartEle() != null && bill.getSuitEndEle() != null) { + } else if (SuitFeeType.rechargeEleList().contains(bill.getSuitFeeType()) && bill.getSuitStartEle() != null && bill.getSuitEndEle() != null) { bill.setTotalUse(bill.getSuitEndEle().subtract(bill.getSuitStartEle())); } } diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/TransactionBillServiceImpl.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/TransactionBillServiceImpl.java index bf368c4c..94f11fd1 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/TransactionBillServiceImpl.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/TransactionBillServiceImpl.java @@ -454,23 +454,16 @@ public class TransactionBillServiceImpl implements TransactionBillService, After order.setVersion(2); // 订单版本 order.setCloseStatus(RechargeCloseStatus.SUCCESS.getStatus()); order.setCloseResult("预计成功"); + order.setStatus(TransactionBillStatus.UNPAID.getStatus()); // 状态:未支付 - if (SuitFeeType.timingList().contains(suit.getFeeType())) { - // 当为分时段收费,状态为未支付押金 - order.setSuitPrice(dto.getSuitDeposit()); + // 订单金额,若为智能收费,则收取押金,否则收取本金 + if (SuitFeeMode.SMART.getMode().equals(suit.getFeeMode())) { + // 智能收费,收取押金 + order.setMoney(dto.getSuitDeposit()); order.setSuitDeposit(dto.getSuitDeposit()); - order.setStatus(TransactionBillStatus.UNPAID_DEPOSIT.getStatus()); // 未支付押金 } else { - // 否则为未支付 - if (SuitFeeMode.SMART.getMode().equals(suit.getFeeMode())) { - // 智能收费,收取押金 - order.setMoney(dto.getSuitDeposit()); - order.setSuitDeposit(dto.getSuitDeposit()); - } else { - // 单次收费,收取本金 - order.setMoney(dto.getMoney()); - } - order.setStatus(TransactionBillStatus.UNPAID.getStatus()); // 状态:未支付 + // 单次收费,收取本金 + order.setMoney(dto.getMoney()); } // 用户信息 @@ -879,9 +872,9 @@ public class TransactionBillServiceImpl implements TransactionBillService, After Boolean iotResult = transactionTemplate.execute(status -> { // 同步设备余额 CommandResponse res = null; - if (SuitFeeType.TIME.getType().equals(bill.getSuitFeeType())) { + if (SuitFeeType.rechargeTimeList().contains(bill.getSuitFeeType())) { res = deviceService.syncTime(bill.getDeviceId(), LocalDateTime.now(), "充值订单:" + bill.getBillNo()); - } else if (SuitFeeType.COUNT.getType().equals(bill.getSuitFeeType())) { + } else if (SuitFeeType.rechargeEleList().contains(bill.getSuitFeeType())) { res = deviceService.syncEle(bill.getDeviceId(), "充值订单:" + bill.getBillNo()); } @@ -1072,14 +1065,15 @@ public class TransactionBillServiceImpl implements TransactionBillService, After return totalAmount; } - private int endSmartUse(EndUseBO bo) { + + @Override + public int endUse(EndUseBO bo) { if (bo == null) { return 0; } TransactionBillVO order = bo.getOrder(); DeviceVO device = bo.getDevice(); ServiceUtil.assertion(order == null, "订单不存在"); - ServiceUtil.assertion(!SuitFeeMode.SMART.getMode().equals(order.getSuitFeeMode()), "当前订单非智能收费订单,无法提前结束"); ServiceUtil.assertion(order.getIsFinished() != null && order.getIsFinished(), "当前订单已结束,无法操作"); ServiceUtil.assertion(!TransactionBillStatus.SUCCESS.getStatus().equals(order.getStatus()), "当前订单状态不允许结束"); ServiceUtil.assertion(device == null, "设备不存在"); @@ -1109,8 +1103,8 @@ public class TransactionBillServiceImpl implements TransactionBillService, After boolean success = result != null && result == 1; // 是否成功 - if (success) { - // 计算需要退款的金额,若金额 > 0.01 则申请退款 + // 若成功,且为智能模式,则计算需要退款的金额, 若金额 > 0.01 则申请退款 + if ( success && SuitFeeMode.SMART.getMode().equals(order.getSuitFeeMode())) { BigDecimal refundAmount = this.calcRefundAmount(order, endTime, totalEle); if (BigDecimal.valueOf(0.01).compareTo(refundAmount) < 0) { scheduledExecutorService.schedule(() -> { @@ -1139,10 +1133,13 @@ public class TransactionBillServiceImpl implements TransactionBillService, After @Override public BigDecimal calcAmount(Long billId, LocalDateTime endTime, BigDecimal totalEle) { - TransactionBillVO bill = selectSmTransactionBillByBillId(billId); ServiceUtil.assertion(bill == null, "计算金额出错,订单不存在"); + return this.calcAmount(bill, endTime, totalEle); + } + + private BigDecimal calcAmount(TransactionBillVO bill, LocalDateTime endTime, BigDecimal totalEle) { // 计量收费,则获取最新的设备信息 if(SuitFeeType.COUNT.getType().equals(bill.getSuitFeeType()) || SuitFeeType.TIMING_COUNT.getType().equals(bill.getSuitFeeType())) { DeviceVO device = deviceService.selectById(bill.getDeviceId()); @@ -1209,17 +1206,19 @@ public class TransactionBillServiceImpl implements TransactionBillService, After * 计算退款金额 */ private BigDecimal calcRefundAmount(TransactionBillVO order, LocalDateTime endTime, BigDecimal totalEle) { - BigDecimal refund = order.getMoney().subtract(this.calcSmartAmount(order, endTime, totalEle)); + + BigDecimal needAmount = this.calcAmount(order, endTime, totalEle); // 需要的金额 + BigDecimal refundAmount = order.getMoney().subtract(needAmount); // 退款金额 // 退款金额不允许大于订单金额 - if (refund.compareTo(order.getMoney()) > 0) { + if (refundAmount.compareTo(order.getMoney()) > 0) { return order.getMoney(); } // 退款金额不允许小于0 - if (refund.compareTo(BigDecimal.ZERO) < 0) { + if (refundAmount.compareTo(BigDecimal.ZERO) < 0) { return BigDecimal.ZERO; } - return refund; + return refundAmount; } /** @@ -1782,31 +1781,31 @@ public class TransactionBillServiceImpl implements TransactionBillService, After } // 结束使用订单 - @Override - public int endUse(EndUseBO bo) { - if (bo == null) { - return 0; - } - TransactionBillVO bill = bo.getOrder(); +// @Override +// public int endUse(EndUseBO bo) { +// if (bo == null) { +// return 0; +// } +// +// return this.endSmartUse(bo); // 智能订单 - if (SuitFeeMode.SMART.getMode().equals(bill.getSuitFeeMode())) { - // 单次智能 - if (SuitFeeType.singleList().contains(bill.getSuitFeeType())) { - return this.endSmartUse(bo); - } - // 分时段智能 - else if (SuitFeeType.timingList().contains(bill.getSuitFeeType())){ - return this.endTimingUse(bo); - } - } - // 单次订单 - else if (SuitFeeMode.SINGLE.getMode().equals(bill.getSuitFeeMode())) { - return this.endUseSingle(bo); - } - - return 0; - } +// if (SuitFeeMode.SMART.getMode().equals(bill.getSuitFeeMode())) { +// // 单次智能 +//// if (SuitFeeType.singleList().contains(bill.getSuitFeeType())) { +//// } +//// // 分时段智能 +//// else if (SuitFeeType.timingList().contains(bill.getSuitFeeType())){ +//// return this.endTimingUse(bo); +//// } +// } +// // 单次订单 +// else if (SuitFeeMode.SINGLE.getMode().equals(bill.getSuitFeeMode())) { +// return this.endUseSingle(bo); +// } +// +// return 0; +// } @Override public List> selectCommonSumOfMoney(TransactionBillQuery query, String groupBy) { diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/TransactionBillValidatorImpl.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/TransactionBillValidatorImpl.java index 4299d58a..977d0d1d 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/TransactionBillValidatorImpl.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/service/impl/TransactionBillValidatorImpl.java @@ -93,14 +93,6 @@ public class TransactionBillValidatorImpl extends BaseValidator implements Trans // 用户 SmUserVO user = bo.getUser(); - // 检查用户是否有未支付的智能分时段订单 - if (this.hasUnpaidSmartTimingOrder(user.getUserId())) { - return error("您有未支付的订单,请先支付后继续"); - } - // 检查用户是否有正在使用中的智能订单 -// if (this.hasUsingSmartOrder(user.getUserId())) { -// return error("您有正在使用中的智能订单,请结束后继续"); -// } // 检查设备是否符合条件 DeviceVO device = bo.getDevice(); diff --git a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/utils/RechargeUtils.java b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/utils/RechargeUtils.java index a55d8d60..ff639a3c 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/utils/RechargeUtils.java +++ b/smart-switch-service/src/main/java/com/ruoyi/ss/transactionBill/utils/RechargeUtils.java @@ -7,6 +7,9 @@ import com.ruoyi.ss.transactionBill.domain.vo.TransactionBillVO; import java.math.BigDecimal; import java.math.RoundingMode; +import java.time.LocalDateTime; +import java.util.List; +import java.util.stream.Collectors; /** * @author wjh @@ -21,17 +24,101 @@ public class RechargeUtils { if (bill == null) { return BigDecimal.ZERO; } - // 计算周期数 - long round = calcRound(bill); - return BigDecimal.valueOf(bill.getSuitTime() * round); + // 普通充值 + if (SuitFeeType.singleList().contains(bill.getSuitFeeType())) { + // 计算周期数 + long round = calcRound(bill); + return BigDecimal.valueOf(bill.getSuitTime() * round); + } + // 分时段充值 + else { + // 按照应用的最低电价来计算电量 + List indexes = bill.getSuitGearTime().stream().distinct().collect(Collectors.toList()); + BigDecimal minAmount = null; + for (Integer index : indexes) { + BigDecimal amount = bill.getSuitGearAmount().get(index); + if (minAmount == null || amount.compareTo(minAmount) < 0) { + minAmount = amount; + } + } + // 若没有配置则默认0.01元 + if (minAmount == null || minAmount.compareTo(BigDecimal.ZERO) <= 0) { + minAmount = new BigDecimal("0.01"); + } + + return bill.getMoney().divide(minAmount, 2, RoundingMode.HALF_UP); + } } /** * 转换为充值秒 */ public static long toRechargeSeconds(TransactionBillVO bill) { - long round = calcRound(bill); - return toSecondSuitTime(bill) * round; + if (bill == null) { + return 0L; + } + // 普通充值 + if (SuitFeeType.singleList().contains(bill.getSuitFeeType())) { + long round = calcRound(bill); + return toSecondSuitTime(bill) * round; + } + // 分时段充值 + else { + return calcUsageSeconds(bill.getMoney(), LocalDateTime.now(), bill.getSuitGearAmount(), bill.getSuitGearTime()); + } + } + + /** + * 根据金额计算可使用时长(秒) + * @param amount 金额 + * @param startTime 开始时间 + * @param suitGearAmount 时段金额列表 + * @param suitGearTime 时段配置列表 + * @return 可使用的时长(秒) + */ + private static long calcUsageSeconds(BigDecimal amount, LocalDateTime startTime, List suitGearAmount, List suitGearTime) { + + // 剩余金额 + BigDecimal remainAmount = amount; + // 累计秒数 + long totalSeconds = 0; + // 初始时间 + LocalDateTime currentTime = startTime.withMinute(0).withSecond(0); + + BigDecimal minAmount = new BigDecimal("0.01"); + + while (remainAmount.compareTo(BigDecimal.ZERO) > 0) { + // 当前小时的每分钟费用 + BigDecimal minuteAmount = suitGearAmount.get(suitGearTime.get(currentTime.getHour())) + .divide(BigDecimal.valueOf(60), 2, RoundingMode.HALF_UP); + + // 如果是开始的第一个小时,需要计算实际开始时间的分钟数 + int availableMinutes = 60; + if (currentTime.equals(startTime.withMinute(0).withSecond(0))) { + availableMinutes = 60 - startTime.getMinute(); + } + + // 当前时段最多可以使用的金额,最低不允许小于0.01元 + BigDecimal maxAmount = minuteAmount.multiply(BigDecimal.valueOf(availableMinutes)); + if (maxAmount.compareTo(minAmount) < 0) { + maxAmount = minAmount; + } + + if (remainAmount.compareTo(maxAmount) >= 0) { + // 如果剩余金额足够支付整个时段 + totalSeconds += availableMinutes * 60; + remainAmount = remainAmount.subtract(maxAmount); + } else { + // 如果剩余金额不足支付整个时段,计算可用分钟数 + int minutes = remainAmount.divide(minuteAmount, 0, RoundingMode.FLOOR).intValue(); + totalSeconds += minutes * 60L; + remainAmount = BigDecimal.ZERO; + } + + currentTime = currentTime.plusHours(1); + } + + return totalSeconds; } /** diff --git a/smart-switch-service/src/main/java/com/ruoyi/task/bill/BillMonitorTask.java b/smart-switch-service/src/main/java/com/ruoyi/task/bill/BillMonitorTask.java index 13837f71..65331bd3 100644 --- a/smart-switch-service/src/main/java/com/ruoyi/task/bill/BillMonitorTask.java +++ b/smart-switch-service/src/main/java/com/ruoyi/task/bill/BillMonitorTask.java @@ -8,7 +8,6 @@ import com.ruoyi.ss.device.domain.enums.DeviceOnlineStatus; import com.ruoyi.ss.device.service.DeviceService; import com.ruoyi.ss.suit.domain.enums.SuitFeeType; import com.ruoyi.ss.transactionBill.domain.TransactionBillQuery; -import com.ruoyi.ss.transactionBill.domain.dto.EndUseDTO; import com.ruoyi.ss.transactionBill.domain.enums.TransactionBillStatus; import com.ruoyi.ss.transactionBill.domain.enums.TransactionBillType; import com.ruoyi.ss.transactionBill.domain.vo.TransactionBillVO; @@ -59,7 +58,7 @@ public class BillMonitorTask { query.setStatusList(TransactionBillStatus.canClose()); query.setCreateTimeEnd(createTimeEnd); query.setIsFinished(false); - query.setSuitFeeTypes(SuitFeeType.rechargeCountList()); + query.setSuitFeeTypes(SuitFeeType.rechargeEleList()); List list = transactionBillService.selectSmTransactionBillList(query); // 从OneNet获取设备信息 diff --git a/smart-switch-web/src/main/java/com/ruoyi/web/controller/app/AppDeviceAdminController.java b/smart-switch-web/src/main/java/com/ruoyi/web/controller/app/AppDeviceAdminController.java index ec4d6fae..6af07019 100644 --- a/smart-switch-web/src/main/java/com/ruoyi/web/controller/app/AppDeviceAdminController.java +++ b/smart-switch-web/src/main/java/com/ruoyi/web/controller/app/AppDeviceAdminController.java @@ -26,7 +26,7 @@ public class AppDeviceAdminController extends BaseController { private DeviceService deviceService; @ApiOperation("管理员通过MAC获取设备信息") - @GetMapping("/admin/byMac") + @GetMapping("/byMac") @DeviceAdminRequired public AjaxResult getByMac(String mac) { return success(deviceService.selectByAnyMac(mac)); @@ -34,20 +34,20 @@ public class AppDeviceAdminController extends BaseController { @ApiOperation("管理员开关设备") @DeviceAdminRequired - @PutMapping("/admin/{deviceId}/switch") + @PutMapping("/{deviceId}/switch") public AjaxResult getExistMac(@PathVariable @ApiParam("设备ID") Long deviceId, @RequestParam @ApiParam("是否开启") Boolean open) { return toAjax(deviceService.switchDevice(deviceId, open, "小程序管理员开关设备")); } @ApiOperation("管理员修改设备电压系数") @DeviceAdminRequired - @PutMapping("/admin/{deviceId}/vxs") + @PutMapping("/{deviceId}/vxs") public AjaxResult updateVxs(@PathVariable @ApiParam("设备ID") Long deviceId, @RequestParam @ApiParam("电压系数") BigDecimal vxs) { return toAjax(deviceService.updateVxs(deviceId, vxs, "小程序管理员修改设备电压系数")); } @ApiOperation("管理员获取设备信息") - @GetMapping("/admin/get") + @GetMapping("/get") @DeviceAdminRequired public AjaxResult adminGet(@RequestParam(required = false) String sn) { DeviceVO device = null; @@ -80,7 +80,7 @@ public class AppDeviceAdminController extends BaseController { @ApiOperation("管理员重启设备") @DeviceAdminRequired - @PutMapping("/admin/reboot") + @PutMapping("/reboot") public AjaxResult adminReboot(@RequestParam String sn) { DeviceVO device = null; if (StringUtils.hasText(sn)) { diff --git a/smart-switch-web/src/main/java/com/ruoyi/web/controller/app/AppDeviceController.java b/smart-switch-web/src/main/java/com/ruoyi/web/controller/app/AppDeviceController.java index 725ab9d8..9facd709 100644 --- a/smart-switch-web/src/main/java/com/ruoyi/web/controller/app/AppDeviceController.java +++ b/smart-switch-web/src/main/java/com/ruoyi/web/controller/app/AppDeviceController.java @@ -307,4 +307,10 @@ public class AppDeviceController extends BaseController { return toAjax(smDeviceService.setWifi(dto)); } + @ApiOperation("蓝牙同步设备物联网信息") + @PutMapping("/bltSyncIot") + public AjaxResult bltSyncIot(String sn, String info) { + return toAjax(smDeviceService.bltSyncIot(sn, info)); + } + } diff --git a/smart-switch-web/src/main/java/com/ruoyi/web/controller/app/AppTransactionBillController.java b/smart-switch-web/src/main/java/com/ruoyi/web/controller/app/AppTransactionBillController.java index 3151a2de..3ff8f4d5 100644 --- a/smart-switch-web/src/main/java/com/ruoyi/web/controller/app/AppTransactionBillController.java +++ b/smart-switch-web/src/main/java/com/ruoyi/web/controller/app/AppTransactionBillController.java @@ -19,10 +19,10 @@ import com.ruoyi.ss.suit.domain.enums.SuitFeeType; import com.ruoyi.ss.transactionBill.domain.TransactionBillQuery; import com.ruoyi.ss.transactionBill.domain.bo.EndUseBO; import com.ruoyi.ss.transactionBill.domain.dto.*; -import com.ruoyi.ss.transactionBill.domain.vo.TransactionBillVO; import com.ruoyi.ss.transactionBill.domain.enums.TransactionBillGroupBy; import com.ruoyi.ss.transactionBill.domain.enums.TransactionBillStatus; import com.ruoyi.ss.transactionBill.domain.enums.TransactionBillType; +import com.ruoyi.ss.transactionBill.domain.vo.TransactionBillVO; import com.ruoyi.ss.transactionBill.service.TransactionAssembler; import com.ruoyi.ss.transactionBill.service.TransactionBillConverter; import com.ruoyi.ss.transactionBill.service.TransactionBillService; @@ -301,8 +301,9 @@ public class AppTransactionBillController extends BaseController @Log(title = "支付押金", businessType = BusinessType.OTHER, operatorType = OperatorType.MOBILE) @ApiOperation("支付押金") @PutMapping("/payDeposit") - public AjaxResult payDeposit(@RequestBody @Validated PayDepositDTO dto) { - return success(transactionBillService.payDeposit(transactionBillConverter.toRechargePayDepositBO(dto))); + public AjaxResult payDeposit(@RequestBody @Validated BillPayDTO dto) { +// return success(transactionBillService.pay(transactionBillConverter.toRechargePayBO(dto))); + return error("当前接口已弃用,请使用/app/bill/pay接口"); } @Log(title = "提前结束使用订单", businessType = BusinessType.OTHER, operatorType = OperatorType.MOBILE) @@ -319,7 +320,7 @@ public class AppTransactionBillController extends BaseController if (SuitFeeType.rechargeTimeList().contains(order.getSuitFeeType())) { int i = deviceService.resetWithBill(order.getDeviceId(), false, bo.getTotalEle(), "提前结束使用订单:" + order.getBillNo(), true, false); return toAjax(i); - } else if (SuitFeeType.rechargeCountList().contains(order.getSuitFeeType())) { + } else if (SuitFeeType.rechargeEleList().contains(order.getSuitFeeType())) { int i = deviceService.resetWithBill(order.getDeviceId(), false, bo.getTotalEle(), "提前结束使用订单:" + order.getBillNo(), false, true); return toAjax(i); } @@ -370,8 +371,10 @@ public class AppTransactionBillController extends BaseController @GetMapping("/unpaidTimingList") @JsonView(JsonViewProfile.App.class) public AjaxResult getUnpaidTimingList(TransactionBillQuery query) { - query.setUserId(getUserId()); - return success(transactionBillService.selectUnpaidTimingBill(query)); +// query.setUserId(getUserId()); +// return success(transactionBillService.selectUnpaidTimingBill(query)); + // 已弃用 + return success(); } @ApiOperation("开启/关闭正在使用的订单设备") diff --git a/smart-switch-web/src/main/java/com/ruoyi/web/controller/ss/SmTransactionBillController.java b/smart-switch-web/src/main/java/com/ruoyi/web/controller/ss/SmTransactionBillController.java index 7bbc78dc..193e2874 100644 --- a/smart-switch-web/src/main/java/com/ruoyi/web/controller/ss/SmTransactionBillController.java +++ b/smart-switch-web/src/main/java/com/ruoyi/web/controller/ss/SmTransactionBillController.java @@ -1,38 +1,32 @@ package com.ruoyi.web.controller.ss; -import java.util.Collections; -import java.util.List; -import javax.servlet.http.HttpServletResponse; - -import com.ruoyi.common.core.domain.model.LoginUser; -import com.ruoyi.ss.device.service.DeviceService; -import com.ruoyi.ss.suit.domain.enums.SuitFeeType; -import com.ruoyi.ss.transactionBill.domain.TransactionBillQuery; -import com.ruoyi.ss.transactionBill.domain.dto.EndUseDTO; -import com.ruoyi.ss.transactionBill.domain.vo.TransactionBillVO; -import com.ruoyi.ss.transactionBill.domain.dto.BillRefundDTO; -import com.ruoyi.ss.transactionBill.domain.dto.WithdrawApprovalDTO; -import com.ruoyi.ss.transactionBill.service.TransactionAssembler; -import com.ruoyi.ss.transactionBill.service.TransactionBillConverter; -import io.swagger.annotations.ApiModelProperty; -import io.swagger.annotations.ApiOperation; -import org.springframework.security.access.prepost.PreAuthorize; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.PutMapping; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestBody; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RestController; import com.ruoyi.common.annotation.Log; import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.domain.AjaxResult; -import com.ruoyi.common.enums.BusinessType; -import com.ruoyi.ss.transactionBill.service.TransactionBillService; -import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.common.core.domain.model.LoginUser; import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.enums.BusinessType; +import com.ruoyi.common.utils.poi.ExcelUtil; +import com.ruoyi.ss.device.service.DeviceService; +import com.ruoyi.ss.suit.domain.enums.SuitFeeType; +import com.ruoyi.ss.transactionBill.domain.TransactionBillQuery; +import com.ruoyi.ss.transactionBill.domain.dto.BillRefundDTO; +import com.ruoyi.ss.transactionBill.domain.dto.EndUseDTO; +import com.ruoyi.ss.transactionBill.domain.dto.WithdrawApprovalDTO; +import com.ruoyi.ss.transactionBill.domain.vo.TransactionBillVO; +import com.ruoyi.ss.transactionBill.service.TransactionAssembler; +import com.ruoyi.ss.transactionBill.service.TransactionBillConverter; +import com.ruoyi.ss.transactionBill.service.TransactionBillService; +import io.swagger.annotations.ApiModelProperty; +import io.swagger.annotations.ApiOperation; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; + +import javax.servlet.http.HttpServletResponse; +import java.util.Collections; +import java.util.List; /** * 充值记录Controller @@ -178,7 +172,7 @@ public class SmTransactionBillController extends BaseController if (SuitFeeType.rechargeTimeList().contains(order.getSuitFeeType())) { int i = deviceService.resetWithBill(order.getDeviceId(), false, null, reason, true, false); return toAjax(i); - } else if (SuitFeeType.rechargeCountList().contains(order.getSuitFeeType())) { + } else if (SuitFeeType.rechargeEleList().contains(order.getSuitFeeType())) { int i = deviceService.resetWithBill(order.getDeviceId(), false, null, reason, false, true); return toAjax(i); }