From 48abe75ced37e64484afbcd21183948b71deb96b Mon Sep 17 00:00:00 2001 From: Diego Basterrech Date: Wed, 3 Dec 2025 14:46:47 -0300 Subject: [PATCH 1/2] Fix daily booking issue --- app/support/reservations/validations.rb | 41 +++++++--- config/locales/en.models.yml | 2 + spec/models/reservations_validations_spec.rb | 80 ++++++++++++++++++++ 3 files changed, 111 insertions(+), 12 deletions(-) diff --git a/app/support/reservations/validations.rb b/app/support/reservations/validations.rb index d90b141baa..d7a8743a5e 100644 --- a/app/support/reservations/validations.rb +++ b/app/support/reservations/validations.rb @@ -123,29 +123,46 @@ def conflicting_admin_reservation end def satisfies_minimum_length? - diff = reserve_end_at - reserve_start_at # in seconds - return false unless product.min_reserve_mins.nil? || product.min_reserve_mins == 0 || diff / 60 >= product.min_reserve_mins - true + if product.daily_booking? + return true if product.min_reserve_days.to_i == 0 + duration_days >= product.min_reserve_days + else + diff = reserve_end_at - reserve_start_at # in seconds + product.min_reserve_mins.nil? || product.min_reserve_mins == 0 || diff / 60 >= product.min_reserve_mins + end end def satisfies_minimum_length - errors.add(:base, :too_short, length: product.min_reserve_mins) unless satisfies_minimum_length? + if product.daily_booking? + errors.add(:base, :too_short_days, length: product.min_reserve_days) unless satisfies_minimum_length? + else + errors.add(:base, :too_short, length: product.min_reserve_mins) unless satisfies_minimum_length? + end end def satisfies_maximum_length? - return true if product.max_reserve_mins.to_i == 0 - diff = reserve_end_at - reserve_start_at # in seconds + if product.daily_booking? + return true if product.max_reserve_days.to_i == 0 + duration_days <= product.max_reserve_days + else + return true if product.max_reserve_mins.to_i == 0 + diff = reserve_end_at - reserve_start_at # in seconds - # If this is updating because we're in the grace period, use the old value for checking duration - if in_grace_period? && actual_start_at && reserve_start_at_changed? && reserve_start_at_was - diff = reserve_end_at - reserve_start_at_was - end + # If this is updating because we're in the grace period, use the old value for checking duration + if in_grace_period? && actual_start_at && reserve_start_at_changed? && reserve_start_at_was + diff = reserve_end_at - reserve_start_at_was + end - diff <= product.max_reserve_mins.minutes + diff <= product.max_reserve_mins.minutes + end end def satisfies_maximum_length - errors.add(:base, :too_long, length: product.max_reserve_mins) unless satisfies_maximum_length? + if product.daily_booking? + errors.add(:base, :too_long_days, length: product.max_reserve_days) unless satisfies_maximum_length? + else + errors.add(:base, :too_long, length: product.max_reserve_mins) unless satisfies_maximum_length? + end end def allowed_in_schedule_rules? diff --git a/config/locales/en.models.yml b/config/locales/en.models.yml index 72ea5c4f09..59e414cfe9 100644 --- a/config/locales/en.models.yml +++ b/config/locales/en.models.yml @@ -112,6 +112,8 @@ en: no_schedule_group: You do not have permission to make a reservation at this time too_long: The reservation is too long. It cannot be longer than %{length} minutes. too_short: The reservation is too short. It must be at least %{length} minutes. + too_long_days: The reservation is too long. It cannot be longer than %{length} days. + too_short_days: The reservation is too short. It must be at least %{length} days. reserve_start_at: after_cutoff: must be at least %{hours} hours in the future in_past: must be in the future diff --git a/spec/models/reservations_validations_spec.rb b/spec/models/reservations_validations_spec.rb index 4181dc811b..42102e6f4b 100644 --- a/spec/models/reservations_validations_spec.rb +++ b/spec/models/reservations_validations_spec.rb @@ -151,4 +151,84 @@ end end end + + describe "daily booking min/max length validations" do + subject(:reservation) do + build( + :setup_reservation, + product:, + reserve_start_at: start_at, + reserve_end_at: end_at + ) + end + + let(:product) { create :setup_instrument, :daily_booking } + let(:start_at) { Time.current } + + describe "satisfies_maximum_length" do + context "when max_reserve_days is not set" do + let(:end_at) { start_at + 10.days } + + it { is_expected.to be_valid } + end + + context "when max_reserve_days is set" do + before { product.update!(max_reserve_days: 4) } + + context "when reservation is within max days" do + let(:end_at) { start_at + 3.days } + + it { is_expected.to be_valid } + end + + context "when reservation equals max days" do + let(:end_at) { start_at + 4.days } + + it { is_expected.to be_valid } + end + + context "when reservation exceeds max days" do + let(:end_at) { start_at + 6.days } + + it "is invalid with too_long_days error" do + is_expected.not_to be_valid + expect(reservation.errors).to be_added(:base, :too_long_days, length: 4) + end + end + end + end + + describe "satisfies_minimum_length" do + context "when min_reserve_days is not set" do + let(:end_at) { start_at + 1.day } + + it { is_expected.to be_valid } + end + + context "when min_reserve_days is set" do + before { product.update!(min_reserve_days: 2) } + + context "when reservation meets minimum days" do + let(:end_at) { start_at + 3.days } + + it { is_expected.to be_valid } + end + + context "when reservation equals minimum days" do + let(:end_at) { start_at + 2.days } + + it { is_expected.to be_valid } + end + + context "when reservation is less than minimum days" do + let(:end_at) { start_at + 1.day } + + it "is invalid with too_short_days error" do + is_expected.not_to be_valid + expect(reservation.errors).to be_added(:base, :too_short_days, length: 2) + end + end + end + end + end end From c4f3dc405b84584c14531c4c9fff2643c5db3a6a Mon Sep 17 00:00:00 2001 From: Diego Basterrech Date: Thu, 4 Dec 2025 14:13:21 -0300 Subject: [PATCH 2/2] Fix NoMethodError when viewing daily booking reservation without actual_end_at --- app/support/reservations/date_support.rb | 2 +- .../reservations/date_support_spec.rb | 46 +++++++++++++++++++ 2 files changed, 47 insertions(+), 1 deletion(-) diff --git a/app/support/reservations/date_support.rb b/app/support/reservations/date_support.rb index 92e7f1e7a8..5a26de38fd 100644 --- a/app/support/reservations/date_support.rb +++ b/app/support/reservations/date_support.rb @@ -95,7 +95,7 @@ def actual_duration_days(actual_end_fallback: nil) if @actual_duration_days @actual_duration_days.to_i elsif actual_start_at - TimeRange.new(actual_start_at, actual_end_at || actual_end_fallback).duration_days.ceil + TimeRange.new(actual_start_at, actual_end_at || actual_end_fallback).duration_days&.ceil else 0 end diff --git a/spec/app_support/reservations/date_support_spec.rb b/spec/app_support/reservations/date_support_spec.rb index b6ca07a75f..2af42717e4 100644 --- a/spec/app_support/reservations/date_support_spec.rb +++ b/spec/app_support/reservations/date_support_spec.rb @@ -270,4 +270,50 @@ expect(reservation.has_reserved_times?).to be true end end + + describe "#actual_duration_days" do + context "when @actual_duration_days is set" do + before { reservation.instance_variable_set(:@actual_duration_days, "3") } + + it "returns the value as an integer" do + expect(reservation.actual_duration_days).to eq(3) + end + end + + context "when actual_start_at and actual_end_at are set" do + let(:actual_start_at) { Time.zone.parse("2015-01-01T09:00:00") } + let(:actual_end_at) { Time.zone.parse("2015-01-04T09:00:00") } + + it "returns the duration in days (ceiling)" do + expect(reservation.actual_duration_days).to eq(3) + end + end + + context "when actual_start_at is set but actual_end_at is nil" do + let(:actual_start_at) { Time.zone.parse("2015-01-01T09:00:00") } + let(:actual_end_at) { nil } + + it "returns nil" do + expect(reservation.actual_duration_days).to be_nil + end + end + + context "when actual_start_at is nil" do + let(:actual_start_at) { nil } + + it "returns 0" do + expect(reservation.actual_duration_days).to eq(0) + end + end + + context "when using actual_end_fallback" do + let(:actual_start_at) { Time.zone.parse("2015-01-01T09:00:00") } + let(:actual_end_at) { nil } + + it "uses the fallback to calculate duration" do + fallback = Time.zone.parse("2015-01-03T09:00:00") + expect(reservation.actual_duration_days(actual_end_fallback: fallback)).to eq(2) + end + end + end end