fee.go 2.5 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182
  1. package service
  2. import (
  3. "errors"
  4. "fmt"
  5. "time"
  6. "wails-app/internal/dao"
  7. "wails-app/internal/global"
  8. )
  9. var (
  10. // ErrFeeConfigNotFound 车辆类型未配置收费规则(不再隐式按 0.1 元/分钟兜底收费)。
  11. ErrFeeConfigNotFound = errors.New("车辆类型未配置收费规则")
  12. // ErrFeeConfigInvalid 收费配置数值非法。
  13. ErrFeeConfigInvalid = errors.New("收费配置无效")
  14. )
  15. const minutesPerDay = 1440
  16. // CalculateFee is the shared parking-fee calculation used by vehicle exit
  17. // flows and the digital-ticket live preview.
  18. //
  19. // 规则:
  20. // - 该车辆类型必须存在收费配置,否则返回错误(避免隐式兜底造成错误收费)。
  21. // - 免费时长(StartTime)内费用为 0;超出后按起步价 + 单位时长累加,不足一个单位按一个单位。
  22. // - 配置了日封顶(DailyMaxFee)时,费用不超过"跨天数 × 日封顶",跨天向上取整,
  23. // 修复多日停车只应用一次日封顶的问题。
  24. // - VIP 免费优先于折扣;折扣应用于封顶后的金额。
  25. func CalculateFee(vehicle *dao.Vehicle, stayTime int64) (float64, error) {
  26. if vehicle == nil || global.GVA_DB == nil {
  27. return 0, errors.New("计费上下文缺失")
  28. }
  29. if stayTime < 0 {
  30. stayTime = 0
  31. }
  32. var feeConfig dao.FeeConfig
  33. if err := global.GVA_DB.Where("vehicle_type_id = ?", vehicle.VehicleTypeID).First(&feeConfig).Error; err != nil {
  34. return 0, fmt.Errorf("%w: 车辆类型ID=%d", ErrFeeConfigNotFound, vehicle.VehicleTypeID)
  35. }
  36. if feeConfig.UnitTime <= 0 {
  37. return 0, fmt.Errorf("%w: 单位时间必须大于0", ErrFeeConfigInvalid)
  38. }
  39. isVIP := false
  40. if vehicle.OwnerId != nil {
  41. var owner dao.Owner
  42. if err := global.GVA_DB.First(&owner, *vehicle.OwnerId).Error; err == nil {
  43. now := time.Now()
  44. isVIP = owner.IsVip && !owner.VipExpireTime.IsZero() && owner.VipExpireTime.After(now)
  45. }
  46. }
  47. if isVIP && feeConfig.IsVIPFree {
  48. return 0, nil
  49. }
  50. if stayTime <= int64(feeConfig.StartTime) {
  51. return 0, nil
  52. }
  53. extraTime := stayTime - int64(feeConfig.StartTime)
  54. extraUnits := extraTime / int64(feeConfig.UnitTime)
  55. if extraTime%int64(feeConfig.UnitTime) > 0 {
  56. extraUnits++
  57. }
  58. fee := feeConfig.StartFee + float64(extraUnits)*feeConfig.UnitFee
  59. if feeConfig.DailyMaxFee > 0 {
  60. days := stayTime / minutesPerDay
  61. if stayTime%minutesPerDay > 0 {
  62. days++
  63. }
  64. capFee := float64(days) * feeConfig.DailyMaxFee
  65. if fee > capFee {
  66. fee = capFee
  67. }
  68. }
  69. if isVIP {
  70. fee *= feeConfig.VIPDiscount
  71. }
  72. return fee, nil
  73. }