active_shipping 0.12.4 → 0.12.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/lib/active_shipping.rb +2 -1
- data/lib/active_shipping/shipping/base.rb +2 -2
- data/lib/active_shipping/shipping/carrier.rb +16 -13
- data/lib/active_shipping/shipping/carriers/benchmark_carrier.rb +3 -4
- data/lib/active_shipping/shipping/carriers/bogus_carrier.rb +1 -3
- data/lib/active_shipping/shipping/carriers/canada_post.rb +33 -44
- data/lib/active_shipping/shipping/carriers/canada_post_pws.rb +72 -81
- data/lib/active_shipping/shipping/carriers/fedex.rb +118 -109
- data/lib/active_shipping/shipping/carriers/kunaki.rb +33 -32
- data/lib/active_shipping/shipping/carriers/new_zealand_post.rb +9 -16
- data/lib/active_shipping/shipping/carriers/shipwire.rb +36 -35
- data/lib/active_shipping/shipping/carriers/stamps.rb +39 -51
- data/lib/active_shipping/shipping/carriers/ups.rb +280 -116
- data/lib/active_shipping/shipping/carriers/ups.rb.orig +456 -0
- data/lib/active_shipping/shipping/carriers/usps.rb +145 -100
- data/lib/active_shipping/shipping/carriers/usps.rb.orig +616 -0
- data/lib/active_shipping/shipping/errors.rb +1 -1
- data/lib/active_shipping/shipping/label_response.rb +25 -0
- data/lib/active_shipping/shipping/location.rb +18 -16
- data/lib/active_shipping/shipping/package.rb +51 -54
- data/lib/active_shipping/shipping/rate_estimate.rb +10 -12
- data/lib/active_shipping/shipping/rate_response.rb +3 -7
- data/lib/active_shipping/shipping/response.rb +6 -9
- data/lib/active_shipping/shipping/shipment_event.rb +2 -4
- data/lib/active_shipping/shipping/shipment_packer.rb +32 -17
- data/lib/active_shipping/shipping/shipping_response.rb +2 -4
- data/lib/active_shipping/shipping/tracking_response.rb +3 -5
- data/lib/active_shipping/version.rb +1 -1
- data/lib/vendor/quantified/lib/quantified/attribute.rb +79 -80
- data/lib/vendor/quantified/lib/quantified/length.rb +5 -5
- data/lib/vendor/quantified/lib/quantified/mass.rb +4 -4
- data/lib/vendor/quantified/test/length_test.rb +19 -15
- data/lib/vendor/quantified/test/mass_test.rb +14 -14
- data/lib/vendor/quantified/test/test_helper.rb +1 -2
- data/lib/vendor/test_helper.rb +0 -1
- data/lib/vendor/xml_node/benchmark/bench_generation.rb +2 -4
- data/lib/vendor/xml_node/lib/xml_node.rb +54 -55
- data/lib/vendor/xml_node/test/test_generating.rb +23 -28
- data/lib/vendor/xml_node/test/test_parsing.rb +5 -8
- metadata +6 -25
- checksums.yaml.gz.sig +0 -1
- data.tar.gz.sig +0 -0
- metadata.gz.sig +0 -0
@@ -0,0 +1,25 @@
|
|
1
|
+
module ActiveMerchant #:nodoc:
|
2
|
+
module Shipping
|
3
|
+
# This is UPS specific for now; the hash is not at all generic
|
4
|
+
# or common between carriers.
|
5
|
+
|
6
|
+
class LabelResponse < Response
|
7
|
+
attr :params # maybe?
|
8
|
+
|
9
|
+
def initialize(success, message, params = {}, options = {})
|
10
|
+
@params = params
|
11
|
+
super
|
12
|
+
end
|
13
|
+
|
14
|
+
def labels
|
15
|
+
return @labels if @labels
|
16
|
+
packages = params["ShipmentResults"]["PackageResults"]
|
17
|
+
packages = [packages] if Hash === packages
|
18
|
+
@labels = packages.map do |package|
|
19
|
+
{ :tracking_number => package["TrackingNumber"],
|
20
|
+
:image => package["LabelImage"] }
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -1,8 +1,8 @@
|
|
1
1
|
module ActiveMerchant #:nodoc:
|
2
2
|
module Shipping #:nodoc:
|
3
3
|
class Location
|
4
|
-
ADDRESS_TYPES = %w
|
5
|
-
|
4
|
+
ADDRESS_TYPES = %w(residential commercial po_box)
|
5
|
+
|
6
6
|
attr_reader :options,
|
7
7
|
:country,
|
8
8
|
:postal_code,
|
@@ -16,14 +16,14 @@ module ActiveMerchant #:nodoc:
|
|
16
16
|
:fax,
|
17
17
|
:address_type,
|
18
18
|
:company_name
|
19
|
-
|
19
|
+
|
20
20
|
alias_method :zip, :postal_code
|
21
21
|
alias_method :postal, :postal_code
|
22
22
|
alias_method :state, :province
|
23
23
|
alias_method :territory, :province
|
24
24
|
alias_method :region, :province
|
25
25
|
alias_method :company, :company_name
|
26
|
-
|
26
|
+
|
27
27
|
def initialize(options = {})
|
28
28
|
@country = (options[:country].nil? or options[:country].is_a?(ActiveMerchant::Country)) ?
|
29
29
|
options[:country] :
|
@@ -41,8 +41,8 @@ module ActiveMerchant #:nodoc:
|
|
41
41
|
|
42
42
|
self.address_type = options[:address_type]
|
43
43
|
end
|
44
|
-
|
45
|
-
def self.from(object, options={})
|
44
|
+
|
45
|
+
def self.from(object, options = {})
|
46
46
|
return object if object.is_a? ActiveMerchant::Shipping::Location
|
47
47
|
attr_mappings = {
|
48
48
|
:name => [:name],
|
@@ -74,13 +74,13 @@ module ActiveMerchant #:nodoc:
|
|
74
74
|
end
|
75
75
|
end
|
76
76
|
attributes.delete(:address_type) unless ADDRESS_TYPES.include?(attributes[:address_type].to_s)
|
77
|
-
|
77
|
+
new(attributes.update(options))
|
78
78
|
end
|
79
|
-
|
79
|
+
|
80
80
|
def country_code(format = :alpha2)
|
81
81
|
@country.nil? ? nil : @country.code(format).value
|
82
82
|
end
|
83
|
-
|
83
|
+
|
84
84
|
def residential?; @address_type == 'residential' end
|
85
85
|
def commercial?; @address_type == 'commercial' end
|
86
86
|
def po_box?; @address_type == 'po_box' end
|
@@ -109,7 +109,7 @@ module ActiveMerchant #:nodoc:
|
|
109
109
|
}
|
110
110
|
end
|
111
111
|
|
112
|
-
def to_xml(options={})
|
112
|
+
def to_xml(options = {})
|
113
113
|
options[:root] ||= "location"
|
114
114
|
to_hash.to_xml(options)
|
115
115
|
end
|
@@ -117,15 +117,15 @@ module ActiveMerchant #:nodoc:
|
|
117
117
|
def to_s
|
118
118
|
prettyprint.gsub(/\n/, ' ')
|
119
119
|
end
|
120
|
-
|
120
|
+
|
121
121
|
def prettyprint
|
122
122
|
chunks = []
|
123
|
-
chunks << [@name, @address1
|
124
|
-
chunks << [@city
|
123
|
+
chunks << [@name, @address1, @address2, @address3].reject(&:blank?).join("\n")
|
124
|
+
chunks << [@city, @province, @postal_code].reject(&:blank?).join(', ')
|
125
125
|
chunks << @country
|
126
|
-
chunks.reject
|
126
|
+
chunks.reject(&:blank?).join("\n")
|
127
127
|
end
|
128
|
-
|
128
|
+
|
129
129
|
def inspect
|
130
130
|
string = prettyprint
|
131
131
|
string << "\nPhone: #{@phone}" unless @phone.blank?
|
@@ -144,7 +144,9 @@ module ActiveMerchant #:nodoc:
|
|
144
144
|
end
|
145
145
|
end
|
146
146
|
|
147
|
+
def address2_and_3
|
148
|
+
[address2, address3].reject(&:blank?).join(", ")
|
149
|
+
end
|
147
150
|
end
|
148
|
-
|
149
151
|
end
|
150
152
|
end
|
@@ -1,7 +1,7 @@
|
|
1
1
|
module ActiveMerchant #:nodoc:
|
2
2
|
module Shipping #:nodoc:
|
3
|
-
# A package item is a unique item(s) that is physically in a package.
|
4
|
-
# A single package can have many items. This is only required
|
3
|
+
# A package item is a unique item(s) that is physically in a package.
|
4
|
+
# A single package can have many items. This is only required
|
5
5
|
# for shipping methods (label creation) right now.
|
6
6
|
class PackageItem
|
7
7
|
include Quantified
|
@@ -12,8 +12,8 @@ module ActiveMerchant #:nodoc:
|
|
12
12
|
@name = name
|
13
13
|
|
14
14
|
imperial = (options[:units] == :imperial) ||
|
15
|
-
|
16
|
-
|
15
|
+
(grams_or_ounces.respond_to?(:unit) && m.unit.to_sym == :imperial)
|
16
|
+
|
17
17
|
@unit_system = imperial ? :imperial : :metric
|
18
18
|
|
19
19
|
@weight = attribute_from_metric_or_imperial(grams_or_ounces, Mass, :grams, :ounces)
|
@@ -36,28 +36,28 @@ module ActiveMerchant #:nodoc:
|
|
36
36
|
@unit_system == :imperial ? m.in_ounces : m
|
37
37
|
end
|
38
38
|
when :billable
|
39
|
-
[
|
39
|
+
[weight, weight(:type => :volumetric)].max
|
40
40
|
end
|
41
41
|
end
|
42
42
|
alias_method :mass, :weight
|
43
43
|
|
44
|
-
def ounces(options={})
|
44
|
+
def ounces(options = {})
|
45
45
|
weight(options).in_ounces.amount
|
46
46
|
end
|
47
47
|
alias_method :oz, :ounces
|
48
|
-
|
49
|
-
def grams(options={})
|
48
|
+
|
49
|
+
def grams(options = {})
|
50
50
|
weight(options).in_grams.amount
|
51
51
|
end
|
52
52
|
alias_method :g, :grams
|
53
|
-
|
54
|
-
def pounds(options={})
|
53
|
+
|
54
|
+
def pounds(options = {})
|
55
55
|
weight(options).in_pounds.amount
|
56
56
|
end
|
57
57
|
alias_method :lb, :pounds
|
58
58
|
alias_method :lbs, :pounds
|
59
|
-
|
60
|
-
def kilograms(options={})
|
59
|
+
|
60
|
+
def kilograms(options = {})
|
61
61
|
weight(options).in_kilograms.amount
|
62
62
|
end
|
63
63
|
alias_method :kg, :kilograms
|
@@ -72,12 +72,11 @@ module ActiveMerchant #:nodoc:
|
|
72
72
|
return klass.new(obj, (@unit_system == :imperial ? imperial_unit : metric_unit))
|
73
73
|
end
|
74
74
|
end
|
75
|
-
|
76
75
|
end
|
77
76
|
|
78
77
|
class Package
|
79
78
|
include Quantified
|
80
|
-
|
79
|
+
|
81
80
|
cattr_accessor :default_options
|
82
81
|
attr_reader :options, :value, :currency
|
83
82
|
|
@@ -88,36 +87,35 @@ module ActiveMerchant #:nodoc:
|
|
88
87
|
options = @@default_options.update(options) if @@default_options
|
89
88
|
options.symbolize_keys!
|
90
89
|
@options = options
|
91
|
-
|
92
|
-
@dimensions = [dimensions].flatten.reject {|d| d.nil?}
|
93
90
|
|
94
|
-
|
91
|
+
@dimensions = [dimensions].flatten.reject(&:nil?)
|
92
|
+
|
95
93
|
imperial = (options[:units] == :imperial) ||
|
96
|
-
|
97
|
-
|
94
|
+
([grams_or_ounces, *dimensions].all? { |m| m.respond_to?(:unit) && m.unit.to_sym == :imperial })
|
95
|
+
|
98
96
|
weight_imperial = dimensions_imperial = imperial if options.include?(:units)
|
99
97
|
|
100
98
|
if options.include?(:weight_units)
|
101
99
|
weight_imperial = (options[:weight_units] == :imperial) ||
|
102
|
-
|
100
|
+
(grams_or_ounces.respond_to?(:unit) && m.unit.to_sym == :imperial)
|
103
101
|
end
|
104
102
|
|
105
103
|
if options.include?(:dim_units)
|
106
104
|
dimensions_imperial = (options[:dim_units] == :imperial) ||
|
107
|
-
|
105
|
+
(dimensions && dimensions.all? { |m| m.respond_to?(:unit) && m.unit.to_sym == :imperial })
|
108
106
|
end
|
109
|
-
|
107
|
+
|
110
108
|
@weight_unit_system = weight_imperial ? :imperial : :metric
|
111
109
|
@dimensions_unit_system = dimensions_imperial ? :imperial : :metric
|
112
|
-
|
110
|
+
|
113
111
|
@weight = attribute_from_metric_or_imperial(grams_or_ounces, Mass, @weight_unit_system, :grams, :ounces)
|
114
|
-
|
112
|
+
|
115
113
|
if @dimensions.blank?
|
116
114
|
@dimensions = [Length.new(0, (dimensions_imperial ? :inches : :centimetres))] * 3
|
117
115
|
else
|
118
116
|
process_dimensions
|
119
117
|
end
|
120
|
-
|
118
|
+
|
121
119
|
@value = Package.cents_from(options[:value])
|
122
120
|
@currency = options[:currency] || (options[:value].currency if options[:value].respond_to?(:currency))
|
123
121
|
@cylinder = (options[:cylinder] || options[:tube]) ? true : false
|
@@ -125,7 +123,7 @@ module ActiveMerchant #:nodoc:
|
|
125
123
|
@oversized = options[:oversized] ? true : false
|
126
124
|
@unpackaged = options[:unpackaged] ? true : false
|
127
125
|
end
|
128
|
-
|
126
|
+
|
129
127
|
def unpackaged?
|
130
128
|
@unpackaged
|
131
129
|
end
|
@@ -133,48 +131,48 @@ module ActiveMerchant #:nodoc:
|
|
133
131
|
def oversized?
|
134
132
|
@oversized
|
135
133
|
end
|
136
|
-
|
134
|
+
|
137
135
|
def cylinder?
|
138
136
|
@cylinder
|
139
137
|
end
|
140
138
|
alias_method :tube?, :cylinder?
|
141
|
-
|
139
|
+
|
142
140
|
def gift?; @gift end
|
143
|
-
|
144
|
-
def ounces(options={})
|
141
|
+
|
142
|
+
def ounces(options = {})
|
145
143
|
weight(options).in_ounces.amount
|
146
144
|
end
|
147
145
|
alias_method :oz, :ounces
|
148
|
-
|
149
|
-
def grams(options={})
|
146
|
+
|
147
|
+
def grams(options = {})
|
150
148
|
weight(options).in_grams.amount
|
151
149
|
end
|
152
150
|
alias_method :g, :grams
|
153
|
-
|
154
|
-
def pounds(options={})
|
151
|
+
|
152
|
+
def pounds(options = {})
|
155
153
|
weight(options).in_pounds.amount
|
156
154
|
end
|
157
155
|
alias_method :lb, :pounds
|
158
156
|
alias_method :lbs, :pounds
|
159
|
-
|
160
|
-
def kilograms(options={})
|
157
|
+
|
158
|
+
def kilograms(options = {})
|
161
159
|
weight(options).in_kilograms.amount
|
162
160
|
end
|
163
161
|
alias_method :kg, :kilograms
|
164
162
|
alias_method :kgs, :kilograms
|
165
|
-
|
166
|
-
def inches(measurement=nil)
|
167
|
-
@inches ||= @dimensions.map {|m| m.in_inches.amount }
|
163
|
+
|
164
|
+
def inches(measurement = nil)
|
165
|
+
@inches ||= @dimensions.map { |m| m.in_inches.amount }
|
168
166
|
measurement.nil? ? @inches : measure(measurement, @inches)
|
169
167
|
end
|
170
168
|
alias_method :in, :inches
|
171
|
-
|
172
|
-
def centimetres(measurement=nil)
|
173
|
-
@centimetres ||= @dimensions.map {|m| m.in_centimetres.amount }
|
169
|
+
|
170
|
+
def centimetres(measurement = nil)
|
171
|
+
@centimetres ||= @dimensions.map { |m| m.in_centimetres.amount }
|
174
172
|
measurement.nil? ? @centimetres : measure(measurement, @centimetres)
|
175
173
|
end
|
176
174
|
alias_method :cm, :centimetres
|
177
|
-
|
175
|
+
|
178
176
|
def weight(options = {})
|
179
177
|
case options[:type]
|
180
178
|
when nil, :actual
|
@@ -185,11 +183,11 @@ module ActiveMerchant #:nodoc:
|
|
185
183
|
@weight_unit_system == :imperial ? m.in_ounces : m
|
186
184
|
end
|
187
185
|
when :billable
|
188
|
-
[
|
186
|
+
[weight, weight(:type => :volumetric)].max
|
189
187
|
end
|
190
188
|
end
|
191
189
|
alias_method :mass, :weight
|
192
|
-
|
190
|
+
|
193
191
|
def self.cents_from(money)
|
194
192
|
return nil if money.nil?
|
195
193
|
if money.respond_to?(:cents)
|
@@ -205,7 +203,7 @@ module ActiveMerchant #:nodoc:
|
|
205
203
|
end
|
206
204
|
end
|
207
205
|
end
|
208
|
-
|
206
|
+
|
209
207
|
private
|
210
208
|
|
211
209
|
def attribute_from_metric_or_imperial(obj, klass, unit_system, metric_unit, imperial_unit)
|
@@ -215,20 +213,20 @@ module ActiveMerchant #:nodoc:
|
|
215
213
|
return klass.new(obj, (unit_system == :imperial ? imperial_unit : metric_unit))
|
216
214
|
end
|
217
215
|
end
|
218
|
-
|
216
|
+
|
219
217
|
def measure(measurement, ary)
|
220
218
|
case measurement
|
221
|
-
when Fixnum then ary[measurement]
|
219
|
+
when Fixnum then ary[measurement]
|
222
220
|
when :x, :max, :length, :long then ary[2]
|
223
221
|
when :y, :mid, :width, :wide then ary[1]
|
224
|
-
when :z, :min, :height
|
225
|
-
when :girth, :around
|
222
|
+
when :z, :min, :height, :depth, :high, :deep then ary[0]
|
223
|
+
when :girth, :around, :circumference
|
226
224
|
self.cylinder? ? (Math::PI * (ary[0] + ary[1]) / 2) : (2 * ary[0]) + (2 * ary[1])
|
227
|
-
when :volume then self.cylinder? ? (Math::PI * (ary[0] + ary[1]) / 4)**2 * ary[2] : measure(:box_volume,ary)
|
225
|
+
when :volume then self.cylinder? ? (Math::PI * (ary[0] + ary[1]) / 4)**2 * ary[2] : measure(:box_volume, ary)
|
228
226
|
when :box_volume then ary[0] * ary[1] * ary[2]
|
229
227
|
end
|
230
228
|
end
|
231
|
-
|
229
|
+
|
232
230
|
def process_dimensions
|
233
231
|
@dimensions = @dimensions.map do |l|
|
234
232
|
attribute_from_metric_or_imperial(l, Length, @dimensions_unit_system, :centimetres, :inches)
|
@@ -236,11 +234,10 @@ module ActiveMerchant #:nodoc:
|
|
236
234
|
# [1,2] => [1,1,2]
|
237
235
|
# [5] => [5,5,5]
|
238
236
|
# etc..
|
239
|
-
2.downto(@dimensions.length) do |
|
237
|
+
2.downto(@dimensions.length) do |_n|
|
240
238
|
@dimensions.unshift(@dimensions[0])
|
241
239
|
end
|
242
240
|
end
|
243
|
-
|
244
241
|
end
|
245
242
|
end
|
246
243
|
end
|
@@ -1,6 +1,5 @@
|
|
1
1
|
module ActiveMerchant #:nodoc:
|
2
2
|
module Shipping #:nodoc:
|
3
|
-
|
4
3
|
class RateEstimate
|
5
4
|
attr_reader :origin # Location objects
|
6
5
|
attr_reader :destination
|
@@ -9,20 +8,20 @@ module ActiveMerchant #:nodoc:
|
|
9
8
|
attr_reader :service_name # name of service ("First Class Ground", etc.)
|
10
9
|
attr_reader :service_code
|
11
10
|
attr_reader :currency # 'USD', 'CAD', etc.
|
12
|
-
|
11
|
+
# http://en.wikipedia.org/wiki/ISO_4217
|
13
12
|
attr_reader :shipping_date
|
14
13
|
attr_reader :delivery_date # Usually only available for express shipments
|
15
14
|
attr_reader :delivery_range # Min and max delivery estimate in days
|
16
15
|
attr_reader :negotiated_rate
|
17
16
|
attr_reader :insurance_price
|
18
17
|
|
19
|
-
def initialize(origin, destination, carrier, service_name, options={})
|
18
|
+
def initialize(origin, destination, carrier, service_name, options = {})
|
20
19
|
@origin, @destination, @carrier, @service_name = origin, destination, carrier, service_name
|
21
20
|
@service_code = options[:service_code]
|
22
21
|
if options[:package_rates]
|
23
|
-
@package_rates = options[:package_rates].map {|p| p.update(
|
22
|
+
@package_rates = options[:package_rates].map { |p| p.update(:rate => Package.cents_from(p[:rate])) }
|
24
23
|
else
|
25
|
-
@package_rates = Array(options[:packages]).map {|p| {:package => p}}
|
24
|
+
@package_rates = Array(options[:packages]).map { |p| {:package => p} }
|
26
25
|
end
|
27
26
|
@total_price = Package.cents_from(options[:total_price])
|
28
27
|
@negotiated_rate = options[:negotiated_rate] ? Package.cents_from(options[:negotiated_rate]) : nil
|
@@ -34,15 +33,13 @@ module ActiveMerchant #:nodoc:
|
|
34
33
|
end
|
35
34
|
|
36
35
|
def total_price
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
raise ArgumentError.new("RateEstimate must have a total_price set, or have a full set of valid package rates.")
|
41
|
-
end
|
36
|
+
@total_price || @package_rates.sum { |p| p[:rate] }
|
37
|
+
rescue NoMethodError
|
38
|
+
raise ArgumentError.new("RateEstimate must have a total_price set, or have a full set of valid package rates.")
|
42
39
|
end
|
43
40
|
alias_method :price, :total_price
|
44
41
|
|
45
|
-
def add(package,rate=nil)
|
42
|
+
def add(package, rate = nil)
|
46
43
|
cents = Package.cents_from(rate)
|
47
44
|
raise ArgumentError.new("New packages must have valid rate information since this RateEstimate has no total_price set.") if cents.nil? and total_price.nil?
|
48
45
|
@package_rates << {:package => package, :rate => cents}
|
@@ -50,7 +47,7 @@ module ActiveMerchant #:nodoc:
|
|
50
47
|
end
|
51
48
|
|
52
49
|
def packages
|
53
|
-
package_rates.map {|p| p[:package]}
|
50
|
+
package_rates.map { |p| p[:package] }
|
54
51
|
end
|
55
52
|
|
56
53
|
def package_count
|
@@ -58,6 +55,7 @@ module ActiveMerchant #:nodoc:
|
|
58
55
|
end
|
59
56
|
|
60
57
|
private
|
58
|
+
|
61
59
|
def date_for(date)
|
62
60
|
date && DateTime.strptime(date.to_s, "%Y-%m-%d")
|
63
61
|
rescue ArgumentError
|
@@ -1,19 +1,15 @@
|
|
1
1
|
module ActiveMerchant #:nodoc:
|
2
2
|
module Shipping
|
3
|
-
|
4
3
|
class RateResponse < Response
|
5
|
-
|
6
4
|
attr_reader :rates
|
7
|
-
|
5
|
+
|
8
6
|
def initialize(success, message, params = {}, options = {})
|
9
7
|
@rates = Array(options[:estimates] || options[:rates] || options[:rate_estimates])
|
10
8
|
super
|
11
9
|
end
|
12
|
-
|
10
|
+
|
13
11
|
alias_method :estimates, :rates
|
14
12
|
alias_method :rate_estimates, :rates
|
15
|
-
|
16
13
|
end
|
17
|
-
|
18
14
|
end
|
19
|
-
end
|
15
|
+
end
|