fedex_ship 0.1.0
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 +7 -0
- data/.gitignore +15 -0
- data/.idea/.rakeTasks +7 -0
- data/.idea/fedex_ship-0.1.0.iml +22 -0
- data/.idea/misc.xml +7 -0
- data/.idea/modules.xml +8 -0
- data/.idea/vcs.xml +6 -0
- data/.idea/workspace.xml +56 -0
- data/.rspec +2 -0
- data/Gemfile +5 -0
- data/Rakefile +7 -0
- data/Readme.md +496 -0
- data/fedex_ship.gemspec +28 -0
- data/lib/fedex_ship.rb +55 -0
- data/lib/fedex_ship/address.rb +31 -0
- data/lib/fedex_ship/credentials.rb +26 -0
- data/lib/fedex_ship/document.rb +51 -0
- data/lib/fedex_ship/ground_manifest.rb +25 -0
- data/lib/fedex_ship/helpers.rb +20 -0
- data/lib/fedex_ship/label.rb +71 -0
- data/lib/fedex_ship/rate.rb +38 -0
- data/lib/fedex_ship/request/address.rb +97 -0
- data/lib/fedex_ship/request/base.rb +443 -0
- data/lib/fedex_ship/request/delete.rb +76 -0
- data/lib/fedex_ship/request/document.rb +45 -0
- data/lib/fedex_ship/request/ground_close.rb +73 -0
- data/lib/fedex_ship/request/label.rb +29 -0
- data/lib/fedex_ship/request/logs_fedex.rb +74 -0
- data/lib/fedex_ship/request/pickup.rb +135 -0
- data/lib/fedex_ship/request/pickup_availability.rb +102 -0
- data/lib/fedex_ship/request/rate.rb +94 -0
- data/lib/fedex_ship/request/service_availability.rb +86 -0
- data/lib/fedex_ship/request/shipment.rb +249 -0
- data/lib/fedex_ship/request/tracking_information.rb +119 -0
- data/lib/fedex_ship/shipment.rb +115 -0
- data/lib/fedex_ship/tracking_information.rb +54 -0
- data/lib/fedex_ship/tracking_information/event.rb +24 -0
- data/lib/fedex_ship/version.rb +6 -0
- data/spec/config/fedex_credentials.example.yml +13 -0
- data/spec/lib/fedex_ship/address_spec.rb +59 -0
- data/spec/lib/fedex_ship/delete_spec.rb +26 -0
- data/spec/lib/fedex_ship/document_spec.rb +177 -0
- data/spec/lib/fedex_ship/ground_close_spec.rb +42 -0
- data/spec/lib/fedex_ship/label_spec.rb +73 -0
- data/spec/lib/fedex_ship/pickup_availability_spec.rb +19 -0
- data/spec/lib/fedex_ship/pickup_spec.rb +32 -0
- data/spec/lib/fedex_ship/rate_spec.rb +216 -0
- data/spec/lib/fedex_ship/service_availability_spec.rb +20 -0
- data/spec/lib/fedex_ship/shipment_spec.rb +86 -0
- data/spec/lib/fedex_ship/track_spec.rb +67 -0
- data/spec/spec_helper.rb +12 -0
- data/spec/support/credentials.rb +15 -0
- data/spec/support/vcr.rb +14 -0
- metadata +193 -0
@@ -0,0 +1,115 @@
|
|
1
|
+
require 'fedex_ship/credentials'
|
2
|
+
require 'fedex_ship/request/label'
|
3
|
+
require 'fedex_ship/request/rate'
|
4
|
+
require 'fedex_ship/request/tracking_information'
|
5
|
+
require 'fedex_ship/request/address'
|
6
|
+
require 'fedex_ship/request/document'
|
7
|
+
require 'fedex_ship/request/delete'
|
8
|
+
require 'fedex_ship/request/ground_close'
|
9
|
+
require 'fedex_ship/request/pickup'
|
10
|
+
require 'fedex_ship/request/pickup_availability'
|
11
|
+
require 'fedex_ship/request/service_availability'
|
12
|
+
|
13
|
+
module FedexShip
|
14
|
+
class Shipment
|
15
|
+
|
16
|
+
# In order to use Fedex rates API you must first apply for a developer(and later production keys),
|
17
|
+
# Visit {http://www.fedex.com/us/developer/ Fedex Developer Center} for more information about how to obtain your keys.
|
18
|
+
# @param [String] key - Fedex web service key
|
19
|
+
# @param [String] password - Fedex password
|
20
|
+
# @param [String] account_number - Fedex account_number
|
21
|
+
# @param [String] meter - Fedex meter number
|
22
|
+
# @param [String] mode - [development/production]
|
23
|
+
#
|
24
|
+
# return a FedexShip::Shipment object
|
25
|
+
def initialize(options={})
|
26
|
+
@credentials = Credentials.new(options)
|
27
|
+
end
|
28
|
+
|
29
|
+
# @param [Hash] shipper, A hash containing the shipper information
|
30
|
+
# @param [Hash] recipient, A hash containing the recipient information
|
31
|
+
# @param [Array] packages, An array including a hash for each package being shipped
|
32
|
+
# @param [String] service_type, A valid fedex service type, to view a complete list of services FedexShip::Shipment::SERVICE_TYPES
|
33
|
+
# @param [String] filename, A location where the label will be saved
|
34
|
+
# @param [Hash] label_specification, A hash containing the label printer settings
|
35
|
+
def label(options = {})
|
36
|
+
Request::Label.new(@credentials, options).process_request
|
37
|
+
end
|
38
|
+
|
39
|
+
# @param [Hash] shipper, A hash containing the shipper information
|
40
|
+
# @param [Hash] recipient, A hash containing the recipient information
|
41
|
+
# @param [Array] packages, An array including a hash for each package being shipped
|
42
|
+
# @param [String] service_type, A valid fedex service type, to view a complete list of services FedexShip::Shipment::SERVICE_TYPES
|
43
|
+
def rate(options = {})
|
44
|
+
Request::Rate.new(@credentials, options).process_request
|
45
|
+
end
|
46
|
+
|
47
|
+
# @param [Hash] address, A hash containing the address information
|
48
|
+
def validate_address(options = {})
|
49
|
+
Request::Address.new(@credentials, options).process_request
|
50
|
+
end
|
51
|
+
|
52
|
+
# @param [Hash] shipper, A hash containing the shipper information
|
53
|
+
# @param [Hash] recipient, A hash containing the recipient information
|
54
|
+
# @param [Array] packages, An array including a hash for each package being shipped
|
55
|
+
# @param [String] service_type, A valid fedex service type, to view a complete list of services FedexShip::Shipment::SERVICE_TYPES
|
56
|
+
def ship(options = {})
|
57
|
+
Request::Shipment.new(@credentials, options).process_request
|
58
|
+
end
|
59
|
+
|
60
|
+
# @param [String] carrier_code, A valid fedex carrier code, to view a complete list of carrier codes FedexShip::Shipment::CARRIER_CODES
|
61
|
+
# @param [Hash] packages, A hash containing the number of packages and their total weight
|
62
|
+
# @param [DateTime] ready_timestamp, A timestamp that indicates what day and time the package will be available for pickup
|
63
|
+
# @param [Time] close_time, The latest time that the business will be open to accept a pickup
|
64
|
+
# @param [Hash] pickup_location, A hash containing the pickup location information
|
65
|
+
def pickup(options = {})
|
66
|
+
Request::Pickup.new(@credentials, options).process_request
|
67
|
+
end
|
68
|
+
|
69
|
+
# @param [Hash] package_id, A string with the requested tracking number
|
70
|
+
# @param [Hash] package_type, A string identifitying the type of tracking number used. Full list FedexShip::Track::PACKAGE_IDENTIFIER_TYPES
|
71
|
+
def track(options = {})
|
72
|
+
Request::TrackingInformation.new(@credentials, options).process_request
|
73
|
+
end
|
74
|
+
|
75
|
+
# @param [Hash] shipper, A hash containing the shipper information
|
76
|
+
# @param [Hash] recipient, A hash containing the recipient information
|
77
|
+
# @param [Array] packages, An array including a hash for each package being shipped
|
78
|
+
# @param [String] service_type, A valid fedex service type, to view a complete list of services FedexShip::Shipment::SERVICE_TYPES
|
79
|
+
# @param [Hash] customs_clearance, A hash containing customs clearance specification
|
80
|
+
# @param [Hash] shipping_document, A hash containing shipping document specification
|
81
|
+
# @param [Array] filenames, A location where the label and shipment documents will be saved
|
82
|
+
def document(options = {})
|
83
|
+
Request::Document.new(@credentials, options).process_request
|
84
|
+
end
|
85
|
+
|
86
|
+
# @param [Hash] package_id, A string with the tracking number to delete
|
87
|
+
def delete(options = {})
|
88
|
+
Request::Delete.new(@credentials, options).process_request
|
89
|
+
end
|
90
|
+
|
91
|
+
# @param [Date] up_to_time, A time up to which shipments are to be closed
|
92
|
+
# @param [String] filename, A location where the manifest (text file) will be saved
|
93
|
+
def ground_close(options = {})
|
94
|
+
Request::GroundClose.new(@credentials, options).process_request
|
95
|
+
end
|
96
|
+
# @param [String] country_code, A string containing country code
|
97
|
+
# @param [String] state_code, A string containing state code
|
98
|
+
# @param [String] postal_code, A string containing postal code
|
99
|
+
# @param [String] carrier_code, A string containing carrier code
|
100
|
+
# @param [String] request_type, A string with request type
|
101
|
+
# @param [String] dispatch_date, A string with dispatch date in YYYY-MM-DD format
|
102
|
+
def pickup_availability(options = {})
|
103
|
+
Request::PickupAvailability.new(@credentials, options).process_request
|
104
|
+
end
|
105
|
+
|
106
|
+
# param [Hash] origin, A hash containing origin information
|
107
|
+
# param [Hash] destination, A hash containing destination information
|
108
|
+
# param [date] ship_date, A string containing ship date in YYYY-MM-DD format
|
109
|
+
# param [String] carrier_code, A string containing carrier code
|
110
|
+
def service_availability(options = {})
|
111
|
+
Request::ServiceAvailability.new(@credentials, options).process_request
|
112
|
+
end
|
113
|
+
|
114
|
+
end
|
115
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
require 'fedex_ship/tracking_information/event'
|
2
|
+
|
3
|
+
module FedexShip
|
4
|
+
class TrackingInformation
|
5
|
+
PACKAGE_IDENTIFIER_TYPES = %w{
|
6
|
+
BILL_OF_LADING
|
7
|
+
COD_RETURN_TRACKING_NUMBER
|
8
|
+
CUSTOMER_AUTHORIZATION_NUMBER
|
9
|
+
CUSTOMER_REFERENCE
|
10
|
+
DEPARTMENT
|
11
|
+
FREE_FORM_REFERENCE
|
12
|
+
GROUND_INTERNATIONAL
|
13
|
+
GROUND_SHIPMENT_ID
|
14
|
+
GROUP_MPS
|
15
|
+
INVOICE
|
16
|
+
JOB_GLOBAL_TRACKING_NUMBER
|
17
|
+
ORDER_GLOBAL_TRACKING_NUMBER
|
18
|
+
ORDER_TO_PAY_NUMBER
|
19
|
+
PARTNER_CARRIER_NUMBER
|
20
|
+
PART_NUMBER
|
21
|
+
PURCHASE_ORDER
|
22
|
+
RETURN_MATERIALS_AUTHORIZATION
|
23
|
+
RETURNED_TO_SHIPPER_TRACKING_NUMBER
|
24
|
+
TRACKING_CONTROL_NUMBER
|
25
|
+
TRACKING_NUMBER_OR_DOORTAG
|
26
|
+
TRANSPORTATION_CONTROL_NUMBER
|
27
|
+
SHIPPER_REFERENCE
|
28
|
+
STANDARD_MPS
|
29
|
+
}
|
30
|
+
|
31
|
+
attr_reader :tracking_number, :signature_name, :service_type, :status, :status_code, :delivery_at, :events, :unique_tracking_number, :details, :other_identifiers
|
32
|
+
|
33
|
+
def initialize(details = {})
|
34
|
+
@details = details
|
35
|
+
|
36
|
+
@tracking_number = details[:tracking_number]
|
37
|
+
@unique_tracking_number = details[:tracking_number_unique_identifier]
|
38
|
+
@signature_name = details[:delivery_signature_name]
|
39
|
+
@service_type = details[:service][:type]
|
40
|
+
@status = details[:status_detail][:description]
|
41
|
+
@status_code = details[:status_detail][:code]
|
42
|
+
@other_identifiers = details[:other_identifiers]
|
43
|
+
|
44
|
+
if details.has_key?(:actual_delivery_timestamp)
|
45
|
+
@delivery_at = Time.parse(details[:actual_delivery_timestamp])
|
46
|
+
end
|
47
|
+
|
48
|
+
@events = [details[:events]].flatten.compact.map do |event_details|
|
49
|
+
Event.new(event_details)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module FedexShip
|
2
|
+
class TrackingInformation
|
3
|
+
class Event
|
4
|
+
attr_reader :description, :type, :occurred_at, :city, :state, :postal_code,
|
5
|
+
:country, :residential, :exception_code, :exception_description
|
6
|
+
|
7
|
+
alias_method :occured_at, :occurred_at
|
8
|
+
|
9
|
+
def initialize(details = {})
|
10
|
+
@description = details[:event_description]
|
11
|
+
@type = details[:event_type]
|
12
|
+
@occurred_at = Time.parse(details[:timestamp])
|
13
|
+
@city = details[:address][:city]
|
14
|
+
@state = details[:address][:state_or_province_code]
|
15
|
+
@postal_code = details[:address][:postal_code]
|
16
|
+
@country = details[:address][:country_code]
|
17
|
+
@residential = details[:address][:residential] == "true"
|
18
|
+
@exception_code = details[:status_exception_code]
|
19
|
+
@exception_description = details[:status_exception_description]
|
20
|
+
end
|
21
|
+
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,59 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module FedexShip
|
4
|
+
describe Address, :production do
|
5
|
+
describe "validation" do
|
6
|
+
|
7
|
+
# Address Validation is only enabled in the production environment
|
8
|
+
#
|
9
|
+
let(:fedex) { Shipment.new(fedex_production_credentials) }
|
10
|
+
|
11
|
+
context "valid address", :vcr do
|
12
|
+
let(:address) do
|
13
|
+
{
|
14
|
+
:address => "5 Elm Street",
|
15
|
+
:city => "Norwalk",
|
16
|
+
:state => "CT",
|
17
|
+
:postal_code => "06850",
|
18
|
+
:country => "USA"
|
19
|
+
}
|
20
|
+
end
|
21
|
+
|
22
|
+
let(:options) do
|
23
|
+
{ :address => address }
|
24
|
+
end
|
25
|
+
|
26
|
+
it "validates the address" do
|
27
|
+
address = fedex.validate_address(options)
|
28
|
+
|
29
|
+
expect(address.residential).to be_truthy
|
30
|
+
expect(address.business).to be_falsey
|
31
|
+
expect(address.score).to eq(100)
|
32
|
+
|
33
|
+
expect(address.postal_code).to eq("06850-3901")
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context "multiple address validation results", :vcr do
|
38
|
+
let(:address) do
|
39
|
+
{
|
40
|
+
:address => "301 Las Colinas Blvd",
|
41
|
+
:city => "Irving",
|
42
|
+
:state => "TX",
|
43
|
+
:postal_code => "75039",
|
44
|
+
:country => "USA"
|
45
|
+
}
|
46
|
+
end
|
47
|
+
|
48
|
+
let(:options) do
|
49
|
+
{ :address => address }
|
50
|
+
end
|
51
|
+
|
52
|
+
it "validates the address" do
|
53
|
+
expect{ fedex.validate_address(options) }.to_not raise_error
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -0,0 +1,26 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
module FedexShip
|
4
|
+
describe Shipment do
|
5
|
+
let (:fedex) { Shipment.new(fedex_credentials) }
|
6
|
+
context "#delete" do
|
7
|
+
context "delete shipment with tracking number", :vcr do
|
8
|
+
let(:options) do
|
9
|
+
{ :tracking_number => '794608797150' }
|
10
|
+
end
|
11
|
+
it "deletes a shipment" do
|
12
|
+
expect{ fedex.delete(options) }.to_not raise_error
|
13
|
+
end
|
14
|
+
end
|
15
|
+
context "raise an error when the tracking number is invalid", :vcr do
|
16
|
+
let(:options) do
|
17
|
+
{ :tracking_number => '111111111' }
|
18
|
+
end
|
19
|
+
|
20
|
+
it "raises an error" do
|
21
|
+
expect {fedex.delete(options) }.to raise_error(FedexShip::RateError, 'Invalid tracking number')
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
@@ -0,0 +1,177 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'tmpdir'
|
3
|
+
|
4
|
+
module FedexShip
|
5
|
+
describe Document do
|
6
|
+
let(:fedex) { Shipment.new(fedex_credentials) }
|
7
|
+
let(:shipper) do
|
8
|
+
{ :name => "Sender", :company => "Company", :phone_number => "555-555-5555", :address => "King Street", :city => "Ashbourne", :postal_code => "DE6 1EA", :country_code => "GB" }
|
9
|
+
end
|
10
|
+
let(:recipient) do
|
11
|
+
{ :name => "Recipient", :company => "Company", :phone_number => "555-555-5555", :address => "Main Street", :city => "Frankin Park", :state => "IL", :postal_code => "60131", :country_code => "US", :residential => true }
|
12
|
+
end
|
13
|
+
let(:packages) do
|
14
|
+
[
|
15
|
+
{
|
16
|
+
:weight => { :units => "LB", :value => 2 },
|
17
|
+
:dimensions => { :length => 10, :width => 5, :height => 4, :units => "IN" }
|
18
|
+
}
|
19
|
+
]
|
20
|
+
end
|
21
|
+
let(:shipping_options) do
|
22
|
+
{ :packaging_type => "YOUR_PACKAGING", :drop_off_type => "REGULAR_PICKUP" }
|
23
|
+
end
|
24
|
+
let(:customs) do
|
25
|
+
{
|
26
|
+
:duties_payment => {
|
27
|
+
:payment_type => 'SENDER',
|
28
|
+
:payor => {
|
29
|
+
:responsible_party => {
|
30
|
+
:account_number => fedex_credentials[:account_number],
|
31
|
+
:contact => {
|
32
|
+
:person_name => 'Mr. Test',
|
33
|
+
:phone_number => '12345678'
|
34
|
+
}
|
35
|
+
}
|
36
|
+
}
|
37
|
+
},
|
38
|
+
:document_content => 'NON_DOCUMENTS',
|
39
|
+
:customs_value => {
|
40
|
+
:currency => 'UKL', # UK Pounds Sterling
|
41
|
+
:amount => 155.79
|
42
|
+
},
|
43
|
+
:commercial_invoice => {
|
44
|
+
:terms_of_sale => 'DDU'
|
45
|
+
},
|
46
|
+
:commodities => [
|
47
|
+
{
|
48
|
+
:number_of_pieces => 1,
|
49
|
+
:description => 'Pink Toy',
|
50
|
+
:country_of_manufacture => 'GB',
|
51
|
+
:weight => {
|
52
|
+
:units => 'LB',
|
53
|
+
:value => 2
|
54
|
+
},
|
55
|
+
:quantity => 1,
|
56
|
+
:quantity_units => 'EA',
|
57
|
+
:unit_price => {
|
58
|
+
:currency => 'UKL',
|
59
|
+
:amount => 155.79
|
60
|
+
},
|
61
|
+
:customs_value => {
|
62
|
+
:currency => 'UKL', # UK Pounds Sterling
|
63
|
+
:amount => 155.79
|
64
|
+
}
|
65
|
+
}
|
66
|
+
]
|
67
|
+
}
|
68
|
+
end
|
69
|
+
let(:document) do
|
70
|
+
{
|
71
|
+
:shipping_document_types => 'COMMERCIAL_INVOICE',
|
72
|
+
:commercial_invoice_detail => {
|
73
|
+
:format => {
|
74
|
+
:image_type => 'PDF',
|
75
|
+
:stock_type => 'PAPER_LETTER'
|
76
|
+
}
|
77
|
+
}
|
78
|
+
}
|
79
|
+
end
|
80
|
+
|
81
|
+
let(:filenames) {
|
82
|
+
require 'tmpdir'
|
83
|
+
{
|
84
|
+
:label => File.join(Dir.tmpdir, "label#{rand(15000)}.pdf"),
|
85
|
+
:commercial_invoice => File.join(Dir.tmpdir, "invoice#{rand(15000)}.pdf")
|
86
|
+
}
|
87
|
+
}
|
88
|
+
|
89
|
+
let(:options) do
|
90
|
+
{
|
91
|
+
:shipper => shipper,
|
92
|
+
:recipient => recipient,
|
93
|
+
:packages => packages,
|
94
|
+
:service_type => "INTERNATIONAL_PRIORITY",
|
95
|
+
:shipping_details => shipping_options,
|
96
|
+
:customs_clearance_detail => customs,
|
97
|
+
:shipping_document => document,
|
98
|
+
:filenames => filenames
|
99
|
+
}
|
100
|
+
end
|
101
|
+
|
102
|
+
describe 'document service', :vcr do
|
103
|
+
|
104
|
+
context 'with document specification' do
|
105
|
+
|
106
|
+
before do
|
107
|
+
@document = fedex.document(options)
|
108
|
+
end
|
109
|
+
|
110
|
+
it "saves a label to file" do
|
111
|
+
expect(File).to exist(filenames[:label])
|
112
|
+
end
|
113
|
+
|
114
|
+
it "saves invoice to file" do
|
115
|
+
expect(File).to exist(filenames[:commercial_invoice])
|
116
|
+
end
|
117
|
+
|
118
|
+
it "returns tracking number" do
|
119
|
+
expect(@document).to respond_to('tracking_number')
|
120
|
+
end
|
121
|
+
|
122
|
+
it "exposes complete response" do
|
123
|
+
expect(@document).to respond_to('response_details')
|
124
|
+
end
|
125
|
+
|
126
|
+
it "exposes the filenames" do
|
127
|
+
expect(@document).to respond_to('filenames')
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
131
|
+
|
132
|
+
context 'without document specification' do
|
133
|
+
|
134
|
+
before do
|
135
|
+
@document = fedex.document(
|
136
|
+
options.reject{|k| k == :shipping_document}
|
137
|
+
)
|
138
|
+
end
|
139
|
+
|
140
|
+
it "saves a label to file" do
|
141
|
+
expect(File).to exist(filenames[:label])
|
142
|
+
end
|
143
|
+
|
144
|
+
it "has no others files" do
|
145
|
+
expect(File).not_to exist(filenames[:commercial_invoice])
|
146
|
+
end
|
147
|
+
|
148
|
+
end
|
149
|
+
|
150
|
+
context 'filename missed' do
|
151
|
+
context 'for label' do
|
152
|
+
before do
|
153
|
+
filenames.delete(:label)
|
154
|
+
@document = fedex.document(options)
|
155
|
+
end
|
156
|
+
|
157
|
+
it "saves invoice to file" do
|
158
|
+
expect(File).to exist(filenames[:commercial_invoice])
|
159
|
+
end
|
160
|
+
end
|
161
|
+
|
162
|
+
context 'for invoice' do
|
163
|
+
before do
|
164
|
+
filenames.delete(:commercial_invoice)
|
165
|
+
@document = fedex.document(options)
|
166
|
+
end
|
167
|
+
|
168
|
+
it "saves label to file" do
|
169
|
+
expect(File).to exist(filenames[:label])
|
170
|
+
end
|
171
|
+
end
|
172
|
+
end
|
173
|
+
|
174
|
+
end
|
175
|
+
|
176
|
+
end
|
177
|
+
end
|