扫码停车
扫码付款后,自动降锁,车位数据设置占用,用户有操作锁的权限。
车位预约 略
用户预约列表,未实现
预约时车位数据不能被设置占用,与扫码占位不同。
操作占位锁
最后一次扫码支付的人,拥有操作锁的权限。
月卡用户
月卡用户扫设备,不创建订单,但是可以分配设备权限。
计费
车场计费方案分为四种:
1、按次收费 2、按时收费 3、按阶梯收费 4、按时段收费
这四种为互斥关系,每个计费方案只能选择其中的一种
1、按次收费:可设置每次前多少分钟免费,和每次停车收费多少元;
2、按时收费:可设置每多少分钟收多少元,前多少分钟收费多少元(如果选择了超过所设时长是否有效,则每次超过所设时长后,前多少分钟收费多少元都起作用),还可设置前多少分钟最高收多少元,每日单次最高收多少元;
3、按阶梯收费,即将一天划分为多个阶梯,通过判断停车时间属于哪个阶梯来收费;
4、按时段收费:设置临时车辆的计费标准,可分日间收费和夜间收费。可对车型分开进行计费。
在临时车收费区间中,可设置前多少分钟收费多少元,也可设置每日单次收费上限钱数。在收费选项中,还可以设置嵌套过场多少分钟内不收费,每日收费上限及前多少分钟是不收费。
多退少补
30分钟免费时间内,不允许取消订单,只能完成结算,离位升锁。
20 计时收费
超过24小时重新滚动当前计费规则。
/**
* <p>根据时长,匹配并计算出一个车场/车位的费用金额 </p>
* 计费方式: 10计次收费; 20计时收费; 30分段收费
*/
public BigDecimal findParkingFeeTotalMoney(long feeId, final double totalDuration) {
final BigDecimal dec0 = new BigDecimal(0);
ParkingFee fee = findOne(feeId);
if(fee == null || fee.getEnabled() == 2){
return dec0;
}
if(fee.getFeeWayId() == 20){
//剩余 匹配多个费用规则计算的总费用
BigDecimal totalFee = getTotalFeeByFeePeriodDuration(feeId, totalDuration);
log.info("[20 计时收费] 总时长:{}, 总费用:{}, feeId:{}", totalDuration, totalFee, feeId);
return totalFee;
}
else if(fee.getFeeWayId() == 30){
ParkingFeeRule rule = findFeeRuleByPeriodTime(feeId);//计分段收费
BigDecimal amount = rule == null ? dec0 : rule.getAmount();
log.info("[30 分段收费] 总时长:{}, 总费用:{}, feeId:{}", totalDuration, amount, feeId);
return amount;
}
return dec0;
}
/**
* <p>按 [计时收费]计算总费用 重要</p>
*
* 1.根据时长,循环匹配费用规则明细,按 [计时收费]合计一个总费用;
* 2.先过滤免费时长、封顶时长;
* 3.首1小时代码固定写死进行判断。
*/
public BigDecimal getTotalFeeByFeePeriodDuration(long feeId, final double totalDuration) {
BigDecimal totalFee = new BigDecimal(0);
BigDecimal remainDur = new BigDecimal(totalDuration); //剩余时长
if(totalDuration < 0.1){
return totalFee;
}
//查询一个费用标价下的所有时段规则
List<ParkingFeeRule> ruleList = parkingFeeRuleService.findRulesByFeeId(feeId);
if (ruleList.isEmpty()) {
return totalFee;
}
//大于封顶金额 超过24小时
for (ParkingFeeRule ru : ruleList) {
if(ru.getFeeTypeId() == 4 && remainDur.doubleValue() > ru.getFeePeriodDuration()){
// 大于24h, 费用35元
if(remainDur.doubleValue() >= 24){
int day = remainDur.intValue() / 24;
totalFee = Misc.sum(totalFee, Misc.cheng(new BigDecimal(day), ru.getAmount()));
remainDur = Misc.subtract(remainDur, day*24);
log.info("rule-4 封顶, day:{}, 总时长:{}, remainDur:{}, totalFee:{}", day, totalDuration, remainDur, totalFee);
}
// 大于封顶时长, 封顶金额35元
if(remainDur.doubleValue() > ru.getFeePeriodDuration()){
remainDur = Misc.subtract(remainDur, 24);
totalFee = Misc.sum(totalFee, ru.getAmount());//35元
log.info("rule-4 封顶, 计费时长:{}, 总:{}, 剩:{}, totalFee:{}", ru.getFeePeriodDuration(), totalDuration, remainDur, totalFee);
}
if(totalDuration < 0.5){
return totalFee;
}
}
}
// mf=0.5, first=1; total=13 sheng=0
// if(mf == 0.5 && total > 1){} //sheng=13-0.5=12.5
// if(first == 1 && total > 1){} //sheng=12.5-1=11.5
// if(total <= 13 && sheng > 0 && sheng <= 13){} //sheng=0h
//开始遍历时段规则信息
for (ParkingFeeRule ru : ruleList) {
if(ru.getFeePeriodDuration() == null){
continue;
}
//剩余时长计算完了
if(remainDur.doubleValue() < 0.1){
return totalFee;
}
//小于免费时长
if(ru.getFeeTypeId() == 1 && totalDuration <= ru.getFeePeriodDuration()){
log.info("rule 免费, 免费时长:{}, 总时长:{}", ru.getFeePeriodDuration(), totalDuration);
return totalFee;
}
//大于免费时长
if(ru.getFeeTypeId() == 1 && totalDuration > ru.getFeePeriodDuration()){
log.info("rule-1 免费, 免费时长:{}, 总:{}, 剩余:{}", ru.getFeePeriodDuration(), totalDuration, remainDur);
continue;
}
//首1小时
if(ru.getFeeTypeId() == 2){
//只有1条记录, 费用=费用单价×费用时长
if(ruleList.size() == 1){
return totalFee.add(Misc.cheng(ru.getAmount(), totalDuration));
}
//
// 硬编码匹配首0.25小时/首半小时/首1小时
// 首1小时
if(ru.getFeePeriodDuration() == 1 && totalDuration >= 1) {
//费用=费用单价*费用时长
totalFee = totalFee.add(Misc.cheng(ru.getAmount(), 1));
//剩余时长 = 总时长 - 费用时长
remainDur = Misc.subtract(remainDur, 1);
log.info("rule-2 首1h, 单价:{}, 计费时长:{}, 总:{}, 剩余:{}", ru.getAmount(), 1, totalDuration, remainDur);
continue;
}
else if(ru.getFeePeriodDuration() == 0.5 && totalDuration >= 0.5) {
totalFee = totalFee.add(Misc.cheng(ru.getAmount(), ru.getFeePeriodDuration()));
remainDur = Misc.subtract(remainDur, 0.5);
log.info("rule-2 首0.5h, 单价:{}, 计费时长:{}, 总:{}, 剩余:{}", ru.getAmount(), 0.5, totalDuration, remainDur);
continue;
}
else if(ru.getFeePeriodDuration() == 0.25 && totalDuration >= 0.25){
totalFee = totalFee.add(Misc.cheng(ru.getAmount(), 0.25));
remainDur = Misc.subtract(remainDur, 0.25);
log.info("rule-2 首0.25h, 单价:{}, 计费时长:{}, 总:{}, 剩余:{}", ru.getAmount(), 0.25, totalDuration, remainDur);
continue;
}
}
//按小时计费匹配, 遍历时段规则信息, 一般会匹配到两个费用规则
if(ru.getFeeTypeId() == 3){
// 时长大于当前计费时长 (14 > 13 && 13==13) 这里要优先排除封顶金额,否则计算错误
if(totalDuration > ru.getFeePeriodDuration() && remainDur.doubleValue() <= ru.getFeePeriodDuration()){
//费用=费用单价*费用时长
BigDecimal fee = Misc.cheng(ru.getAmount(), remainDur);
totalFee = totalFee.add(fee);
//剩余时长 = 总时长 - 费用时长
remainDur = Misc.subtract(remainDur, ru.getFeePeriodDuration());
log.info("rule-2 按小时, 费用:{}, 总费:{}, 单价:{}, 计费时长:{}, 总:{}, 剩余:{}", fee, totalFee, ru.getAmount(), ru.getFeePeriodDuration(), totalDuration, remainDur);
continue;
}
// 最后 小于计费时长 (2~13h <= 13 and 剩余时长=11.5); 排除负数
if(totalDuration <= ru.getFeePeriodDuration() && remainDur.doubleValue() > 0 && remainDur.doubleValue() <= ru.getFeePeriodDuration()){
BigDecimal fee = Misc.cheng(ru.getAmount(), remainDur);
totalFee = totalFee.add(fee);
//剩余时长 <= 0
remainDur = Misc.subtract(remainDur, ru.getFeePeriodDuration());
log.info("rule-3 按小时 单价:{}, 计费时长:{}, 费用:{}, 总费用:{}, 总:{}, 剩余:{}", ru.getAmount(), ru.getFeePeriodDuration(), fee, totalFee, totalDuration, remainDur);
return totalFee;
}
}
//
if(ru.getFeeTypeId() == 5 && totalDuration > ru.getFeePeriodDuration()){
log.info("rule, 超时:{}", Misc.toJson(ru));
totalFee = totalFee.add(ru.getAmount());
remainDur = Misc.subtract(remainDur, ru.getFeePeriodDuration());
log.info("rule, 超时, 费用:{}, 时长:{}", totalFee, remainDur);
return totalFee;
}
}
return totalFee;
}
30
/**
* 暂时先不用
* 根据 [分段收费方式] 按时段时间, 匹配一个车场费用的时段规则
*/
public ParkingFeeRule findFeeRuleByPeriodTime(long feeId) {
//查询一个费用标价下的所有时段规则
List<ParkingFeeRule> ruleList = parkingFeeRuleService.findRulesByFeeId(feeId);
if (ruleList.isEmpty()) {
return null;
}
//遍历费用规则的时段信息
for (ParkingFeeRule rule : ruleList) {
if (Misc.isNull(rule.getPeriodStartTime()) || Misc.isNull(rule.getPeriodEndTime())) {
continue;
}
//判断工作日
if(!DateUtil.checkWeekType(rule.getDateType())){
continue;
}
//在这个时段范围内
if (rule.getIsAllDay() == 1 && rule.getAmount().doubleValue() > 0) {
log.info("时段费用规则 全天:{}", Misc.toJson(rule));
rule.setPeriodStartTime("00:00");
rule.setPeriodEndTime("23:59");
return rule;
}
//在这个时段范围内
log.info("时段费用规则:{}", Misc.toJson(rule));
if (DateUtil.isBetweenTime(rule.getPeriodStartTime(), rule.getPeriodEndTime())) {
return rule;
}
}
return null;
}
CREATE TABLE `d_parking_fee` (
`fee_id` bigint(20) NOT NULL COMMENT 'ID',
`fee_name` varchar(50) DEFAULT NULL COMMENT '收费标准名称',
`fee_way_id` int(11) DEFAULT '0' COMMENT '计费方式: 10计次收费; 20计时收费; 30分段收费',
`enabled` int(1) DEFAULT '1' COMMENT '是否启用: 0未使用; 1使用中; 2停止使用',
`fee_remark` varchar(90) DEFAULT '' COMMENT '费用备注',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`create_user` bigint(20) DEFAULT NULL COMMENT '创建人',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`update_user` bigint(20) DEFAULT NULL COMMENT '更新人',
PRIMARY KEY (`fee_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='车场收费参考';
参考资料
停车场收费管理系统/停车场管理系统的设计与实现
https://blog.csdn.net/weixin_47958760/article/details/126860836
yue ka
月卡规则
用户月卡表
购买记录
版权声明:本文为qq_19636353原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。