diff --git a/lib/No_Available_Room_Error.rb b/lib/No_Available_Room_Error.rb new file mode 100644 index 000000000..b2467d001 --- /dev/null +++ b/lib/No_Available_Room_Error.rb @@ -0,0 +1,2 @@ +class NoAvailableRoomError < StandardError +end diff --git a/lib/date_range.rb b/lib/date_range.rb new file mode 100644 index 000000000..e999ce9a8 --- /dev/null +++ b/lib/date_range.rb @@ -0,0 +1,30 @@ +require 'date' + +module Hotel + class DateRange + attr_reader :start_date, :end_date, :range + + def initialize(start_date:, end_date:) + raise ArgumentError.new("start and end dates must be Date objects") if start_date.class != Date && end_date.class != Date + @start_date = start_date + @end_date = end_date + @range = (@start_date .. @end_date).to_a + raise ArgumentError.new("End date can not be before start date") if @end_date < @start_date + raise ArgumentError.new("Reservation can not be before today") if @end_date < Date.today || @start_date < Date.today + + end + + def overlap?(date_range) + # if the array of the combined arrays is empty, there is no overlap, so return false + # if the array is not empty, return true + overlap = (self.range & date_range.range).empty? || (date_range.start_date >= self.end_date) || (date_range.end_date <= self.start_date) ? false : true + return overlap + end + + def nights + return @range.count - 1 + end + + + end +end diff --git a/lib/front_desk.rb b/lib/front_desk.rb new file mode 100644 index 000000000..8a9480b4e --- /dev/null +++ b/lib/front_desk.rb @@ -0,0 +1,101 @@ +require_relative 'room' +require_relative 'date_range' +require_relative 'reservations' +require_relative 'hotel_block' +require_relative 'no_available_room_error' + +module Hotel + class FrontDesk + attr_accessor :rooms, :reservations, :hotel_blocks + + def initialize + @rooms = [] + + (1..20).each do |room_num| + new_room = Room.new(room_number: room_num, cost: 200) + @rooms << new_room + end + @reservations = [] + @hotel_blocks = [] + end + + def add_reservation(date_range) + available_rooms = check_block_status(date_range) + + raise NoAvailableRoomError.new("there are no available rooms for that date")if available_rooms.empty? == true + + chosen_room = available_rooms[0] + new_reservation = Hotel::Reservation.new(date_range: date_range, room: chosen_room) + @reservations << new_reservation + chosen_room.add_room_reservation(new_reservation) + return new_reservation + end + + def available_rooms(date_range) + available_rooms = @rooms.reject do |room| + room.reservations.any?{|reservation| reservation.date_range.overlap?(date_range) == true} + end + return available_rooms + + end + + def find_reservation_with(room: nil, date_range:) + res_w_given_date = @reservations.select {|reservation| (reservation.date_range == date_range && reservation.room == room) || reservation.date_range == date_range } + return res_w_given_date + end + + def total_cost(reservation) + if reservation.block_reservation == false + total_cost = reservation.date_range.nights * reservation.room.cost + else + total_cost = reservation.date_range.nights * reservation.room.cost * (1-reservation.room.discount_cost) + end + return total_cost + end + + def check_block_status(date_range) + available_rooms = available_rooms(date_range) + @hotel_blocks.each do |hotel_block| + if hotel_block.date_range.overlap?(date_range) == true + hotel_block.rooms.each do |block_room| + available_rooms.delete(block_room) + end + end + end + return available_rooms + end + + def request_block(block_count, date_range, discount_cost) + available_rooms = check_block_status(date_range) + raise NoAvailableRoomError.new("Not enough available rooms to fulfill block") if available_rooms.count < block_count + + hotel_block = Hotel::HotelBlock.new(block_count: block_count, date_range: date_range, discount_cost: discount_cost) + + x = 0 + until x == block_count do + available_room = available_rooms[x] + available_room.change_cost(discount_cost) + hotel_block.rooms << available_room + x += 1 + end + @hotel_blocks << hotel_block + return hotel_block + end + + def available_rooms_in_block(hotel_block) + available_rooms = hotel_block.rooms.select{|room| room.reservations.empty? == true || room.reservations.any?{|reservation| reservation.date_range.overlap?(hotel_block.date_range) == false}} + return available_rooms + end + + def add_reservation_to_room_in_block(hotel_block) + available_rooms = available_rooms_in_block(hotel_block) + raise NoAvailableRoomError.new("there are no available rooms left in this block")if available_rooms.empty? == true + chosen_room = available_rooms[0] + new_reservation = Hotel::Reservation.new(date_range: hotel_block.date_range, room: chosen_room) + new_reservation.block_reservation = true + @reservations << new_reservation + chosen_room.add_room_reservation(new_reservation) + return new_reservation + end + end +end diff --git a/lib/hotel_block.rb b/lib/hotel_block.rb new file mode 100644 index 000000000..c655da3e7 --- /dev/null +++ b/lib/hotel_block.rb @@ -0,0 +1,19 @@ +module Hotel + class HotelBlock + attr_reader :block_count, :date_range, :discount_cost + attr_accessor :rooms + + def initialize(block_count:, date_range:, discount_cost:) + @block_count = block_count # I interpreted the user story "collection of rooms" to mean I can pass it a + #count and the count determines how many rooms to request for the block. + @date_range = date_range + @discount_cost = discount_cost + @rooms = [] + + raise ArgumentError.new("Block count must be an integer between 2 and 5 inclusive") if @block_count.between?(2,5) == false + + end + + end + +end diff --git a/lib/reservations.rb b/lib/reservations.rb new file mode 100644 index 000000000..9b60dde9a --- /dev/null +++ b/lib/reservations.rb @@ -0,0 +1,14 @@ + +module Hotel + class Reservation + attr_reader :date_range, :room + attr_accessor :block_reservation + + def initialize(date_range:, room: nil) + @date_range = date_range + @room = room + @block_reservation = block_reservation + end + + end +end diff --git a/lib/room.rb b/lib/room.rb new file mode 100644 index 000000000..4a60595ad --- /dev/null +++ b/lib/room.rb @@ -0,0 +1,28 @@ + +module Hotel + class Room + attr_reader :room_number + attr_accessor :cost, :reservations, :discount_cost + + def initialize(room_number:, cost:, reservations: nil) + @room_number = room_number + + raise ArgumentError.new("There are only 20 rooms") if room_number < 1 || room_number > 20 + + @cost = 200 + @reservations = reservations || [] + @discount_cost = 0 + + end + + def add_room_reservation(reservation) + @reservations << reservation + end + + def change_cost(discount_cost) + self.discount_cost = discount_cost + return self + end + + end +end diff --git a/refactors.txt b/refactors.txt new file mode 100644 index 000000000..5ab41262f --- /dev/null +++ b/refactors.txt @@ -0,0 +1,6 @@ +Possible Refactors: +1. Only tracking reservation in either my front desk or my rooms, but not both +2. Refactor my block check status so that time and space are not O(n**2) +3. Write more test that cover more edge cases for the methods in my front desk +4. Don't track discount cost in my rooms + a. figure out a better way to account for discount costs for rooms in block \ No newline at end of file diff --git a/test/daterange_test.rb b/test/daterange_test.rb new file mode 100644 index 000000000..6e237161d --- /dev/null +++ b/test/daterange_test.rb @@ -0,0 +1,63 @@ +require_relative 'test_helper' + +describe "DateRange" do + before do + @start_date = Date.today + 2 + @end_date = Date.today + 6 + @date_range = Hotel::DateRange.new(start_date: @start_date, end_date: @end_date) + end + + describe "initialize DateRange" do + + it "creates a DateRange object" do + expect(@date_range).must_be_instance_of Hotel::DateRange + end + + it "raises ArgumentError if end date is before start date" do + expect{Hotel::DateRange.new(start_date: Date.today + 6, end_date: Date.today + 2)}.must_raise ArgumentError + end + + it "raises ArgumentError if end date and start date are not Date objects" do + expect{Hotel::DateRange.new(start_date: "March 4, 2020", end_date: "March 2, 2020")}.must_raise ArgumentError + end + + it "creates a range of the start and end dates" do + expect(@date_range.range).must_equal (@start_date..@end_date).to_a + end + end + + describe "overlap?" do + + it "returns true if there is overlap between date ranges" do + date_range2 = Hotel::DateRange.new(start_date: Date.today + 2, end_date: Date.today + 4) + date_range3 = Hotel::DateRange.new(start_date: Date.today + 3, end_date: Date.today + 6) + date_range4 = Hotel::DateRange.new(start_date: Date.today + 1, end_date: Date.today + 9) + date_range5 = Hotel::DateRange.new(start_date: Date.today + 1, end_date: Date.today + 6) + expect(@date_range.overlap?(date_range2)).must_equal true + expect(@date_range.overlap?(date_range3)).must_equal true + expect(@date_range.overlap?(date_range4)).must_equal true + expect(@date_range.overlap?(date_range5)).must_equal true + end + + it "returns false if the date is not within the range" do + date_range2 = Hotel::DateRange.new(start_date: Date.today + 6, end_date: Date.today + 9) + date_range3 = Hotel::DateRange.new(start_date: Date.today + 7, end_date: Date.today + 13) + date_range4 = Hotel::DateRange.new(start_date: Date.today + 1, end_date: Date.today + 2) + date_range5 = Hotel::DateRange.new(start_date: Date.today + 19, end_date: Date.today + 20) + date_range5 = Hotel::DateRange.new(start_date: Date.today , end_date: Date.today + 1) + expect(@date_range.overlap?(date_range2)).must_equal false + expect(@date_range.overlap?(date_range3)).must_equal false + expect(@date_range.overlap?(date_range4)).must_equal false + expect(@date_range.overlap?(date_range5)).must_equal false + end + + end + + describe "nights" do + + it "calculates the number of nights booked" do + expect(@date_range.nights).must_equal 4 + end + end + +end \ No newline at end of file diff --git a/test/front_desk_test.rb b/test/front_desk_test.rb new file mode 100644 index 000000000..0f5ee4490 --- /dev/null +++ b/test/front_desk_test.rb @@ -0,0 +1,217 @@ +require_relative 'test_helper' + +describe "front desk" do + before do + @front_desk = Hotel::FrontDesk.new + @dates = Hotel::DateRange.new(start_date: Date.today + 2, end_date: Date.today + 6) + @block_count = 3 + @discount_cost = 0.2 + end + + describe "initialize FrontDesk" do + + it "creates a FrontDesk object" do + expect(@front_desk).must_be_instance_of Hotel::FrontDesk + end + + it "stores all 20 hotel rooms" do + expect(@front_desk.rooms.count).must_equal 20 + end + + it "returns an array of the 20 rooms" do + expect(@front_desk.rooms).must_be_instance_of Array + end + + it "stores instances of the Room class inside the array" do + expect(@front_desk.rooms.sample).must_be_instance_of Hotel::Room + expect(@front_desk.rooms.sample).must_be_instance_of Hotel::Room + expect(@front_desk.rooms.sample).must_be_instance_of Hotel::Room + end + end + + describe "add_reservation" do + before do + @front_desk.add_reservation(@dates) + end + + it "raises exception when there are no available rooms for a given date range" do + 19.times do + @front_desk.add_reservation(@dates) + end + + expect{@front_desk.add_reservation(@dates)}.must_raise NoAvailableRoomError + + end + + it "adds an instance of the room class to the reservation" do + expect(@front_desk.reservations[0].room).must_be_instance_of Hotel::Room + end + + it "does not add a reservation to a room if the room is not in a block" do + @front_desk.request_block(3, @dates, 180) + @front_desk.add_reservation(@dates) + expect(@front_desk.reservations[1].room.room_number).must_equal 5 + end + + it "adds a reservation object to collection of reservations" do + expect(@front_desk.reservations[0]).must_be_instance_of Hotel::Reservation + end + + it " adds the reservation to the room object" do + expect(@front_desk.reservations[0].room.reservations[0]).must_be_instance_of Hotel::Reservation + end + + end + + describe "available_rooms" do + + it "returns an array of all available rooms given a date range" do + expect(@front_desk.available_rooms(@dates)).must_be_instance_of Array + end + + it "has room objects contained in the array" do + expect(@front_desk.available_rooms(@dates).sample).must_be_instance_of Hotel::Room + end + + it "returns array of all rooms for a given date range if no reservations exist for any room" do + expect(@front_desk.available_rooms(@dates).count).must_equal 20 + expect(@front_desk.available_rooms(@dates)[0].room_number).must_equal 1 + end + + it "returns array of all rooms for a given date range that don't overlap the date range" do + @front_desk.add_reservation(@dates) + expect(@front_desk.available_rooms(@dates).count).must_equal 19 + expect(@front_desk.available_rooms(@dates)[0].room_number).must_equal 2 + + @front_desk.add_reservation(@dates) + + expect(@front_desk.available_rooms(@dates).count).must_equal 18 + expect(@front_desk.available_rooms(@dates)[0].room_number).must_equal 3 + + new_dates = Hotel::DateRange.new(start_date: Date.today + 6, end_date: Date.today + 10) + @front_desk.add_reservation(new_dates) + + expect(@front_desk.available_rooms(@dates).count).must_equal 18 + expect(@front_desk.available_rooms(@dates)[0].room_number).must_equal 3 + + test_dates = Hotel::DateRange.new(start_date: Date.today + 1, end_date: Date.today + 2) + expect(@front_desk.available_rooms(test_dates).count).must_equal 20 + expect(@front_desk.available_rooms(test_dates)[0].room_number).must_equal 1 + end + + end + + describe "find_reservation_with" do + before do + @room = @front_desk.rooms[0] + end + + it "returns an array of reservations that match the date" do + expect(@front_desk.find_reservation_with(room: @room, date_range: @dates)).must_be_instance_of Array + end + + it "returns an empty array if there are no reservations" do + expect(@front_desk.find_reservation_with(room: @room, date_range: @dates).sample).must_equal nil + end + + it "returns reservations if there are reservations for given room and date range" do + @front_desk.add_reservation(@dates) + expect(@front_desk.find_reservation_with(room: @room, date_range: @dates).sample).must_be_instance_of Hotel::Reservation + end + + it "returns reservations if there are reservations for given date range" do + @front_desk.add_reservation(@dates) + expect(@front_desk.find_reservation_with(date_range: @dates).sample).must_be_instance_of Hotel::Reservation + end + + end + + describe " total_cost" do + + it "calculates total cost of the reservation not in block " do + new_reservation = @front_desk.add_reservation(@dates) + expect(@front_desk.total_cost(new_reservation)).must_equal 800 + end + + it "calculates total cost of a reservation in a block " do + block1 = @front_desk.request_block( @block_count, @dates, @discount_cost) + block_reservation = @front_desk.add_reservation_to_room_in_block(block1) + expect(@front_desk.total_cost(block_reservation)).must_equal 640 + + end + end + + describe "request_block" do + + it "reserves the correct number of rooms for the hotel block for given date range" do + @front_desk.request_block( @block_count, @dates, @discount_cost) + + expect(@front_desk.hotel_blocks[0].rooms.count).must_equal 3 + + end + + it " raises exception if there aren't enough rooms to fill block for given date range" do + 19.times do + @front_desk.add_reservation(@dates) + end + + expect{@front_desk.request_block(@block_count, @dates, @discount_cost)}.must_raise NoAvailableRoomError + + end + + + it "does not add room to a block for a given date range if that room is already in a block" do + @front_desk.request_block( @block_count, @dates, @discount_cost) + + expect(@front_desk.request_block( @block_count, @dates, @discount_cost).rooms[0].room_number).must_equal 4 + expect(@front_desk.request_block( @block_count, @dates, @discount_cost).rooms[0].room_number).must_equal 7 + expect(@front_desk.request_block( @block_count, @dates, @discount_cost).rooms[0].room_number).must_equal 10 + + end + + end + + describe "check_block_status" do + + it "deletes rooms in block from available rooms if there is overlap" do + @front_desk.request_block( @block_count, @dates, @discount_cost) + new_dates = Hotel::DateRange.new(start_date: Date.today + 6, end_date: Date.today + 10) + + expect(@front_desk.check_block_status(@dates).count).must_equal 17 + expect(@front_desk.check_block_status(new_dates).count).must_equal 20 + + end + end + + describe "available_rooms_in_block" do + it "returns all rooms that are available for reservations" do + block1 = @front_desk.request_block( @block_count, @dates, @discount_cost) + expect(@front_desk.available_rooms_in_block(block1).count).must_equal 3 + expect(@front_desk.available_rooms_in_block(block1)[0].room_number).must_equal 1 + expect(@front_desk.available_rooms_in_block(block1)[1].room_number).must_equal 2 + expect(@front_desk.available_rooms_in_block(block1)[2].room_number).must_equal 3 + + @front_desk.add_reservation_to_room_in_block(block1) + expect(@front_desk.available_rooms_in_block(block1).count).must_equal 2 + expect(@front_desk.available_rooms_in_block(block1)[0].room_number).must_equal 2 + expect(@front_desk.available_rooms_in_block(block1)[1].room_number).must_equal 3 + end + end + + describe "add_reservation_to_room_in_block" do + it "adds a reservation to the first availavle room in the block" do + block1 = @front_desk.request_block( @block_count, @dates, @discount_cost) + @front_desk.add_reservation_to_room_in_block(block1) + expect(block1.rooms[0].reservations[0].date_range).must_equal @dates + + end + + it "raises exception if there are no rooms left to add a reservation to" do + block1 = @front_desk.request_block( @block_count, @dates, @discount_cost) + 3.times do + @front_desk.add_reservation_to_room_in_block(block1) + end + expect{@front_desk.add_reservation_to_room_in_block(block1)}.must_raise NoAvailableRoomError + end + end +end \ No newline at end of file diff --git a/test/hotel_block_test.rb b/test/hotel_block_test.rb new file mode 100644 index 000000000..e719fb5c6 --- /dev/null +++ b/test/hotel_block_test.rb @@ -0,0 +1,27 @@ +require_relative 'test_helper' + +describe "HotelBlock" do + before do + @start_date = Date.today + 2 + @end_date = Date.today + 6 + @date_range = Hotel::DateRange.new(start_date: @start_date, end_date: @end_date) + end + + describe "initialize HotelBlock" do + + it "creates a HotelBlock object" do + expect(Hotel::HotelBlock.new(block_count: 3, date_range: @date_range, discount_cost: 180)).must_be_instance_of Hotel::HotelBlock + end + + it "raises exception if block_count is not greater than 1 or less than 6" do + expect{Hotel::HotelBlock.new(block_count: 6, date_range: @date_range, discount_cost: 180).rooms.count}.must_raise ArgumentError + expect{Hotel::HotelBlock.new(block_count: 1, date_range: @date_range, discount_cost: 180).rooms.count}.must_raise ArgumentError + expect{Hotel::HotelBlock.new(block_count: 0, date_range: @date_range, discount_cost: 180).rooms.count}.must_raise ArgumentError + expect{Hotel::HotelBlock.new(block_count: -1, date_range: @date_range, discount_cost: 180).rooms.count}.must_raise ArgumentError + expect{Hotel::HotelBlock.new(block_count: "three", date_range: @date_range, discount_cost: 180).rooms.count}.must_raise ArgumentError + end + + end + +end + diff --git a/test/reservations_test.rb b/test/reservations_test.rb new file mode 100644 index 000000000..57844285e --- /dev/null +++ b/test/reservations_test.rb @@ -0,0 +1,23 @@ +require_relative 'test_helper' + +describe "reservations" do + before do + @new_reservation = Hotel::Reservation.new(date_range: Hotel::DateRange.new(start_date: Date.today + 2, end_date: Date.today + 6), room: Hotel::Room.new(room_number: 1, cost: 200)) + end + + describe "initialize reservations" do + + it "creates a Reservation object" do + expect(@new_reservation).must_be_instance_of Hotel::Reservation + end + + it "has an attribute of date_range that is an instance of Date_Range class" do + expect(@new_reservation.date_range).must_be_instance_of Hotel::DateRange + end + + it "has an attribute of room that is an instance of Room class" do + expect(@new_reservation.room).must_be_instance_of Hotel::Room + end + end + +end \ No newline at end of file diff --git a/test/rooms_test.rb b/test/rooms_test.rb new file mode 100644 index 000000000..c5ab9c633 --- /dev/null +++ b/test/rooms_test.rb @@ -0,0 +1,47 @@ +require_relative 'test_helper' + +describe "rooms" do + before do + @room = Hotel::Room.new(room_number: 1, cost: 200) + end + + describe "initializing room" do + + it "creates a Room object" do + expect(@room).must_be_instance_of Hotel::Room + end + + it "raises an arguement error if room number is less than 1 or more than 20" do + expect{Hotel::Room.new(room_number: 21, cost: 200)}.must_raise ArgumentError + expect{Hotel::Room.new(room_number: -1, cost: 200)}.must_raise ArgumentError + end + end + + describe "add_room_reservation" do + # TODO write a let statement of new reservatio and room.add_res + it "adds a reservation to a room object" do + expect(@room.reservations.empty?).must_equal true + + @new_reservation = Hotel::Reservation.new(date_range: Hotel::DateRange.new(start_date: Date.today + 2, end_date: Date.today + 6), room: Hotel::Room.new(room_number: 1, cost: 200)) + @room.add_room_reservation(@new_reservation) + expect(@room.reservations.count).must_equal 1 + end + + it "adds and reservation object to the collection of reservations in the room" do + @new_reservation = Hotel::Reservation.new(date_range: Hotel::DateRange.new(start_date: Date.today + 2, end_date: Date.today + 6), room: Hotel::Room.new(room_number: 1, cost: 200)) + @room.add_room_reservation(@new_reservation) + expect(@room.reservations.empty?).must_equal false + expect(@room.reservations[0]).must_be_instance_of Hotel::Reservation + end + end + + describe "change_cost" do + it "changes the discount cost rate" do + expect(@room.discount_cost).must_equal 0 + + discount_cost = 0.2 + @room.change_cost(discount_cost) + expect(@room.discount_cost).must_equal discount_cost + end + end +end \ No newline at end of file diff --git a/test/test_helper.rb b/test/test_helper.rb index c3a7695cf..1fe262d7d 100644 --- a/test/test_helper.rb +++ b/test/test_helper.rb @@ -1,8 +1,20 @@ # Add simplecov +require "simplecov" +SimpleCov.start do + add_filter 'test/' +end require "minitest" require "minitest/autorun" require "minitest/reporters" +require "minitest/skip_dsl" +require "date" Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new # require_relative your lib files here! +require_relative "../lib/date_range" +require_relative "../lib/front_desk" +require_relative "../lib/reservations" +require_relative "../lib/room" +require_relative "../lib/hotel_block" +require_relative '../lib/No_Available_Room_Error.rb'