diff --git a/Rakefile b/Rakefile new file mode 100644 index 000000000..deb52f2cd --- /dev/null +++ b/Rakefile @@ -0,0 +1,9 @@ +require 'rake/testtask' + +Rake::TestTask.new do |t| + t.libs = ["lib"] + t.warning = true + t.test_files = FileList['specs/*_spec.rb'] +end + +task default: :test diff --git a/lib/block.rb b/lib/block.rb new file mode 100644 index 000000000..98e828661 --- /dev/null +++ b/lib/block.rb @@ -0,0 +1,19 @@ +module Hotel + + class Block + + attr_reader :block_id, :party, :start_date, :end_date + + def initialize(input) + @block_id = input[:block_id] + @party = input[:party] + @start_date = input[:start_date] + @end_date = input[:end_date] + + if @end_date < @start_date + raise ArgumentError.new("Start date must be before end date.") + end + end + + end +end diff --git a/lib/front_desk.rb b/lib/front_desk.rb new file mode 100644 index 000000000..778693c77 --- /dev/null +++ b/lib/front_desk.rb @@ -0,0 +1,129 @@ +require 'date' + +require_relative 'room' +require_relative 'reservation' +require_relative 'block' + + +module Hotel + class FrontDesk + attr_reader :rooms, :reservations, :blocked_rooms + + def initialize + @rooms = load_rooms + @reservations = [] + @blocked_rooms = [] + end + + def find_room(room_number) + check_room_number(room_number) + @rooms.find{ |room| room.room_number == room_number } + end + + def load_rooms + all_rooms = [] + 20.times do |room_index| + all_rooms << Hotel::Room.new(room_number: room_index + 1) + end + return all_rooms + + end + + def store_occupied_rooms(start_date, end_date) + occupied_rooms = [] + range = (start_date...end_date) + + @reservations.each do |reservation| + if (range.include? reservation.start_date) || (range.include? reservation.end_date - 1) + occupied_rooms << reservation.room_number + end + end + return occupied_rooms + end + + def get_available_rooms(start_date, end_date) + @available_rooms = [] + @rooms.each do |room| + if !store_occupied_rooms(start_date, end_date).include? room + @available_rooms << room + end + end + return @available_rooms + end + + def first_available_room(start_date, end_date) + first_available_room = get_available_rooms(start_date, end_date).first + return first_available_room + end + + def reserve_room(input) + room = first_available_room(input[:start_date], input[:end_date]) + + reservation_data = { + reservation_id: reservations.length + 1, + block_id: input[:block_id], + room: room, + start_date: input[:start_date], + end_date: input[:end_date], + } + + new_reservation = Reservation.new(reservation_data) + + room.add_reservation(new_reservation) + @reservations << new_reservation + p new_reservation + return new_reservation + end + + def get_reservation_list(specified_date) + reservations_on_date = [] + @reservations.each do |reservation| + if reservation.date_range.include? specified_date + reservations_on_date << reservation + end + end + return reservations_on_date + end + + def get_available_blocked_rooms(number_of_rooms, start_date, end_date) + if number_of_rooms > 5 + raise ArgumentError.new("Cannot block more than 5 rooms!") + end + + # if get_available_rooms(start_date, end_date) < number_of_rooms + # raise ArgumentError.new("There are not enough rooms available for the requested dates") + # end + + available_block = (get_available_rooms(start_date, end_date)).take(number_of_rooms) + # p available_block + return available_block + end + + def block_rooms(number_of_rooms, start_date, end_date) + + party = get_available_blocked_rooms(number_of_rooms, start_date, end_date) + + block_data = { + block_id: @blocked_rooms.length + 1, + party: party, + start_date: start_date, + end_date: end_date, + } + + new_block = Block.new(block_data) + + @blocked_rooms << new_block + # p "++++TEST++++" + # p @blocked_rooms + return new_block + end + + private + + def check_room_number(room_number) + if room_number == nil || room_number < 1 || room_number > 20 + raise ArgumentError.new("Room number cannot be blank or less than zero. (got #{room_number})") + end + end + end +end diff --git a/lib/reservation.rb b/lib/reservation.rb new file mode 100644 index 000000000..3c70c7b10 --- /dev/null +++ b/lib/reservation.rb @@ -0,0 +1,47 @@ +# require 'csv' + + +module Hotel + RATE_PER_NIGHT = 200 + BLOCK_DISCOUNT = 0.80 + + class Reservation + + attr_reader :reservation_id, :room_number, :start_date, :end_date, :cost, :duration + + def initialize(input) + @reservation_id = input[:reservation_id] + @block_id = input[:block_id] == nil ? nil : input[:block_id] + @room_number = input[:room_number] + @start_date = (input[:start_date]) + @end_date = (input[:end_date]) + @cost = calculate_cost + + if @end_date < @start_date + raise ArgumentError.new("Start date must be before end date.") + end + end + + def date_range + @range = (@start_date...@end_date) + return @range + end + + def duration + duration = ((@end_date - 1) - @start_date) + # Convert Rational to Integer + duration = duration.to_i + return duration + end + + def calculate_cost + if @block_id == nil + cost = RATE_PER_NIGHT * duration + else + cost = (RATE_PER_NIGHT * duration) * BLOCK_DISCOUNT + end + return cost + end + + end +end diff --git a/lib/room.rb b/lib/room.rb new file mode 100644 index 000000000..35560178a --- /dev/null +++ b/lib/room.rb @@ -0,0 +1,26 @@ +require_relative 'reservation' + +module Hotel + class Room + attr_reader :room_number, :reservations + + def initialize(input) + if input[:room_number] == nil || input[:room_number] <= 0 + raise ArgumentError.new("ID cannot be blank or less than zero. (got #{input[:id]})") + end + + @room_number = input[:room_number] + + @reservations = input[:reservations] == nil ? [] : input[:reservations] + end + + def add_reservation(reservation) + if reservation.class != Reservation + raise ArgumentError.new("Can only add reservation instance to reservation collection") + end + + @reservations << reservation + + end + end +end diff --git a/specs/block_speck.rb b/specs/block_speck.rb new file mode 100644 index 000000000..6be86ec8c --- /dev/null +++ b/specs/block_speck.rb @@ -0,0 +1,46 @@ +require_relative 'spec_helper' + +describe "Block class" do + + before do + start_date = Date.new(2018, 03, 06) + end_date = start_date + 5 # 5 days after start date + number_of_rooms = 5 + + @block_data = { + block_id: 8, + party: Hotel::Room.new(room_number: 3), + start_date: start_date, + end_date: end_date, + } + + @block = Hotel::Block.new(@block_data) + end + + + describe "initialize" do + it "is an instance of Block" do + @block.must_be_kind_of Hotel::Block + end + + before do + start_date = Date.new(2018, 03, 06) + end_date = start_date - 5 # 5 days before start date + + @reservation_data = { + block_id: 8, + party: Hotel::Room.new(room_number: 3), + start_date: start_date, + end_date: end_date, + } + end + + it "raises an error if the end date is before the start date" do + + proc {Hotel::Reservation.new(@reservation_data)}.must_raise ArgumentError + end + # + end + + +end diff --git a/specs/front_desk_spec.rb b/specs/front_desk_spec.rb new file mode 100644 index 000000000..960885bc2 --- /dev/null +++ b/specs/front_desk_spec.rb @@ -0,0 +1,209 @@ +require_relative 'spec_helper' + +describe "FrontDesk class" do + + before do + @front_desk = Hotel::FrontDesk.new + end + + describe "Initializer" do + it "is an instance of FrontDesk" do + skip + @front_desk.must_be_kind_of Hotel::FrontDesk + end + + it "establishes the base input structures when instantiated" do + skip + [:rooms, :reservations].each do |prop| + @front_desk.must_respond_to prop + end + @front_desk.rooms.must_be_kind_of Array + @front_desk.reservations.must_be_kind_of Array + end + end + + describe "find_room method" do + + it "throws an argument error for a bad room_number" do + skip + proc{ @front_desk.find_room(0) }.must_raise ArgumentError + end + + it "throws an argument error for a bad room_number" do + skip + proc{ @front_desk.find_room(21) }.must_raise ArgumentError + end + + it "finds a room instance" do + skip + room = @front_desk.find_room(2) + room.must_be_kind_of Hotel::Room + end + end + + describe "loader methods" do + it "accurately loads room information into rooms array" do + skip + + first_room = @front_desk.rooms.first + last_room = @front_desk.rooms.last + + first_room.room_number.must_equal 1 + last_room.room_number.must_equal 20 + end + end + + before do + @front_desk = Hotel::FrontDesk.new + @start_date = Date.new(2018, 03, 06) + @end_date = @start_date + 5 + @input = {start_date: @start_date, end_date: @end_date, } + @front_desk.reserve_room(@input) + + end + + describe "store_occupied_rooms method" do + it "takes reservations and stores them in an occupied array" do + skip + + occupied_rooms = @front_desk.store_occupied_rooms(@start_date, @end_date) + + occupied_rooms.must_be_kind_of Array + occupied_rooms.length.must_equal 1 + + end + end + + describe "testing get_available_rooms method" do + it "returns an array of all available rooms for given range" do + skip + @front_desk.reserve_room(@input) + @front_desk.get_available_rooms(@start_date, @end_date).length.must_equal 19 + end + + it "returns an empty array if no rooms available for a given date range" do + skip + + 20.times do + @front_desk.reserve_room(@input) + end + + @front_desk.get_available_rooms(@start_date, @end_date).must_equal [] + @front_desk.get_available_rooms(@start_date, @end_date).must_be_empty + end + end + + describe "reserve_room method" do + it "reserves a room if available" do + skip + + @front_desk.reserve_room(@input).must_be_instance_of Hotel::Reservation + end + + it "adds new reservation to reservations array" do + skip + initial_length = @front_desk.reservations.length + @front_desk.reserve_room(@input) + new_length = @front_desk.reservations.length + + new_length.must_equal initial_length + 1 + end + end + + before do + @front_desk = Hotel::FrontDesk.new + @start_date = Date.new(2018, 03, 06) + @end_date = @start_date + 5 + @input = {start_date: @start_date, end_date: @end_date, } + @specified_date = Date.new(2018, 03, 06) + end + + describe "list_reservations method" do + + it "lists the reservations if they exist" do + skip + @front_desk.reserve_room(@input) + reservations_by_date = @front_desk.get_reservation_list(@specified_date) + + reservations_by_date.must_be_kind_of Array + reservations_by_date.first.must_be_instance_of Hotel::Reservation + + end + + it "tests multiple reservations for same date" do + skip + front_desk = Hotel::FrontDesk.new + + start_date = Date.new(2018, 03, 06) + end_date = start_date + 5 + + front_desk.reserve_room(@input) + + start_date = Date.new(2018, 03, 07) + end_date = start_date + 5 + + front_desk.reserve_room(@input) + + specified_date = Date.new(2018, 03, 07) + + reservations_by_date = front_desk.get_reservation_list(specified_date) + + reservations_by_date.length.must_equal 2 + end + end + + + + describe "block_rooms method" do + + before do + @front_desk = Hotel::FrontDesk.new + @number_of_rooms = 5 + @start_date = Date.new(2018, 03, 06) + @end_date = @start_date + 5 + @input = {start_date: @start_date, end_date: @end_date, } + + end + + # it "throws an argument error for unavailable number of rooms to block" do + # proc{ @front_desk.block_rooms(0) }.must_raise ArgumentError + # end + + it "blocks rooms if available" do + @front_desk.reserve_room(@input) + @front_desk.block_rooms(@number_of_rooms, @start_date, @end_date).must_be_instance_of Hotel::Block + end + end + + describe "add_reservation from blocked room method" do + before do + @front_desk = Hotel::FrontDesk.new + @number_of_rooms = 5 + @start_date = Date.new(2018, 03, 06) + @end_date = @start_date + 5 + @input = {block_id: 1, start_date: @start_date, end_date: @end_date } + end + + it "adds a reservation with a block_id" do + @front_desk.block_rooms(@number_of_rooms, @start_date, @end_date) + reserve_room_with_block_id = @front_desk.reserve_room(@input) + reserve_room_with_block_id.must_be_instance_of Hotel::Reservation + end + + it "adds new block to blocks array" do + initial_length = @front_desk.blocked_rooms.length + @front_desk.block_rooms(@number_of_rooms, @start_date, @end_date) + new_length = @front_desk.blocked_rooms.length + + new_length.must_equal initial_length + 1 + end + + it "adds a second reservation with a block_id to the next available room in the block" do + @front_desk.block_rooms(@number_of_rooms, @start_date, @end_date) + @front_desk.reserve_room(@input) + @front_desk.reserve_room(@input) + + end + end + +end diff --git a/specs/reservation_spec.rb b/specs/reservation_spec.rb new file mode 100644 index 000000000..34a091104 --- /dev/null +++ b/specs/reservation_spec.rb @@ -0,0 +1,82 @@ +require_relative 'spec_helper' + +describe "Reservation class" do + + before do + start_date = Date.new(2018, 03, 06) + end_date = start_date + 5 # 5 days after start date + + @reservation_data = { + id: 8, + room_number: Hotel::Room.new(room_number: 3), + start_date: start_date, + end_date: end_date, + } + + @reservation = Hotel::Reservation.new(@reservation_data) + # binding.pry + + end + + + describe "initialize" do + # + it "is an instance of Reservation" do + @reservation.must_be_kind_of Hotel::Reservation + end + # + it "stores an instance of room" do + @reservation.room_number.must_be_kind_of Hotel::Room + end + # + before do + start_date = Date.new(2018, 03, 06) + end_date = start_date - 5 # 5 days before start date + + @reservation_data = { + id: 8, + room_number: Hotel::Room.new(room_number: 3), + start_date: start_date, + end_date: end_date, + } + + end + + it "raises an error if the end date is before the start date" do + + proc {Hotel::Reservation.new(@reservation_data)}.must_raise ArgumentError + end + # + end + + describe "The Range Method" do + it "calculates the range of a reservation in nights" do + reservation_date_range = @reservation.date_range + reservation_date_range.must_be_kind_of Range + end + end + + describe "The Duration Method" do + it "calculates the duration of a reservation in nights stayed" do + + reservation_length = @reservation.duration + reservation_length.must_be_kind_of Integer + # 5 days/4 nights long + reservation_length.must_equal 4 + + end + end + + describe "The Calculate Cost Method" do + it "calculates the cost of a reservation" do + + reservation_cost = @reservation.calculate_cost + reservation_cost.must_be_kind_of Integer + # Cost is 200 * 4 nights + reservation_cost.must_equal 800 + + end + end + + +end diff --git a/specs/room_spec.rb b/specs/room_spec.rb new file mode 100644 index 000000000..fe1e99dd6 --- /dev/null +++ b/specs/room_spec.rb @@ -0,0 +1,55 @@ +require_relative 'spec_helper' + +describe "Room class" do + + describe "Room instantiation" do + before do + @room = Hotel::Room.new(room_number: 1) + end + + it "is an instance of Room" do + @room.must_be_kind_of Hotel::Room + end + + it "throws an argument error with a bad ID value" do + proc{ Hotel::Room.new(room_number: 0)}.must_raise ArgumentError + end + + it "sets reservations to an empty array if not provided" do + @room.reservations.must_be_kind_of Array + @room.reservations.length.must_equal 0 + end + + it "is set up for specific attributes and data types" do + [:room_number].each do |prop| + @room.must_respond_to prop + end + + # [:id, :status].each do |prop| + # @room.must_respond_to prop + # end + + @room.room_number.must_be_kind_of Integer + # @room.status.must_be_kind_of Symbol + end + end + + describe "add reservation method" do + before do + @room = Hotel::Room.new(room_number: 3) + @reservation = Hotel::Reservation.new({room_number: 8, room: @room, start_date: Date.new(2018, 03, 06), end_date: Date.new(2018, 03, 11)}) + end + + it "throws an argument error if reservation is not provided" do + proc{ @room.add_reservation(1) }.must_raise ArgumentError + end + + it "increases the reservation count by one" do + previous = @room.reservations.length + @room.add_reservation(@reservation) + @room.reservations.length.must_equal previous + 1 + end + end + + +end diff --git a/specs/spec_helper.rb b/specs/spec_helper.rb new file mode 100644 index 000000000..a9be4f801 --- /dev/null +++ b/specs/spec_helper.rb @@ -0,0 +1,22 @@ +# require 'simplecov' +# SimpleCov.start + +require 'simplecov' +SimpleCov.start + +require 'date' + +require 'pry' +require 'awesome_print' + +require 'minitest' +require 'minitest/autorun' +require 'minitest/reporters' + + +Minitest::Reporters.use! Minitest::Reporters::SpecReporter.new + +# Require_relative your lib files here! +require_relative '../lib/front_desk' +require_relative '../lib/room' +require_relative '../lib/reservation'