require 'quick_travel/adapter'
require 'quick_travel/passenger'
require 'quick_travel/vehicle'
require 'quick_travel/payment'
require 'quick_travel/payment_type'
require 'quick_travel/price_changes'
require 'uri'
module QuickTravel
class Booking < Adapter
def self.api_base
'/api/bookings'
end
def self.find_by_reference(reference)
find_all!("#{api_base}/reference/#{URI.encode_www_form_component(reference)}.json").first
end
def documents(regenerate = false)
Document.find_all!("#{Booking.api_base}/#{@id}/documents.json",
last_group: true, regenerate: regenerate)
end
has_many :reservations
has_many :passengers
has_many :vehicles
has_many :payments
has_many :payment_types
def on_account_payment_type
payment_types_by_code = payment_types.group_by(&:code)
# Try on-account, or if not, on-account-with-reference
payment_types_by_code['on_account_without_reference'].try(:first) ||
payment_types_by_code['on_account_with_reference'].try(:first)
end
def country
Country.find(@country_id) if @country_id
end
# Create an empty booking
#
# Note, options pertain to initializing booking with some values:
#
# options = {
# :when => "28-04-2010" ,
# :passengers => { "1" => nil, "2" => nil, "3" => nil, "4" => nil , "5" => nil } ,
# :include_vehicle => nil , :vehicle => { :vehicle_type_id => nil , :length , :weight } ,
# :vehicle_return_weight => nil ,
# :include_trailer => nil ,
# :trailer => { :vehicle_type_id => nil , :length => nil }
# }
def self.create(options = {})
response = post_and_validate("#{api_base}.json", booking: options)
fail AdapterError.new(response) unless response['id']
return nil unless response['id']
Booking.new(response)
end
# Update an existing booking
def update(attrs = {}, options = {})
response_object = put_and_validate("#{api_base}/#{@id}.json", options.merge(booking: attrs), return_response_object: true)
response = response_object.parsed_response
# id is returned if other attributes change otherwise success: true
fail AdapterError.new(response) unless response_object.no_content? || response['id'] || response['success']
Booking.find(@id)
end
# ###
# Updates:
# - Client information
# - Client contact
# - Client address
# - Pax details
# - Vehicle details
def update_with_nested_attributes!(booking_args = {})
response = put_and_validate("#{api_base}/#{@id}/update_with_nested_attributes.json",
booking: booking_args)
fail AdapterError.new(response) unless response['id']
Booking.find(response['id'])
end
def remove_unassigned_passengers
put_and_validate("/api/bookings/#{@id}/remove_unassigned_passengers")
end
# Create an accommodation reservation from the given options
#
# Returns current booking object after having added the reservation
#
# reservations_options = {
# :id => nil ,
# :first_travel_date => nil,
# :last_travel_date => nil,
# :resource_id => nil ,
# :passenger_ids => {} ,
# :vehicle_ids => {} ,
# :bed_configuration_id => nil ,
# :tariff_level_id => nil
# }
#
# Example 2:
# reservations_options = {
# :first_trave_date => "28-04-2010",
# :last_travel_date => "28-04-2010",
# :resource_id => 89,
# :bed_configuration_id => 581,
# :passenger_ids => {""=>nil},
# :vehicle_ids => {""=>nil}
# }
def accommodation_reserve(reservations_options = {})
if reservations_options[:last_travel_date].nil?
fail AdapterError.new('No checkout date specified')
end
reserve('accommodations/create_or_update', reservations: reservations_options)
end
# Reserve a scheduled trips resource
# Returns current booking object after having added the reservation
#
# Note:
# Forward_options and return_options look similar, but each is a different trip
#
# Example:
# forward_options = {
# :passenger_ids => {} ,
# :vehicle_ids => {} ,
# :first_travel_date => nil,
# :tariff_level_id => nil ,
# :resource_id => nil ,
# :trip_id => nil
# }
def scheduled_trips_reserve(segments = {})
reserve(:scheduled_trips, segments.merge(segments: segments.keys))
end
def scheduled_trips_update(segments = {})
params = segments.merge(segments: segments.keys)
params[:booking_id] = @id
put_and_validate('/reservation_for/scheduled_trips/bulk_update.json', params)
Booking.find(@id)
end
def generics_reserve(options)
reserve(:generics, options)
end
def item_reserve(options)
reserve(:items, options)
end
def tour_reserve(options)
reserve(:packages, options)
end
# Delete a reservation
#
# Returns current booking object after deleting the reservation
def delete_reservation(reservation)
delete_reservation_by_id(reservation.id)
end
def delete_reservation_by_id(reservation_id)
if state != 'new'
fail AdapterError.new('Reservation cannot be deleted unless the booking is new')
end
delete_and_validate("#{api_base}/#{@id}/reservations/#{reservation_id}.json")
refresh!
end
def refresh!
Booking.find(@id) # refresh
end
def find_passenger_by_id(pid)
passengers.detect { |p| p.id.to_i == pid.to_i }
end
def find_vehicle_by_id(vid)
vehicles.detect { |v| v.id.to_i == vid.to_i }
end
def passengers
@passenger_objects ||= @passengers_attributes.map{ |p| Passenger.new(p) }
end
def passenger_types_counts
passengers.each_with_object(Hash.new(0)) do |passenger, hash|
hash[passenger.passenger_type_id] += 1
end
end
def client_address
return nil unless @client_address
@client_address_object ||= Address.new(@client_address)
end
def client_party
return nil unless @client_party
@client_party_object ||= Party.new(@client_party)
end
def client_contact
return nil unless @client_contact
@client_contact_object ||= Contact.new(@client_contact)
end
def client
return nil unless @client
@client_object ||= Client.new(@client)
end
def calculate_existing_and_new_vehicles_for(required_vehicle_types)
return [[], []] if required_vehicle_types.blank?
vehicle_types_to_add = []
existing_vehicle_ids_to_assign = []
vehicles_yet_to_include = try(:vehicles)
required_vehicle_types.each do |searched_vehicle_type_hash|
matching_existing_vehicle = vehicles_yet_to_include.detect { |veh| veh.vehicle_type_id.to_i == searched_vehicle_type_hash[:vehicle_type_id].to_i }
if matching_existing_vehicle
existing_vehicle_ids_to_assign << matching_existing_vehicle.id.to_i
vehicles_yet_to_include -= [matching_existing_vehicle]
else
vehicle_types_to_add << searched_vehicle_type_hash
end
end
[existing_vehicle_ids_to_assign, vehicle_types_to_add]
end
def include_reservation_of?(product_type_id)
reservations.any? { |r| r.product_type_id.to_i == product_type_id }
end
def includes_resource_class?(resource_class_name)
reservations.any? { |r| r.resource_class_name == resource_class_name }
end
def clear_unfinished_reservations!
booking = self
reservations.reject(&:complete).each do |reservation|
# This will return updated booking..
booking = delete_reservation(reservation)
end
booking
end
def finalised?
(balance_in_cents == 0 && state != 'new' && !reservations.empty?) || state == 'quote'
end
def price_change
@price_change ||= fetch_price_change
end
def price_change_on(reservation)
price_change.price_change_on(reservation.id)
end
def total_price_change_on(reservation)
price_change.total_price_change_on(reservation.id)
end
# DEPRECATED:
# Please use PriceQuote.calculate(params.merge(booking_id: booking.id)) instead.
def calculate_price_quote(params = {})
response = post_and_validate("#{api_base}/#{@id}/price_quotes/calculate", params)
Money.new(response['quoted_booking_gross_in_cents'])
end
def activate!
put_and_validate("#{api_base}/#{@id}/activate")
end
def cancel!
put_and_validate("#{api_base}/#{@id}/cancel")
end
def subscribe_to_email_list(email = nil)
params = email.nil? ? {} : { email: email }
put_and_validate("#{api_base}/#{@id}/subscribe_to_email_list", params)
end
# secureAccessCodeString is simply a MAC algorithm (using SHA1) on the booking id string.
# it is using @id that is booking.id and configuration constant QUICK_TRAVEL_ACCESS_KEY
def secure_access_code
Encrypt.access_key(@id.to_s)
end
def customer_comments
comment = comments.detect{ |comment| comment['comment_type'] == 'customer' }
comment.presence.try(:[], 'text') || ''
end
protected
def reserve(url, options)
options[:booking_id] = @id
post_and_validate("/reservation_for/#{url}.json", options)
Booking.find(@id)
end
private
def fetch_price_change
attributes = get_and_validate("#{api_base}/#{@id}/price_change.json")
QuickTravel::PriceChanges::BookingPriceChange.new(attributes)
end
end
end