package service import ( "errors" "fmt" "time" "wails-app/internal/dao" "wails-app/internal/global" ) var ( // ErrFeeConfigNotFound 车辆类型未配置收费规则(不再隐式按 0.1 元/分钟兜底收费)。 ErrFeeConfigNotFound = errors.New("车辆类型未配置收费规则") // ErrFeeConfigInvalid 收费配置数值非法。 ErrFeeConfigInvalid = errors.New("收费配置无效") ) const minutesPerDay = 1440 // CalculateFee is the shared parking-fee calculation used by vehicle exit // flows and the digital-ticket live preview. // // 规则: // - 该车辆类型必须存在收费配置,否则返回错误(避免隐式兜底造成错误收费)。 // - 免费时长(StartTime)内费用为 0;超出后按起步价 + 单位时长累加,不足一个单位按一个单位。 // - 配置了日封顶(DailyMaxFee)时,费用不超过"跨天数 × 日封顶",跨天向上取整, // 修复多日停车只应用一次日封顶的问题。 // - VIP 免费优先于折扣;折扣应用于封顶后的金额。 func CalculateFee(vehicle *dao.Vehicle, stayTime int64) (float64, error) { if vehicle == nil || global.GVA_DB == nil { return 0, errors.New("计费上下文缺失") } if stayTime < 0 { stayTime = 0 } var feeConfig dao.FeeConfig if err := global.GVA_DB.Where("vehicle_type_id = ?", vehicle.VehicleTypeID).First(&feeConfig).Error; err != nil { return 0, fmt.Errorf("%w: 车辆类型ID=%d", ErrFeeConfigNotFound, vehicle.VehicleTypeID) } if feeConfig.UnitTime <= 0 { return 0, fmt.Errorf("%w: 单位时间必须大于0", ErrFeeConfigInvalid) } isVIP := false if vehicle.OwnerId != nil { var owner dao.Owner if err := global.GVA_DB.First(&owner, *vehicle.OwnerId).Error; err == nil { now := time.Now() isVIP = owner.IsVip && !owner.VipExpireTime.IsZero() && owner.VipExpireTime.After(now) } } if isVIP && feeConfig.IsVIPFree { return 0, nil } if stayTime <= int64(feeConfig.StartTime) { return 0, nil } extraTime := stayTime - int64(feeConfig.StartTime) extraUnits := extraTime / int64(feeConfig.UnitTime) if extraTime%int64(feeConfig.UnitTime) > 0 { extraUnits++ } fee := feeConfig.StartFee + float64(extraUnits)*feeConfig.UnitFee if feeConfig.DailyMaxFee > 0 { days := stayTime / minutesPerDay if stayTime%minutesPerDay > 0 { days++ } capFee := float64(days) * feeConfig.DailyMaxFee if fee > capFee { fee = capFee } } if isVIP { fee *= feeConfig.VIPDiscount } return fee, nil }