dhl-get_quote 0.4.25

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,183 @@
1
+ require 'rubygems'
2
+ require 'httparty'
3
+ require 'erb'
4
+ require 'set'
5
+
6
+ class Dhl::GetQuote::Request
7
+ attr_reader :site_id, :password, :from_country_code, :from_postal_code, :to_country_code, :to_postal_code
8
+ attr_accessor :pieces
9
+
10
+ URLS = {
11
+ :production => 'https://xmlpi-ea.dhl.com/XMLShippingServlet',
12
+ :test => 'https://xmlpitest-ea.dhl.com/XMLShippingServlet'
13
+ }
14
+
15
+ def initialize(options = {})
16
+ @test_mode = !!options[:test_mode] || Dhl::GetQuote.test_mode?
17
+
18
+ @site_id = options[:site_id] || Dhl::GetQuote.site_id
19
+ @password = options[:password] || Dhl::GetQuote.password
20
+
21
+ [ :site_id, :password ].each do |req|
22
+ unless instance_variable_get("@#{req}").to_s.size > 0
23
+ raise Dhl::GetQuote::OptionsError, ":#{req} is a required option"
24
+ end
25
+ end
26
+
27
+ @special_services_list = Set.new
28
+
29
+ @is_dutiable = Dhl::GetQuote.dutiable?
30
+
31
+ @pieces = []
32
+ end
33
+
34
+ def test_mode?
35
+ !!@test_mode
36
+ end
37
+
38
+ def test_mode!
39
+ @test_mode = true
40
+ end
41
+
42
+ def production_mode!
43
+ @test_mode = false
44
+ end
45
+
46
+ def from(country_code, postal_code)
47
+ @from_postal_code = postal_code.to_s
48
+ validate_country_code!(country_code)
49
+ @from_country_code = country_code
50
+ end
51
+
52
+ def to(country_code, postal_code)
53
+ @to_postal_code = postal_code.to_s
54
+ validate_country_code!(country_code)
55
+ @to_country_code = country_code
56
+ end
57
+
58
+ def dutiable?
59
+ !!@is_dutiable
60
+ end
61
+
62
+ def dutiable(val)
63
+ @is_dutiable = !!val
64
+ end
65
+
66
+ def dutiable!
67
+ dutiable(true)
68
+ end
69
+
70
+ def not_dutiable!
71
+ dutiable(false)
72
+ end
73
+
74
+ def dimensions_unit
75
+ @dimensions_unit ||= Dhl::GetQuote.dimensions_unit
76
+ end
77
+
78
+ def weight_unit
79
+ @weight_unit ||= Dhl::GetQuote.weight_unit
80
+ end
81
+
82
+ def centimeters!
83
+ @dimensions_unit = Dhl::GetQuote::DIMENSIONS_UNIT_CODES[:centimeters]
84
+ end
85
+ alias :centimetres! :centimeters!
86
+
87
+ def inches!
88
+ @dimensions_unit = Dhl::GetQuote::DIMENSIONS_UNIT_CODES[:inches]
89
+ end
90
+
91
+ def centimeters?
92
+ dimensions_unit == Dhl::GetQuote::DIMENSIONS_UNIT_CODES[:centimeters]
93
+ end
94
+ alias :centimetres? :centimeters?
95
+
96
+ def inches?
97
+ dimensions_unit == Dhl::GetQuote::DIMENSIONS_UNIT_CODES[:inches]
98
+ end
99
+
100
+ def kilograms!
101
+ @weight_unit = Dhl::GetQuote::WEIGHT_UNIT_CODES[:kilograms]
102
+ end
103
+ alias :kilogrammes! :kilograms!
104
+
105
+ def pounds!
106
+ @weight_unit = Dhl::GetQuote::WEIGHT_UNIT_CODES[:pounds]
107
+ end
108
+
109
+ def pounds?
110
+ weight_unit == Dhl::GetQuote::WEIGHT_UNIT_CODES[:pounds]
111
+ end
112
+
113
+ def kilograms?
114
+ weight_unit == Dhl::GetQuote::WEIGHT_UNIT_CODES[:kilograms]
115
+ end
116
+ alias :kilogrammes? :kilograms?
117
+
118
+ def to_xml
119
+ validate!
120
+ ERB.new(File.new(xml_template_path).read, nil,'%<>-').result(binding)
121
+ end
122
+
123
+ def ready_time(time=Time.now)
124
+ time.strftime("PT%HH%MM")
125
+ end
126
+
127
+ def post
128
+ validate!
129
+ response = HTTParty.post(servlet_url,
130
+ :body => to_xml,
131
+ :headers => { 'Content-Type' => 'application/xml' }
132
+ ).response
133
+
134
+ Dhl::GetQuote::Response.new(response.body)
135
+ end
136
+
137
+ def special_services
138
+ @special_services_list.to_a.sort
139
+ end
140
+
141
+ def add_special_service(special_service_type)
142
+ return if special_service_type.to_s.size < 1
143
+ @special_services_list << special_service_type
144
+ end
145
+
146
+ def remove_special_service(special_service_type)
147
+ return if special_service_type.to_s.size < 1
148
+ @special_services_list.delete_if{|x| x == special_service_type}
149
+ end
150
+
151
+ protected
152
+
153
+ def servlet_url
154
+ test_mode? ? URLS[:test] : URLS[:production]
155
+ end
156
+
157
+ def validate!
158
+ raise Dhl::GetQuote::FromNotSetError, "#from() is not set" unless (@from_country_code && @from_postal_code)
159
+ raise Dhl::GetQuote::ToNotSetError, "#to() is not set" unless (@to_country_code && @to_postal_code)
160
+ validate_pieces!
161
+ end
162
+
163
+ def validate_pieces!
164
+ pieces.each do |piece|
165
+ klass_name = "Dhl::GetQuote::Piece"
166
+ if piece.class.to_s != klass_name
167
+ raise Dhl::GetQuote::PieceError, "entry in #pieces is not a #{klass_name} object!"
168
+ end
169
+ end
170
+ end
171
+
172
+ def validate_country_code!(country_code)
173
+ unless country_code =~ /^[A-Z]{2}$/
174
+ raise Dhl::GetQuote::CountryCodeError, 'country code must be upper-case, two letters (A-Z)'
175
+ end
176
+ end
177
+
178
+ def xml_template_path
179
+ spec = Gem::Specification.find_by_name("dhl-get_quote")
180
+ gem_root = spec.gem_dir
181
+ gem_root + "/tpl/request.xml.erb"
182
+ end
183
+ end
@@ -0,0 +1,94 @@
1
+ class Dhl::GetQuote::Response
2
+ include Dhl::GetQuote::Helper
3
+
4
+ attr_reader :raw_xml, :parsed_xml, :error
5
+ attr_reader :currency_code, :currency_role_type_code, :weight_charge, :total_amount, :total_tax_amount, :weight_charge_tax
6
+
7
+ CURRENCY_ROLE_TYPE_CODES = %w[ BILLC PULCL BASEC INVCU ]
8
+ DEFAULT_CURRENCY_ROLE_TYPE_CODE = 'BILLC'
9
+
10
+ def initialize(xml="")
11
+ @raw_xml = xml
12
+ begin
13
+ @parsed_xml = MultiXml.parse(xml)
14
+ rescue MultiXml::ParseError => e
15
+ @error = e
16
+ return self
17
+ end
18
+
19
+ if response_indicates_error?
20
+ @error = case response_error_condition_code.to_s
21
+ when "100"
22
+ Dhl::GetQuote::Upstream::ValidationFailureError.new(response_error_condition_data)
23
+ else
24
+ Dhl::GetQuote::Upstream::UnknownError.new(response_error_condition_data)
25
+ end
26
+ else
27
+ load_costs(DEFAULT_CURRENCY_ROLE_TYPE_CODE)
28
+ end
29
+ end
30
+
31
+ def error?
32
+ !!@error
33
+ end
34
+
35
+ def load_costs(currency_role_type_code=DEFAULT_CURRENCY_ROLE_TYPE_CODE)
36
+ validate_currency_role_type_code!(currency_role_type_code)
37
+
38
+ return if error?
39
+ qtd_s_in_ad_cur = @parsed_xml["DCTResponse"]["GetQuoteResponse"]["BkgDetails"]["QtdShp"]["QtdSInAdCur"]
40
+ pricing = if x = qtd_s_in_ad_cur.detect{|q|q["CurrencyRoleTypeCode"]==currency_role_type_code}
41
+ x
42
+ else
43
+ qtd_s_in_ad_cur.first
44
+ end
45
+
46
+ pricing.each do |k,v|
47
+ instance_variable_set("@#{underscore(k)}".to_sym, v)
48
+ end
49
+ end
50
+
51
+ def validate_currency_role_type_code!(currency_role_type_code)
52
+ unless CURRENCY_ROLE_TYPE_CODES.include?(currency_role_type_code)
53
+ raise Dhl::GetQuote::OptionsError,
54
+ "'#{currency_role_type_code}' is not one of #{CURRENCY_ROLE_TYPE_CODES.join(', ')}"
55
+ end
56
+ end
57
+
58
+ def offered_services
59
+ market_services.select do
60
+ |m| m['TransInd'].to_s == "Y" || m['MrkSrvInd'].to_s == "Y"
61
+ end.map do |m|
62
+ Dhl::GetQuote::MarketService.new(m)
63
+ end.sort{|a,b| a.code <=> b.code }
64
+ end
65
+
66
+ def all_services
67
+ market_services.map do |m|
68
+ Dhl::GetQuote::MarketService.new(m)
69
+ end.sort{|a,b| a.code <=> b.code }
70
+ end
71
+
72
+ protected
73
+
74
+ def response_indicates_error?
75
+ @parsed_xml.keys.include?('ErrorResponse')
76
+ end
77
+
78
+ def response_error_status_condition
79
+ @response_error_status_condition ||= @parsed_xml['ErrorResponse']['Response']['Status']['Condition']
80
+ end
81
+
82
+ def response_error_condition_code
83
+ @response_error_condition_code ||= response_error_status_condition['ConditionCode']
84
+ end
85
+
86
+ def response_error_condition_data
87
+ @response_error_condition_data ||= response_error_status_condition['ConditionData']
88
+ end
89
+
90
+ def market_services
91
+ @market_services ||= @parsed_xml["DCTResponse"]["GetQuoteResponse"]["Srvs"]["Srv"]["MrkSrv"]
92
+ end
93
+
94
+ end
@@ -0,0 +1,5 @@
1
+ class Dhl
2
+ class GetQuote
3
+ VERSION = "0.4.25"
4
+ end
5
+ end
@@ -0,0 +1,96 @@
1
+ require "dhl/get_quote/version"
2
+ require "dhl/get_quote/helper"
3
+ require "dhl/get_quote/errors"
4
+ require "dhl/get_quote/request"
5
+ require "dhl/get_quote/response"
6
+ require "dhl/get_quote/piece"
7
+ require "dhl/get_quote/market_service"
8
+
9
+ class Dhl
10
+ class GetQuote
11
+
12
+ DIMENSIONS_UNIT_CODES = { :centimeters => "CM", :inches => "IN" }
13
+ WEIGHT_UNIT_CODES = { :kilograms => "KG", :pounds => "LB" }
14
+
15
+ def self.configure(&block)
16
+ yield self if block_given?
17
+ end
18
+
19
+ def self.test_mode!
20
+ @@test_mode = true
21
+ end
22
+
23
+ def self.test_mode?
24
+ !!@@test_mode
25
+ end
26
+
27
+ def self.production_mode!
28
+ @@test_mode = false
29
+ end
30
+
31
+ def self.site_id(site_id=nil)
32
+ if (s = site_id.to_s).size > 0
33
+ @@site_id = s
34
+ else
35
+ @@site_id
36
+ end
37
+ end
38
+
39
+ def self.password(password=nil)
40
+ if (s = password.to_s).size > 0
41
+ @@password = s
42
+ else
43
+ @@password
44
+ end
45
+ end
46
+
47
+ def self.kilograms!
48
+ @@weight_unit = WEIGHT_UNIT_CODES[:kilograms]
49
+ end
50
+ def self.kilogrammes!; self.kilograms!; end
51
+
52
+ def self.pounds!
53
+ @@weight_unit = WEIGHT_UNIT_CODES[:pounds]
54
+ end
55
+
56
+ def self.weight_unit
57
+ @@weight_unit
58
+ end
59
+
60
+ def self.centimeters!
61
+ @@dimensions_unit = DIMENSIONS_UNIT_CODES[:centimeters]
62
+ end
63
+ def self.centimetres!; self.centimeters!; end
64
+
65
+ def self.inches!
66
+ @@dimensions_unit = DIMENSIONS_UNIT_CODES[:inches]
67
+ end
68
+
69
+ def self.dimensions_unit
70
+ @@dimensions_unit
71
+ end
72
+
73
+ def self.dutiable!
74
+ @@dutiable = true
75
+ end
76
+
77
+ def self.not_dutiable!
78
+ @@dutiable = false
79
+ end
80
+
81
+ def self.dutiable?
82
+ !!@@dutiable
83
+ end
84
+
85
+ def self.set_defaults
86
+ @@site_id = nil
87
+ @@password = nil
88
+ @@weight_unit = WEIGHT_UNIT_CODES[:kilograms]
89
+ @@dimensions_unit = DIMENSIONS_UNIT_CODES[:centimeters]
90
+ @@dutiable = false
91
+ @@test_mode = false
92
+ end
93
+ end
94
+ end
95
+
96
+ Dhl::GetQuote.set_defaults
@@ -0,0 +1,177 @@
1
+ require 'spec_helper'
2
+ require 'dhl-get_quote'
3
+
4
+ describe Dhl::GetQuote do
5
+
6
+ # classvars may be remembered between tests, this sets things back to default
7
+ after(:each) do
8
+ Dhl::GetQuote.set_defaults
9
+ end
10
+
11
+ let(:klass) { Dhl::GetQuote }
12
+
13
+ let(:valid_request_options) do
14
+ {
15
+ :site_id => 'SiteId',
16
+ :password => 'p4ssw0rd'
17
+ }
18
+ end
19
+
20
+ let (:valid_request) { request = Dhl::GetQuote::Request.new(valid_request_options) }
21
+
22
+ describe ".configure" do
23
+
24
+ it "must accept and execute a block" do
25
+ lambda do
26
+ klass.configure do
27
+ raise RuntimeError, "Testing"
28
+ end
29
+ end.must raise_exception RuntimeError
30
+ end
31
+
32
+ context "configure() block" do
33
+
34
+ describe ".test_mode!" do
35
+ before(:each) do
36
+ klass.configure { |c| c.test_mode! }
37
+ end
38
+
39
+ it "must set the class test_mode? to true" do
40
+ klass.test_mode?.must be_true
41
+ end
42
+
43
+ it "Dhl::GetQuote::Request must honor this test mode" do
44
+ request = Dhl::GetQuote::Request.new(valid_request_options)
45
+ request.test_mode?.must be_true
46
+ end
47
+ end
48
+
49
+ describe ".production_mode!" do
50
+ before(:each) do
51
+ klass.configure { |c| c.production_mode! }
52
+ end
53
+
54
+ it "must set the classvar test_mode to false" do
55
+ klass.test_mode?.must be_false
56
+ end
57
+
58
+ it "Dhl::GetQuote::Request must honor this test mode" do
59
+ request = Dhl::GetQuote::Request.new(valid_request_options)
60
+ request.test_mode?.must be_false
61
+ end
62
+ end
63
+
64
+ describe ".site_id" do
65
+ before(:each) { klass.configure { |c| c.site_id "SomethingHere" } }
66
+
67
+ it "must set class site_id to passed string" do
68
+ klass.site_id.must == "SomethingHere"
69
+ end
70
+
71
+ it "Dhl::GetQuote::Request must honor this" do
72
+ request = Dhl::GetQuote::Request.new(
73
+ :password => 'xxx'
74
+ )
75
+
76
+ request.instance_variable_get(:@site_id).must == "SomethingHere"
77
+ end
78
+ end
79
+
80
+ describe ".password" do
81
+ before(:each) { klass.configure { |c| c.password "ppaasswwoorrdd" } }
82
+
83
+ it "must set class password to passed string" do
84
+ klass.password.must == "ppaasswwoorrdd"
85
+ end
86
+
87
+ it "Dhl::GetQuote::Request must honor this" do
88
+ request = Dhl::GetQuote::Request.new(
89
+ :site_id => 'ASiteId'
90
+ )
91
+
92
+ request.instance_variable_get(:@password).must == "ppaasswwoorrdd"
93
+ end
94
+ end
95
+
96
+ describe "kilograms!" do
97
+ before(:each) { klass.configure { |c| c.kilograms! } }
98
+
99
+ it "must set class weight_unit to KG" do
100
+ klass.weight_unit.must == "KG"
101
+ end
102
+
103
+ it "Dhl::GetQuote::Request must honor this" do
104
+ valid_request.weight_unit.must == "KG"
105
+ end
106
+ end
107
+
108
+ describe "pounds!" do
109
+ before(:each) { klass.configure { |c| c.pounds! } }
110
+
111
+ it "must set class weight_unit to LB" do
112
+ klass.weight_unit.must == "LB"
113
+ end
114
+
115
+ it "Dhl::GetQuote::Request must honor this" do
116
+ valid_request.weight_unit.must == "LB"
117
+ end
118
+ end
119
+
120
+ describe "centimeters!" do
121
+ before(:each) { klass.configure { |c| c.centimeters! } }
122
+
123
+ it "must set class weight_unit to CM" do
124
+ klass.dimensions_unit.must == "CM"
125
+ end
126
+
127
+ it "Dhl::GetQuote::Request must honor this" do
128
+ valid_request.dimensions_unit.must == "CM"
129
+ end
130
+ end
131
+
132
+ describe "inches!" do
133
+ before(:each) { klass.configure { |c| c.inches! } }
134
+
135
+ it "must set class weight_unit to IN" do
136
+ klass.dimensions_unit.must == "IN"
137
+ end
138
+
139
+ it "Dhl::GetQuote::Request must honor this" do
140
+ valid_request.dimensions_unit.must == "IN"
141
+ end
142
+ end
143
+
144
+ describe ".dutiable!" do
145
+ before(:each) do
146
+ klass.configure { |c| c.dutiable! }
147
+ end
148
+
149
+ it "must set the class dutiable to true" do
150
+ klass.dutiable?.must be_true
151
+ end
152
+
153
+ it "Dhl::GetQuote::Request must honor this test mode" do
154
+ request = Dhl::GetQuote::Request.new(valid_request_options)
155
+ request.dutiable?.must be_true
156
+ end
157
+ end
158
+
159
+ describe ".not_dutiable!" do
160
+ before(:each) do
161
+ klass.configure { |c| c.not_dutiable! }
162
+ end
163
+
164
+ it "must set the classvar test_mode to false" do
165
+ klass.dutiable?.must be_false
166
+ end
167
+
168
+ it "Dhl::GetQuote::Request must honor this test mode" do
169
+ request = Dhl::GetQuote::Request.new(valid_request_options)
170
+ request.dutiable?.must be_false
171
+ end
172
+ end
173
+
174
+ end
175
+ end
176
+
177
+ end
@@ -0,0 +1,78 @@
1
+ require 'spec_helper'
2
+ require 'dhl-get_quote'
3
+
4
+ describe Dhl::GetQuote::MarketService do
5
+
6
+ let(:valid_params) do
7
+ {
8
+ :local_service_type => "SA",
9
+ :global_service_name => "DELIVERY SIGNATURE",
10
+ :local_service_type_name => "DELIVERY SIGNATURE",
11
+ :s_offered_cust_agreement => "N",
12
+ :charge_code_type => "XCH",
13
+ :mrk_srv_ind => "Y"
14
+ }
15
+ end
16
+
17
+ let(:valid_xml) do
18
+ '<MrkSrv>
19
+ <LocalServiceType>SA</LocalServiceType>
20
+ <GlobalServiceName>DELIVERY SIGNATURE</GlobalServiceName>
21
+ <LocalServiceTypeName>DELIVERY SIGNATURE</LocalServiceTypeName>
22
+ <SOfferedCustAgreement>N</SOfferedCustAgreement>
23
+ <ChargeCodeType>XCH</ChargeCodeType>
24
+ <MrkSrvInd>Y</MrkSrvInd>
25
+ </MrkSrv>'
26
+ end
27
+
28
+ let(:klass) { Dhl::GetQuote::MarketService }
29
+
30
+ subject do
31
+ klass.new(valid_params)
32
+ end
33
+
34
+ describe "#initialize" do
35
+
36
+ context "string of xml is passed" do
37
+
38
+ it 'must build object from xml data' do
39
+
40
+ m = klass.new(valid_xml)
41
+
42
+ m.local_service_type.must == "SA"
43
+ m.global_service_name.must == "DELIVERY SIGNATURE"
44
+ m.local_service_type_name.must == "DELIVERY SIGNATURE"
45
+ m.s_offered_cust_agreement.must == "N"
46
+ m.charge_code_type.must == "XCH"
47
+ m.mrk_srv_ind.must == "Y"
48
+
49
+ end
50
+ end
51
+
52
+ context "hash is passed" do
53
+
54
+ it "must build an object from the hash" do
55
+
56
+ m = klass.new(valid_params)
57
+
58
+ valid_params.each do |k,v|
59
+ m.send(k).must == v
60
+ end
61
+
62
+ end
63
+
64
+ end
65
+ end
66
+
67
+ describe "#code" do
68
+ it "must return the special service code for a mrksrv" do
69
+ subject.code.must == "SA"
70
+ end
71
+ end
72
+
73
+ describe "#name" do
74
+ it "must return the local name for a mrksrv" do
75
+ subject.name.must == "DELIVERY SIGNATURE"
76
+ end
77
+ end
78
+ end