hostconnect 0.1.7
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGELOG +8 -0
- data/MIT-LICENSE +22 -0
- data/README.markdown +16 -0
- data/Rakefile +48 -0
- data/hostconnect.gemspec +44 -0
- data/lib/hostconnect/builder.rb +50 -0
- data/lib/hostconnect/builders/add_service_builder.rb +60 -0
- data/lib/hostconnect/builders/agent_info_builder.rb +19 -0
- data/lib/hostconnect/builders/get_booking_builder.rb +24 -0
- data/lib/hostconnect/builders/get_booking_payment_summary_builder.rb +20 -0
- data/lib/hostconnect/builders/get_locations_builder.rb +18 -0
- data/lib/hostconnect/builders/get_services_builder.rb +18 -0
- data/lib/hostconnect/builders/get_system_settings_builder.rb +18 -0
- data/lib/hostconnect/builders/list_bookings_builder.rb +27 -0
- data/lib/hostconnect/builders/new_booking_info_builder.rb +23 -0
- data/lib/hostconnect/builders/option_info_builder.rb +49 -0
- data/lib/hostconnect/builders/pax_details_builder.rb +21 -0
- data/lib/hostconnect/builders/ping_builder.rb +11 -0
- data/lib/hostconnect/builders/record_booking_payment_builder.rb +25 -0
- data/lib/hostconnect/builders/room_config_builder.rb +25 -0
- data/lib/hostconnect/builders/service_line_note_builder.rb +17 -0
- data/lib/hostconnect/builders/suppler_info_builder.rb +27 -0
- data/lib/hostconnect/client.rb +32 -0
- data/lib/hostconnect/coercion.rb +50 -0
- data/lib/hostconnect/core_extensions/string.rb +18 -0
- data/lib/hostconnect/core_extensions/symbol.rb +9 -0
- data/lib/hostconnect/response.rb +119 -0
- data/lib/hostconnect/responses/add_service.rb +4 -0
- data/lib/hostconnect/responses/agent_info.rb +7 -0
- data/lib/hostconnect/responses/get_booking.rb +20 -0
- data/lib/hostconnect/responses/get_booking_payment_summary.rb +21 -0
- data/lib/hostconnect/responses/get_locations.rb +21 -0
- data/lib/hostconnect/responses/get_services.rb +21 -0
- data/lib/hostconnect/responses/get_system_settings.rb +24 -0
- data/lib/hostconnect/responses/list_bookings.rb +14 -0
- data/lib/hostconnect/responses/option_info.rb +60 -0
- data/lib/hostconnect/responses/ping.rb +7 -0
- data/lib/hostconnect/responses/record_booking_payment.rb +4 -0
- data/lib/hostconnect/responses/supplier_info.rb +31 -0
- data/lib/hostconnect/rtf_document.rb +19 -0
- data/lib/hostconnect/translation.rb +22 -0
- data/lib/hostconnect.rb +58 -0
- data/spec/builders/add_service_builder_spec.rb +36 -0
- data/spec/builders/agent_info_builder_spec.rb +13 -0
- data/spec/builders/get_locations_builder_spec.rb +13 -0
- data/spec/builders/new_booking_info_builder_spec.rb +34 -0
- data/spec/builders/option_info_builder_spec.rb +48 -0
- data/spec/builders/pax_details_builder_spec.rb +41 -0
- data/spec/builders/ping_builder_spec.rb +23 -0
- data/spec/builders/room_config_builder_spec.rb +60 -0
- data/spec/client_spec.rb +20 -0
- data/spec/coercion_spec.rb +85 -0
- data/spec/core_extensions_spec.rb +17 -0
- data/spec/fixtures/requests/add_service_request/hotel_booking.xml +42 -0
- data/spec/fixtures/requests/agent_info.xml +8 -0
- data/spec/fixtures/requests/get_locations.xml +8 -0
- data/spec/fixtures/requests/option_info/hotel_search.xml +11 -0
- data/spec/fixtures/requests/option_info/hotel_search_total_stay_price.xml +18 -0
- data/spec/fixtures/requests/option_info/option_number.xml +10 -0
- data/spec/fixtures/requests/ping.xml +5 -0
- data/spec/fixtures/responses/add_service.xml +11 -0
- data/spec/fixtures/responses/agent_info.xml +12 -0
- data/spec/fixtures/responses/agent_info_error.xml +7 -0
- data/spec/fixtures/responses/get_booking.xml +40 -0
- data/spec/fixtures/responses/get_locations.xml +48 -0
- data/spec/fixtures/responses/get_system_settings.xml +28 -0
- data/spec/fixtures/responses/option_info/hotel_price.xml +17 -0
- data/spec/fixtures/responses/option_info/hotel_search.xml +721 -0
- data/spec/fixtures/responses/option_info/hotel_search_with_stay_price.xml +470 -0
- data/spec/fixtures/responses/option_info/option_number.xml +14 -0
- data/spec/fixtures/responses/option_info/tours.xml +74 -0
- data/spec/fixtures/responses/ping.xml +8 -0
- data/spec/responses/add_service_spec.rb +13 -0
- data/spec/responses/agent_info_spec.rb +24 -0
- data/spec/responses/get_booking_spec.rb +33 -0
- data/spec/responses/get_locations_spec.rb +29 -0
- data/spec/responses/get_system_settings_spec.rb +21 -0
- data/spec/responses/option_info_spec.rb +48 -0
- data/spec/responses/ping_spec.rb +22 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/translation_spec.rb +18 -0
- metadata +259 -0
@@ -0,0 +1,50 @@
|
|
1
|
+
module HostConnect
|
2
|
+
# Coercion
|
3
|
+
#
|
4
|
+
# Possible Types:
|
5
|
+
# * String
|
6
|
+
# * Strings with multiple lines (don't know yet if these must be handled specifically)
|
7
|
+
# * Boolean (Y/N)
|
8
|
+
# * Date (yyyy-mm-dd)
|
9
|
+
# * Time (hhmm)
|
10
|
+
# * Integer
|
11
|
+
# * Integer list (In an integer list, a single space is used to separate integer values.)
|
12
|
+
# * Price (Prices returned are Tourplan prices multiplied by 100, + currency stuff..)
|
13
|
+
#
|
14
|
+
# It must be possible to convert this data from/to native Ruby objects
|
15
|
+
class Coercion
|
16
|
+
# Convert HostConnect formatted data to Ruby objects
|
17
|
+
def self.from_hc(data)
|
18
|
+
case data
|
19
|
+
when /^[0-9]+\d*$/ then data.to_i
|
20
|
+
when /^(\d+\s){2,}[0-9 ]*$/ then data.split.collect { |i| i.to_i }
|
21
|
+
when /^\s*$/ then nil
|
22
|
+
when /\d{4}-\d\d-\d\d/ then Date.parse(data)
|
23
|
+
when "Y" then true
|
24
|
+
when "N" then false
|
25
|
+
else data.strip
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
# Convert Ruby objects to HostConnect formatted data
|
30
|
+
def self.to_hc(data)
|
31
|
+
case data
|
32
|
+
when Integer then data.to_s
|
33
|
+
when Date then data.to_s
|
34
|
+
when nil then ""
|
35
|
+
when true then "Y"
|
36
|
+
when false then "N"
|
37
|
+
else data.strip
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Time strings are i.e "2310"
|
42
|
+
def self.time(string)
|
43
|
+
Time.parse(string[0,2] << ":" << string[2,4])
|
44
|
+
end
|
45
|
+
|
46
|
+
def self.price(string)
|
47
|
+
(string.to_f / 100).round
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
class String
|
2
|
+
def requestify
|
3
|
+
self.gsub(/Id$/, 'ID')
|
4
|
+
end
|
5
|
+
|
6
|
+
# Useful for heredocs - removes whitespace margin.
|
7
|
+
def margin
|
8
|
+
lines = self.dup.split($/)
|
9
|
+
|
10
|
+
min_margin = 0
|
11
|
+
lines.each do |line|
|
12
|
+
if line =~ /^(\s+)/ && (min_margin == 0 || $1.size < min_margin)
|
13
|
+
min_margin = $1.size
|
14
|
+
end
|
15
|
+
end
|
16
|
+
lines.map { |line| line.sub(/^\s{#{min_margin}}/, '') }.join($/)
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,119 @@
|
|
1
|
+
module HostConnect
|
2
|
+
# Abstract response class. Each subclass takes the XML responses and turns them into
|
3
|
+
# a basic datastructure, with each element available as an attribute.
|
4
|
+
class AbstractResponse
|
5
|
+
# Yields each element
|
6
|
+
def each
|
7
|
+
elements.each { |e| yield e }
|
8
|
+
end
|
9
|
+
|
10
|
+
# Returns the first element
|
11
|
+
def first
|
12
|
+
elements.first
|
13
|
+
end
|
14
|
+
|
15
|
+
# Returns the last element
|
16
|
+
def last
|
17
|
+
elements.last
|
18
|
+
end
|
19
|
+
|
20
|
+
# Act like an array
|
21
|
+
def [](num)
|
22
|
+
elements[num]
|
23
|
+
end
|
24
|
+
|
25
|
+
# Exposes the element-array of responses which consists of multiple elements.
|
26
|
+
# Raises an error if used on non-collection responses (Ping etc).
|
27
|
+
def elements
|
28
|
+
(@elements.blank?) ? raise(ArgumentError, "Not a collection") : @elements
|
29
|
+
end
|
30
|
+
|
31
|
+
protected
|
32
|
+
def parse(xml)
|
33
|
+
@data = Hpricot.XML xml
|
34
|
+
error_reply = @data.search("/Reply/ErrorReply/Error")
|
35
|
+
unless error_reply.blank?
|
36
|
+
error_msg = error_reply.innerHTML
|
37
|
+
error_code = error_msg[0..3].to_i
|
38
|
+
|
39
|
+
# See http://www.tourplan.com/support/Connector/ErrorMessages.html
|
40
|
+
error_description = case error_code
|
41
|
+
when 1000 then "General error."
|
42
|
+
when 1001 then "Missing input."
|
43
|
+
when 1002 then "Illegal input."
|
44
|
+
when 1003 then "Data communications link within iCom failed."
|
45
|
+
when 1004 then "A timeout occurred while an iCom component was waiting for something to happen."
|
46
|
+
when 1005 then "A database access error occurred within an iCom component."
|
47
|
+
when 1006 then "An error occurred when an iCom component attempted a file access of some sort."
|
48
|
+
when 1007 then "Data servlet cache not loaded."
|
49
|
+
when 1008 then "An error was reported by the iCom booking engine."
|
50
|
+
when 1009 then "The booking engine is currently off-line."
|
51
|
+
when 1040 then "No merchant account."
|
52
|
+
when 1050 then "Booking not found."
|
53
|
+
when 1051 then "Agent not found (or wrong password given)."
|
54
|
+
when 1052 then "Option not found."
|
55
|
+
when 1053 then "Service line not found."
|
56
|
+
when 1054 then "Operation is illegal on a read-only booking."
|
57
|
+
when 1055 then "Operation is illegal for an agent over their credit limit."
|
58
|
+
when 1056 then "Operation is illegal for this service line."
|
59
|
+
when 1057 then "Permission denied for this operation."
|
60
|
+
when 1058 then "The object the request is trying to create already exists."
|
61
|
+
when 1059 then "Supplier not found."
|
62
|
+
when 1060 then "Database record not found."
|
63
|
+
when 1061 then "Supplier exists."
|
64
|
+
when 1062 then "Option exists."
|
65
|
+
when 1063 then "Agent exists, but has no default currency (iS iCom only)."
|
66
|
+
when 1064 then "Operation is illegal on a non-iCom booking."
|
67
|
+
when 1065 then "Login failed; two or more agents have this login."
|
68
|
+
when 2000 then "Credit card expired."
|
69
|
+
when 2001 then "Credit card number invalid (is non-numeric, or fails validation check)."
|
70
|
+
when 2002 then "Card wrong type (card type does not match the specified card type)."
|
71
|
+
when 2006 then "Operation requested is not valid for a confirmed booking."
|
72
|
+
when 2007 then "Operation requested is not valid for a quote."
|
73
|
+
when 2008 then "Error accessing external card payment system."
|
74
|
+
when 2009 then "Transaction refused by external card payment system."
|
75
|
+
when 2010 then "All services already cancelled."
|
76
|
+
when 2011 then "Attempt to add service violates a MinSCU requirement at the option rate level."
|
77
|
+
when 2050 then "Connector refused request. No valid hostConnect or webConnect entry was found that permitted the specfied request by the specified agent from the source IP address of the request."
|
78
|
+
when 5000 then "The specified BookingUpdateCount is no longer valid; request rejected."
|
79
|
+
when 5001 then "The specified ServiceLineUpdateCount is no longer valid; request rejected."
|
80
|
+
else "Unknown error."
|
81
|
+
end
|
82
|
+
|
83
|
+
HostConnect.logger.fatal "Exception thrown: " + error_description
|
84
|
+
raise ArgumentError, error_msg << " (" << error_description << ")"
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
private
|
89
|
+
def initialize(xml)
|
90
|
+
parse xml
|
91
|
+
private_methods.include?("populate") ? populate : set_attrs
|
92
|
+
end
|
93
|
+
|
94
|
+
# Sets all instance variables. This only works for the simple requests.
|
95
|
+
# For convenience, query methods gets defined for booleans.
|
96
|
+
def set_attrs
|
97
|
+
reply_name = ActiveSupport::Inflector.demodulize(self.class) << "Reply"
|
98
|
+
@data.search("/Reply/" << reply_name) do |n|
|
99
|
+
n.containers.each do |e|
|
100
|
+
var = e.name.underscore
|
101
|
+
|
102
|
+
# Special treatment for variables that end with "_price"
|
103
|
+
if var =~ /_price$/
|
104
|
+
value = Coercion.price(Hpricot.uxs(e.innerHTML))
|
105
|
+
else
|
106
|
+
value = Coercion.from_hc(Hpricot.uxs(e.innerHTML))
|
107
|
+
end
|
108
|
+
|
109
|
+
if value.kind_of?(FalseClass) || value.kind_of?(TrueClass)
|
110
|
+
eval "def #{var}?; #{var}; end"
|
111
|
+
end
|
112
|
+
|
113
|
+
instance_variable_set("@#{var}", value)
|
114
|
+
self.class.class_eval "attr_reader :#{var}"
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module HostConnect
|
2
|
+
class GetBooking < AbstractResponse
|
3
|
+
private
|
4
|
+
def populate
|
5
|
+
set_attrs
|
6
|
+
|
7
|
+
@services = []
|
8
|
+
@data.search("/Reply/GetBookingReply/Services/Service").each do |service|
|
9
|
+
s = Struct.new(:service_line_id, :service_line_update_count, :opt,
|
10
|
+
:status).new
|
11
|
+
s.service_line_id = (service/"ServiceLineId").innerHTML.to_i
|
12
|
+
s.service_line_update_count = (service/"ServiceLineUpdateCount").innerHTML.to_i
|
13
|
+
s.opt = (service/"Opt").innerHTML
|
14
|
+
s.status = (service/"Status").innerHTML
|
15
|
+
|
16
|
+
@services << s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module HostConnect
|
2
|
+
class GetBookingPaymentSummary < AbstractResponse
|
3
|
+
private
|
4
|
+
def populate
|
5
|
+
@data.search("/Reply/GetBookingPaymentSummaryReply") do |n|
|
6
|
+
n.containers.each do |e|
|
7
|
+
var = e.name.underscore
|
8
|
+
|
9
|
+
if var == "currency"
|
10
|
+
value = Hpricot.uxs(e.innerHTML)
|
11
|
+
else
|
12
|
+
value = Coercion.price(Hpricot.uxs(e.innerHTML))
|
13
|
+
end
|
14
|
+
|
15
|
+
instance_variable_set("@#{var}", value)
|
16
|
+
self.class.class_eval "attr_reader :#{var}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module HostConnect
|
2
|
+
class GetLocations < AbstractResponse
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def size
|
6
|
+
@size ||= @data.search("/Reply/GetLocationsReply/Locations/Location").size
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def populate
|
11
|
+
@elements = []
|
12
|
+
|
13
|
+
@data.search("/Reply/GetLocationsReply/Locations/Location").each do |location|
|
14
|
+
s = Struct.new(:code, :name).new
|
15
|
+
s.code = (location/"Code").innerHTML
|
16
|
+
s.name = (location/"Name").innerHTML
|
17
|
+
@elements << s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,21 @@
|
|
1
|
+
module HostConnect
|
2
|
+
class GetServices < AbstractResponse
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def size
|
6
|
+
@size ||= @data.search("/Reply/GetServicesReply/TPLServices/TPLService").size
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def populate
|
11
|
+
@elements = []
|
12
|
+
|
13
|
+
@data.search("/Reply/GetServicesReply/TPLServices/TPLService").each do |location|
|
14
|
+
s = Struct.new(:code, :name).new
|
15
|
+
s.code = (location/"Code").innerHTML
|
16
|
+
s.name = (location/"Name").innerHTML
|
17
|
+
@elements << s
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module HostConnect
|
2
|
+
class GetSystemSettings < AbstractResponse
|
3
|
+
private
|
4
|
+
def populate
|
5
|
+
set_attrs
|
6
|
+
|
7
|
+
@countries = []
|
8
|
+
@data.search("/Reply/GetSystemSettingsReply/Countries/Country").each do |country|
|
9
|
+
c = Struct.new(:country_name, :destination_names).new
|
10
|
+
c.country_name = (country/"CountryName").innerHTML
|
11
|
+
|
12
|
+
c.destination_names = []
|
13
|
+
|
14
|
+
(country/"DestinationNames/DestinationName").each do |destination_name|
|
15
|
+
d = Struct.new(:destination_name).new
|
16
|
+
d.destination_name = destination_name.innerHTML
|
17
|
+
c.destination_names << d
|
18
|
+
end
|
19
|
+
|
20
|
+
@countries << c
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
module HostConnect
|
2
|
+
class OptionInfo < AbstractResponse
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def size
|
6
|
+
@size ||= @data.search("/Reply/OptionInfoReply/Option").size
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def populate
|
11
|
+
@elements = []
|
12
|
+
@data.search("/Reply/OptionInfoReply/Option").each do |option|
|
13
|
+
s = OpenStruct.new
|
14
|
+
s.opt = Coercion.from_hc((option/"Opt").innerHTML)
|
15
|
+
s.option_number = Coercion.from_hc((option/"OptionNumber").innerHTML)
|
16
|
+
|
17
|
+
general = (option/"OptGeneral")
|
18
|
+
unless general.blank?
|
19
|
+
r = Struct.new(:description, :comment, :periods).new
|
20
|
+
r.description = (general/"Description").first.innerHTML
|
21
|
+
r.comment = (general/"Comment").innerHTML
|
22
|
+
r.periods = (general/"Periods").innerHTML.to_i
|
23
|
+
|
24
|
+
s.general = r
|
25
|
+
end
|
26
|
+
|
27
|
+
stay_results = (option/"OptStayResults")
|
28
|
+
unless stay_results.blank?
|
29
|
+
r = Struct.new(:availability, :currency, :total_price, :rate_name,
|
30
|
+
:rate_text).new
|
31
|
+
r.availability = (stay_results/"Availability").innerHTML
|
32
|
+
r.currency = (stay_results/"Currency").innerHTML
|
33
|
+
r.total_price = Coercion.price((stay_results/"TotalPrice").innerHTML)
|
34
|
+
r.rate_name = (stay_results/"RateName").innerHTML
|
35
|
+
r.rate_text = (stay_results/"RateText").innerHTML
|
36
|
+
|
37
|
+
s.stay_results = r
|
38
|
+
end
|
39
|
+
|
40
|
+
rates = (option/"OptRates")
|
41
|
+
unless rates.blank?
|
42
|
+
r = OpenStruct.new
|
43
|
+
r.currency = (rates/"Currency").innerHTML
|
44
|
+
|
45
|
+
room_rates = Struct.new(:single_rate, :double_rate, :twin_rate, :triple_rate).new
|
46
|
+
room_rates.single_rate = Coercion.price((rates/"RoomRates/SingleRate").innerHTML)
|
47
|
+
room_rates.double_rate = Coercion.price((rates/"RoomRates/DoubleRate").innerHTML)
|
48
|
+
room_rates.twin_rate = Coercion.price((rates/"RoomRates/TwinRate").innerHTML)
|
49
|
+
room_rates.triple_rate = Coercion.price((rates/"RoomRates/TripleRate").innerHTML)
|
50
|
+
|
51
|
+
r.rate = OpenStruct.new
|
52
|
+
r.rate.room_rates = room_rates
|
53
|
+
s.rates = r
|
54
|
+
end
|
55
|
+
|
56
|
+
@elements << s
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,31 @@
|
|
1
|
+
module HostConnect
|
2
|
+
class SupplierInfo < AbstractResponse
|
3
|
+
include Enumerable
|
4
|
+
|
5
|
+
def size
|
6
|
+
@size ||= @data.search("/Reply/SupplierInfoReply/Suppliers/Supplier").size
|
7
|
+
end
|
8
|
+
|
9
|
+
private
|
10
|
+
def populate
|
11
|
+
@elements = []
|
12
|
+
|
13
|
+
@data.search("/Reply/SupplierInfoReply/Suppliers/Supplier").each do |supplier|
|
14
|
+
s = Struct.new(:supplier_id, :supplier_code, :name, :address1, :address2,
|
15
|
+
:address3, :address4, :address5, :post_code, :supplier_note).new
|
16
|
+
s.supplier_id = (supplier/"SupplierId").innerHTML
|
17
|
+
s.supplier_code = (supplier/"SupplierCode").innerHTML
|
18
|
+
s.name = (supplier/"Name").innerHTML
|
19
|
+
s.address1 = (supplier/"Address1").innerHTML
|
20
|
+
s.address2 = (supplier/"Address2").innerHTML
|
21
|
+
s.address3 = (supplier/"Address3").innerHTML
|
22
|
+
s.address4 = (supplier/"Address4").innerHTML
|
23
|
+
s.address5 = (supplier/"Address5").innerHTML
|
24
|
+
s.post_code = (supplier/"PostCode").innerHTML
|
25
|
+
s.supplier_note = (supplier/"SupplierNotes"/"SupplierNote"/"NoteText").innerHTML
|
26
|
+
|
27
|
+
@elements << s
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
@@ -0,0 +1,19 @@
|
|
1
|
+
module HostConnect
|
2
|
+
# For setting up simple RTF Documents
|
3
|
+
class RtfDocument
|
4
|
+
def initialize(paragraphs)
|
5
|
+
@document = RTF::Document.new(RTF::Font.new(RTF::Font::ROMAN, 'Times New Roman'))
|
6
|
+
paragraphs.each do |p|
|
7
|
+
@document.paragraph << p
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def to_rtf
|
12
|
+
@document.to_rtf
|
13
|
+
end
|
14
|
+
|
15
|
+
def to_s
|
16
|
+
@document.to_s
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module HostConnect
|
2
|
+
# Translate to/from HostConnect codes
|
3
|
+
class Translation
|
4
|
+
GRAMMAR = {
|
5
|
+
"SG" => "Single",
|
6
|
+
"TW" => "Twin",
|
7
|
+
"DB" => "Double",
|
8
|
+
"TR" => "Triple",
|
9
|
+
"QD" => "Quad",
|
10
|
+
"CF" => "Confirmed",
|
11
|
+
"CX" => "Cancelled",
|
12
|
+
"FQ" => "FIT Quote",
|
13
|
+
"IN" => "Internet Booking",
|
14
|
+
"RQ" => "Request",
|
15
|
+
"CRCD" => "Credit Card Payment"
|
16
|
+
}
|
17
|
+
|
18
|
+
def self.translate(string)
|
19
|
+
(string =~ /^[A-Z]{2}$/) ? GRAMMAR[string] : GRAMMAR.invert[string]
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
data/lib/hostconnect.rb
ADDED
@@ -0,0 +1,58 @@
|
|
1
|
+
# This file begins the loading sequence.
|
2
|
+
pwd = File.dirname(__FILE__)
|
3
|
+
|
4
|
+
# This line just let's us require anything in the +lib+ sub-folder
|
5
|
+
# without specifying a full path.
|
6
|
+
$LOAD_PATH.unshift(pwd)
|
7
|
+
|
8
|
+
# Require dependencies
|
9
|
+
%w[rubygems net/https builder date time ostruct hpricot rtf
|
10
|
+
active_support yaml logger].each { |lib| require lib }
|
11
|
+
|
12
|
+
# Require HostConnect files
|
13
|
+
%w[client response builder rtf_document translation
|
14
|
+
coercion].each { |lib| require "#{pwd}/hostconnect/#{lib}" }
|
15
|
+
|
16
|
+
Dir.glob("#{pwd}/hostconnect/builders/*.rb").each { |file| require file }
|
17
|
+
Dir.glob("#{pwd}/hostconnect/responses/*.rb").each { |file| require file }
|
18
|
+
Dir.glob("#{pwd}/hostconnect/core_extensions/*.rb").each { |file| require file }
|
19
|
+
|
20
|
+
module HostConnect
|
21
|
+
extend self
|
22
|
+
|
23
|
+
module Version
|
24
|
+
MAJOR = '0'
|
25
|
+
MINOR = '1'
|
26
|
+
REVISION = '6'
|
27
|
+
def self.combined
|
28
|
+
[MAJOR, MINOR, REVISION].join('.')
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def setup(env, config)
|
33
|
+
config[:dtd] =
|
34
|
+
'http://www.tourplan.com/support/Connector/hostConnect_' + config[:version] + '.dtd'
|
35
|
+
@@config = OpenStruct.new config
|
36
|
+
|
37
|
+
log_level = case env
|
38
|
+
when :test then Logger::WARN
|
39
|
+
when :development then Logger::DEBUG
|
40
|
+
when :production then Logger::ERROR
|
41
|
+
else raise ArgumentError, 'Incorrect environment: ' + env.to_s
|
42
|
+
end
|
43
|
+
|
44
|
+
log_path = config[:log_path] || "log/"
|
45
|
+
@@logger = Logger.new(log_path + env.to_s + ".log")
|
46
|
+
@@logger.level = log_level
|
47
|
+
@@logger.formatter = Logger::Formatter.new
|
48
|
+
@@logger.datetime_format = "%Y-%d-%m %H:%M:%S"
|
49
|
+
end
|
50
|
+
|
51
|
+
def config
|
52
|
+
@@config
|
53
|
+
end
|
54
|
+
|
55
|
+
def logger
|
56
|
+
@@logger
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,36 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
|
3
|
+
describe HostConnect::AddServiceBuilder do
|
4
|
+
before(:each) do
|
5
|
+
@xml = File.read("./spec/fixtures/requests/add_service_request/hotel_booking.xml")
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should produce xml" do
|
9
|
+
req = AddServiceBuilder.new(:agent_id => "EURTAT", :password => "EURTAT")
|
10
|
+
|
11
|
+
req.new_booking_info = NewBookingInfoBuilder.new(:name => "Jessop Mr/Mrs", :qb => "B")
|
12
|
+
|
13
|
+
pax1 = PaxDetailsBuilder.new(:title => "Mr", :forename => "Rob",
|
14
|
+
:surname => "Jardim", :pax_type => "A",
|
15
|
+
:date_of_birth => "1980-12-24")
|
16
|
+
pax2 = PaxDetailsBuilder.new(:title => "Mrs", :forename => "Sally",
|
17
|
+
:surname => "Jardim", :pax_type => "A",
|
18
|
+
:date_of_birth => "1980-12-24")
|
19
|
+
|
20
|
+
room = RoomConfigBuilder.new(:adults => 2, :room_type => "TW",
|
21
|
+
:pax_list => [ pax1, pax2 ])
|
22
|
+
req.room_configs = [ room ]
|
23
|
+
|
24
|
+
req.opt = "LONACCUMLONSTDBCB"
|
25
|
+
req.date_from = "2007-10-07"
|
26
|
+
req.scu_qty = "2"
|
27
|
+
|
28
|
+
req.ud_text1 = "+4799999999"
|
29
|
+
req.ud_text2 = "contact@email.net"
|
30
|
+
req.ud_text3 = "Norway"
|
31
|
+
req.ud_text4 = false
|
32
|
+
req.ud_text5 = "Smoking"
|
33
|
+
|
34
|
+
req.to_s.should == @xml
|
35
|
+
end
|
36
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
|
3
|
+
describe HostConnect::AgentInfoBuilder do
|
4
|
+
before(:each) do
|
5
|
+
@xml = File.read("./spec/fixtures/requests/agent_info.xml")
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should produce xml" do
|
9
|
+
req = AgentInfoBuilder.new(:agent_id => "EURTAT", :password => "EURTAT")
|
10
|
+
|
11
|
+
req.to_s.should == @xml
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
|
3
|
+
describe HostConnect::GetLocationsBuilder do
|
4
|
+
before(:each) do
|
5
|
+
@xml = File.read("./spec/fixtures/requests/get_locations.xml")
|
6
|
+
end
|
7
|
+
|
8
|
+
it "should produce xml" do
|
9
|
+
req = GetLocationsBuilder.new(:agent_id => "EURTAT", :password => "EURTAT")
|
10
|
+
|
11
|
+
req.to_s.should == @xml
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,34 @@
|
|
1
|
+
require File.dirname(__FILE__) + "/../spec_helper"
|
2
|
+
|
3
|
+
describe HostConnect::NewBookingInfoBuilder do
|
4
|
+
before(:each) do
|
5
|
+
@xml = <<-END
|
6
|
+
<NewBookingInfo>
|
7
|
+
<Name>Jessop Mr/Mrs</Name>
|
8
|
+
<QB>B</QB>
|
9
|
+
</NewBookingInfo>
|
10
|
+
END
|
11
|
+
end
|
12
|
+
|
13
|
+
it "should produce correct xml" do
|
14
|
+
req = NewBookingInfoBuilder.new(:name => "Jessop Mr/Mrs", :qb => "B")
|
15
|
+
|
16
|
+
req.to_s.should == @xml
|
17
|
+
end
|
18
|
+
|
19
|
+
it "should be possible to set and change the attributes directly" do
|
20
|
+
req = NewBookingInfoBuilder.new
|
21
|
+
req.name = "Jessop Mr/Mrs"
|
22
|
+
req.qb = "B"
|
23
|
+
|
24
|
+
req.to_s.should == @xml
|
25
|
+
|
26
|
+
req.qb = "X"
|
27
|
+
req.to_s.should_not == @xml
|
28
|
+
end
|
29
|
+
|
30
|
+
it "should return a XML Builder object" do
|
31
|
+
req = NewBookingInfoBuilder.new({})
|
32
|
+
req.to_xml.should respond_to(:target!)
|
33
|
+
end
|
34
|
+
end
|