| 
					
				 | 
			
			
				@@ -1,14 +1,19 @@ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 package com.qmrb.parking.fee; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import cn.hutool.core.date.DateUtil; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import com.qmrb.system.pojo.entity.ContractPlaceNumberRel; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import lombok.extern.slf4j.Slf4j; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import org.apache.commons.lang3.StringUtils; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-import java.math.BigDecimal; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import java.time.Duration; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import java.time.LocalDateTime; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import java.time.LocalTime; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import java.time.ZoneId; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import java.util.ArrayList; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import java.util.HashMap; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 import java.util.List; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+import java.util.Map; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 @Slf4j 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 public class ParkingFeeCalculator { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     /** 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -43,6 +48,8 @@ public class ParkingFeeCalculator { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         public CouponRecord couponRecord = new CouponRecord(); // 优惠券记录 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        public String contractType;//合同类型 3包租协议 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     // 支付记录类 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -67,6 +74,7 @@ public class ParkingFeeCalculator { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         // 3. 计算计费时长 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         Duration billingDuration = Duration.between(billingStartTime, billingEndTime); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         // 4. 检查离场时长宽容度 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         /*if (feeRule.gracePeriodMinutes > 0) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             for (PaymentRecord payment : parkingRecord.paymentRecords) { 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -139,12 +147,13 @@ public class ParkingFeeCalculator { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             if(totalFee<0){ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				                 return 0.0; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        } else if (StringUtils.equals(couponRecord.couponType,"2")) {//时长券 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /*else if (StringUtils.equals(couponRecord.couponType,"2")) {//时长券 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             //用计算不足24小时的部分来计算,不区分首周期 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             double dicFee = calculateRemainingFeeDue(Duration.ofMinutes(new BigDecimal(couponRecord.denomination).intValue()), feeRule); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             log.info("优惠金额: "+dicFee+"元"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				             return totalFee - dicFee; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        }*/ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         return totalFee; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     // 计算不足 24 小时的费用 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -210,6 +219,40 @@ public class ParkingFeeCalculator { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				  
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * 包租协议测试 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public static void e3(){ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        // 示例计费规则 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        FeeRule feeRule = new FeeRule(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        feeRule.freeDurationMinutes = 0;//免费时长 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        feeRule.firstCycleFee = 20.0;//首周期计费金额 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        feeRule.firstCycleDurationMinutes = 60;//首周期计费时长 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        feeRule.cycleDurationMinutes = 30;//计费周期 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        feeRule.cycleFee = 2;//每周期计费金额 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        feeRule.dailyCapFee = 30;//24小时封顶费用 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        feeRule.gracePeriodMinutes = 15;// 离场时长宽容度(分钟) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        // 示例停车记录 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        ParkingRecord parkingRecord = new ParkingRecord(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        parkingRecord.entryTime = LocalDateTime.of(2025, 3, 28, 14, 0); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        parkingRecord.exitTime = LocalDateTime.of(2025, 3, 29, 15, 50); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        parkingRecord.monthlyCardExpiryTime = null; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /*parkingRecord.paidAmount = 0.01;//已支付金额 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        parkingRecord.paymentRecords=new ArrayList<>();*/ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        /*PaymentRecord paymentRecord = new PaymentRecord(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        paymentRecord.paymentTime = LocalDateTime.of(2025, 3, 14, 10, 0); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        parkingRecord.paymentRecords.add(paymentRecord);*/ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        List<ContractPlaceNumberRel> registrations = new ArrayList<>(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        ContractPlaceNumberRel placeNumberRel = new ContractPlaceNumberRel(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        placeNumberRel.setStartTime(DateUtil.parse("2025-03-28 12:00:00")); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        placeNumberRel.setEndTime(DateUtil.parse(" 2025-03-29 14:00:00")); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        registrations.add(placeNumberRel); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        // 计算费用 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Map<String, Object> result = calculateParkingFee(parkingRecord, feeRule, registrations); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        System.out.println("停车费用: " + result.toString()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				      * 设【免费时长】=0;【24小时收费金额】=30元;【首周期计费时长】=60分钟;【首周期计费金额】=20元;【计费周期】=30分钟;【每周期计费金额】=2元 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				      */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     public static void e1(){ 
			 | 
		
	
	
		
			
				| 
					
				 | 
			
			
				@@ -234,11 +277,84 @@ public class ParkingFeeCalculator { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         double fee = calculateParkingFee(parkingRecord, feeRule, false); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				         log.info("停车费用: " + fee + " 元"); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * 包租协议计算停车费用 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * @param registrations 车牌登记记录列表(已按车牌筛选) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * @return 包含停车时长和费用的Map 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    public static Map<String, Object> calculateParkingFee(ParkingRecord parkingRecord,FeeRule feeRule, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                                   List<ContractPlaceNumberRel> registrations) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        LocalDateTime entryTime = parkingRecord.entryTime; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        LocalDateTime currentTime = LocalDateTime.now(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        Map<String, Object> result = new HashMap<>(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        // 1. 计算总停车时长(分钟) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        long totalMinutes = Duration.between(entryTime, currentTime).toMinutes(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        // 2. 计算登记记录覆盖的有效时长(分钟) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        long coveredMinutes = calculateCoveredMinutes(entryTime, currentTime, registrations); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        // 3. 计算实际计费时长 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        long chargedMinutes = Math.max(0, totalMinutes - coveredMinutes); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        // 4. 计算费用(不足1小时按1小时计算) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+//        double hours = Math.ceil(chargedMinutes / 60.0); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+//        double fee = hours * HOURLY_RATE; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        result.put("totalMinutes", totalMinutes); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        result.put("coveredMinutes", coveredMinutes); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        result.put("chargedMinutes", chargedMinutes); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        //实际收费的金额为入场时间+收费时长 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        parkingRecord.exitTime = parkingRecord.entryTime.plusMinutes(chargedMinutes); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        result.put("fee", calculateParkingFee(parkingRecord, feeRule, false)); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        log.info("包租协议收费:"+result.toString()); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return result; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * 计算所有登记记录覆盖的有效时长 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private static long calculateCoveredMinutes(LocalDateTime entryTime, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                         LocalDateTime currentTime, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                         List<ContractPlaceNumberRel> registrations) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return registrations.stream() 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                .mapToLong(reg -> calculateSingleRegistrationMinutes(entryTime, currentTime, reg)) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                .sum(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     * 计算单条登记记录的有效时长 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+     */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    private static long calculateSingleRegistrationMinutes(LocalDateTime entryTime, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                                    LocalDateTime currentTime, 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                                                    ContractPlaceNumberRel registration) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        LocalDateTime regStartTime = registration.getStartTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        LocalDateTime regEndTime = registration.getEndTime().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        // 有效开始时间 = max(登记起始时间, 入场时间) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        LocalDateTime effectiveStart = regStartTime.isAfter(entryTime) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                ? regStartTime 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                : entryTime; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        // 有效结束时间 = min(登记到期时间, 当前时间) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        LocalDateTime effectiveEnd = regEndTime.isBefore(currentTime) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                ? regEndTime 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+                : currentTime; 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        // 计算有效时长(分钟) 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        if (effectiveStart.isBefore(effectiveEnd)) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+            return Duration.between(effectiveStart, effectiveEnd).toMinutes(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        return 0; // 无有效覆盖 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+    } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     /** 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				      * @param args 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				      */ 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     public static void main(String[] args) { 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 //        e1(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				-        e2(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+//        e2(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				+        e3(); 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				     } 
			 | 
		
	
		
			
				 | 
				 | 
			
			
				 } 
			 |