spree_marketplace 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +13 -0
  5. data/Gemfile +14 -0
  6. data/LICENSE +21 -0
  7. data/README.md +87 -0
  8. data/Rakefile +15 -0
  9. data/Versionfile +12 -0
  10. data/app/assets/javascripts/spree/backend/spree_marketplace.js.coffee +24 -0
  11. data/app/assets/javascripts/spree/frontend/spree_marketplace.js +2 -0
  12. data/app/assets/javascripts/spree/frontend/supplier_address.js.coffee +56 -0
  13. data/app/assets/stylesheets/spree/backend/spree_marketplace.css +3 -0
  14. data/app/assets/stylesheets/spree/frontend/spree_marketplace.css +10 -0
  15. data/app/controllers/spree/admin/marketplace_settings_controller.rb +19 -0
  16. data/app/controllers/spree/admin/supplier_bank_accounts_controller.rb +24 -0
  17. data/app/controllers/spree/suppliers_controller.rb +67 -0
  18. data/app/models/spree/marketplace_ability.rb +14 -0
  19. data/app/models/spree/marketplace_configuration.rb +27 -0
  20. data/app/models/spree/supplier_ability.rb +76 -0
  21. data/app/models/spree/supplier_bank_account.rb +15 -0
  22. data/app/models/spree/supplier_decorator.rb +55 -0
  23. data/app/overrides/spree/admin/shared/_configuration_menu/add_marketplace_settings.html.erb.deface +3 -0
  24. data/app/overrides/spree/admin/suppliers/edit/add_bank_accounts.html.erb.deface +14 -0
  25. data/app/overrides/spree/layouts/admin/add_stripe_js_to_head.html.erb.deface +6 -0
  26. data/app/overrides/spree/users/show/add_supplier_info.html.erb.deface +17 -0
  27. data/app/views/spree/admin/marketplace_settings/edit.html.erb +23 -0
  28. data/app/views/spree/admin/supplier_bank_accounts/new.html.erb +28 -0
  29. data/app/views/spree/suppliers/new.html.erb +29 -0
  30. data/config/locales/en.yml +14 -0
  31. data/config/locales/es.yml +30 -0
  32. data/config/routes.rb +9 -0
  33. data/db/migrate/20130424201333_create_supplier_bank_accounts.rb +12 -0
  34. data/db/migrate/20131209022116_convert_to_stripe.rb +6 -0
  35. data/lib/generators/spree_marketplace/install/install_generator.rb +31 -0
  36. data/lib/spree_marketplace.rb +4 -0
  37. data/lib/spree_marketplace/engine.rb +47 -0
  38. data/lib/spree_marketplace/factories.rb +29 -0
  39. data/script/rails +7 -0
  40. data/spec/features/admin/bank_accounts_spec.rb +50 -0
  41. data/spec/features/admin/products_spec.rb +225 -0
  42. data/spec/features/admin/properties_spec.rb +91 -0
  43. data/spec/features/admin/relations_spec.rb +31 -0
  44. data/spec/features/admin/return_authorizations_spec.rb +7 -0
  45. data/spec/features/admin/settings_spec.rb +29 -0
  46. data/spec/features/admin/suppliers_controller_spec.rb +43 -0
  47. data/spec/features/supplier_signup_spec.rb +134 -0
  48. data/spec/models/spree/marketplace_ability_spec.rb +32 -0
  49. data/spec/models/spree/supplier_ability_spec.rb +337 -0
  50. data/spec/models/spree/supplier_bank_account_spec.rb +13 -0
  51. data/spec/models/spree/supplier_spec.rb +7 -0
  52. data/spec/spec_helper.rb +85 -0
  53. data/spec/support/integration_helpers.rb +15 -0
  54. data/spree_marketplace.gemspec +90 -0
  55. metadata +391 -0
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe 'Admin - Marketplace Settings', js: true do
4
+
5
+ before do
6
+ login_user create(:admin_user)
7
+
8
+ visit spree.admin_path
9
+ within 'ul[data-hook=admin_tabs]' do
10
+ click_link 'Configuration'
11
+ end
12
+ within 'ul[data-hook=admin_configurations_sidebar_menu]' do
13
+ click_link 'Marketplace Settings'
14
+ end
15
+ end
16
+
17
+ it 'should be able to be updated' do
18
+ # Change settings
19
+ check 'allow_signup'
20
+ click_button 'Update'
21
+ expect(page).to have_content('Marketplace settings successfully updated.')
22
+
23
+ # Verify update saved properly by reversing checkboxes or checking field values.
24
+ uncheck 'allow_signup'
25
+ click_button 'Update'
26
+ expect(page).to have_content('Marketplace settings successfully updated.')
27
+ end
28
+
29
+ end
@@ -0,0 +1,43 @@
1
+ require "spec_helper"
2
+
3
+ feature "Admin - Suppliers", js: true do
4
+
5
+ before do
6
+ country = create(:country, name: "United States")
7
+ create(:state, name: "Vermont", country: country)
8
+ @supplier = create :supplier
9
+ end
10
+
11
+ context "as an Admin" do
12
+
13
+ before do
14
+ login_user create(:admin_user)
15
+ visit spree.admin_path
16
+ within "ul[data-hook=admin_tabs]" do
17
+ click_link "Suppliers"
18
+ end
19
+ page.should have_content("Listing Suppliers")
20
+ end
21
+
22
+ scenario "should be able to create new supplier" do
23
+ click_link "New Supplier"
24
+ check "supplier_active"
25
+ fill_in "supplier[name]", with: "Test Supplier"
26
+ fill_in "supplier[email]", with: "spree@example.com"
27
+ fill_in "supplier[url]", with: "http://www.test.com"
28
+ fill_in "supplier[commission_flat_rate]", with: "0"
29
+ fill_in "supplier[commission_percentage]", with: "0"
30
+ fill_in "supplier[address_attributes][firstname]", with: "First"
31
+ fill_in "supplier[address_attributes][lastname]", with: "Last"
32
+ fill_in "supplier[address_attributes][address1]", with: "1 Test Drive"
33
+ fill_in "supplier[address_attributes][city]", with: "Test City"
34
+ fill_in "supplier[address_attributes][zipcode]", with: "55555"
35
+ select2 "United States", from: "Country"
36
+ select2 "Vermont", from: "State"
37
+ fill_in "supplier[address_attributes][phone]", with: "555-555-5555"
38
+ click_button "Create"
39
+ page.should have_content('Supplier "Test Supplier" has been successfully created!')
40
+ end
41
+
42
+ end
43
+ end
@@ -0,0 +1,134 @@
1
+ require 'spec_helper'
2
+
3
+ feature 'Supplier Signup', js: true do
4
+
5
+ scenario 'existing supplier' do
6
+ @user = create(:user)
7
+ create(:supplier, email: @user.email, users: [@user])
8
+ login_user @user
9
+ visit spree.new_supplier_path
10
+ page.current_path.should eql(spree.account_path)
11
+ page.should have_content("You've already signed up to become a supplier.")
12
+ end
13
+
14
+ context 'as guest user' do
15
+
16
+ after do
17
+ SpreeMarketplace::Config.set(allow_signup: false)
18
+ end
19
+
20
+ before do
21
+ SpreeMarketplace::Config.set(allow_signup: true)
22
+ visit spree.new_supplier_path
23
+ page.should have_content('LOGIN')
24
+ end
25
+
26
+ scenario 'as business' do
27
+ fill_in 'supplier[name]', with: 'Spree'
28
+ fill_in 'supplier[first_name]', with: 'First'
29
+ fill_in 'supplier[last_name]', with: 'Last'
30
+ fill_in 'supplier[email]', with: 'spree@example.com'
31
+ fill_in 'supplier[password]', with: 'password'
32
+ fill_in 'supplier[password_confirmation]', with: 'password'
33
+ fill_in 'supplier[tax_id]', with: '000000000'
34
+ click_button 'Sign Up'
35
+ page.should have_content('Thank you for signing up!')
36
+ page.should_not have_content('LOGIN')
37
+ end
38
+
39
+ scenario 'as individual' do
40
+ fill_in 'supplier[name]', with: 'Spree'
41
+ fill_in 'supplier[first_name]', with: 'First'
42
+ fill_in 'supplier[last_name]', with: 'Last'
43
+ fill_in 'supplier[email]', with: 'spree@example.com'
44
+ fill_in 'supplier[password]', with: 'password'
45
+ fill_in 'supplier[password_confirmation]', with: 'password'
46
+ click_button 'Sign Up'
47
+ page.should have_content('Thank you for signing up!')
48
+ page.should_not have_content('LOGIN')
49
+ end
50
+
51
+ scenario 'should require correct password for existing user account' do
52
+ fill_in 'supplier[email]', with: create(:user).email
53
+ fill_in 'supplier[first_name]', with: 'First'
54
+ fill_in 'supplier[last_name]', with: 'Last'
55
+ fill_in 'supplier[password]', with: 'invalid'
56
+ fill_in 'supplier[password_confirmation]', with: 'invalid'
57
+ fill_in 'supplier[name]', with: 'Test Supplier'
58
+ fill_in 'supplier[tax_id]', with: '000000000'
59
+ click_on 'Sign Up'
60
+ expect(page).to have_content("Invalid password please sign in or sign up.")
61
+ end
62
+ end
63
+
64
+ context 'logged in' do
65
+
66
+ before do
67
+ @user = create(:user)
68
+ login_user @user
69
+ end
70
+
71
+ context 'w/ DropShipConfig[:allow_signup] == false (the default)' do
72
+
73
+ before do
74
+ SpreeMarketplace::Config.set(allow_signup: false)
75
+ end
76
+
77
+ scenario 'should not be able to create new supplier' do
78
+ visit spree.account_path
79
+ within '#user-info' do
80
+ page.should_not have_link 'Sign Up To Become A Supplier'
81
+ end
82
+ visit spree.new_supplier_path
83
+ page.should have_content('Authorization Failure')
84
+ end
85
+
86
+ end
87
+
88
+ context 'w/ DropShipConfig[:allow_signup] == true' do
89
+
90
+ after do
91
+ SpreeMarketplace::Config.set(allow_signup: false)
92
+ end
93
+
94
+ before do
95
+ SpreeMarketplace::Config.set(allow_signup: true)
96
+ visit spree.account_path
97
+ end
98
+
99
+ scenario 'should be able to create new individual supplier' do
100
+ within '#user-info' do
101
+ click_link 'Sign Up To Become A Supplier'
102
+ end
103
+ fill_in 'supplier[name]', with: 'Test Supplier'
104
+ fill_in 'supplier[first_name]', with: 'First'
105
+ fill_in 'supplier[last_name]', with: 'Last'
106
+ click_button 'Sign Up'
107
+ page.should have_content('Thank you for signing up!')
108
+ end
109
+
110
+ scenario 'should be able to create new business supplier' do
111
+ within '#user-info' do
112
+ click_link 'Sign Up To Become A Supplier'
113
+ end
114
+ fill_in 'supplier[name]', with: 'Test Supplier'
115
+ fill_in 'supplier[first_name]', with: 'First'
116
+ fill_in 'supplier[last_name]', with: 'Last'
117
+ fill_in 'supplier[tax_id]', with: '000000000'
118
+ click_button 'Sign Up'
119
+ page.should have_content('Thank you for signing up!')
120
+ end
121
+
122
+ scenario 'should display errors with invalid supplier' do
123
+ within '#user-info' do
124
+ click_link 'Sign Up To Become A Supplier'
125
+ end
126
+ click_button 'Sign Up'
127
+ page.should have_content('This field is required.')
128
+ end
129
+
130
+ end
131
+
132
+ end
133
+
134
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+ require 'cancan/matchers'
3
+ require 'spree/testing_support/ability_helpers'
4
+
5
+ describe Spree::MarketplaceAbility do
6
+
7
+ let(:user) { create(:user, supplier: create(:supplier)) }
8
+ let(:ability) { Spree::MarketplaceAbility.new(user) }
9
+ let(:token) { nil }
10
+
11
+ context 'for SupplierBankAccount' do
12
+ context 'requested by non supplier user' do
13
+ let(:ability) { Spree::MarketplaceAbility.new(create(:user)) }
14
+ let(:resource) { Spree::SupplierBankAccount.new }
15
+
16
+ it_should_behave_like 'admin denied'
17
+ it_should_behave_like 'access denied'
18
+ end
19
+
20
+ context 'requested by suppliers user' do
21
+ let(:resource) { Spree::SupplierBankAccount.new({supplier: user.supplier}, without_protection: true) }
22
+ it_should_behave_like 'admin granted'
23
+ it_should_behave_like 'access granted'
24
+ end
25
+
26
+ context 'requested by another suppliers user' do
27
+ let(:resource) { Spree::SupplierBankAccount.new({supplier: create(:supplier)}, without_protection: true) }
28
+ it_should_behave_like 'create only'
29
+ end
30
+ end
31
+
32
+ end
@@ -0,0 +1,337 @@
1
+ require 'spec_helper'
2
+ require 'cancan/matchers'
3
+ require 'spree/testing_support/ability_helpers'
4
+
5
+ describe Spree::SupplierAbility do
6
+
7
+ let(:user) { create(:user, supplier: create(:supplier)) }
8
+ let(:ability) { Spree::SupplierAbility.new(user) }
9
+ let(:token) { nil }
10
+
11
+ context 'for Dash' do
12
+ let(:resource) { Spree::Admin::OverviewController }
13
+
14
+ context 'requested by supplier' do
15
+ it_should_behave_like 'access denied'
16
+ it_should_behave_like 'no index allowed'
17
+ it_should_behave_like 'admin denied'
18
+ end
19
+ end
20
+
21
+ context 'for Digital' do
22
+ let(:resource) { Spree::Digital }
23
+
24
+ it_should_behave_like 'index allowed'
25
+ it_should_behave_like 'admin granted'
26
+
27
+ context 'requested by another suppliers user' do
28
+ let(:resource) {
29
+ p = create :product
30
+ p.add_supplier! create(:supplier)
31
+ Spree::Digital.new(variant: p.reload.master)
32
+ }
33
+ it_should_behave_like 'create only'
34
+ end
35
+
36
+ context 'requested by suppliers user' do
37
+ let(:resource) {
38
+ p = create :product
39
+ p.add_supplier! user.supplier
40
+ Spree::Digital.new(variant: p.reload.master)
41
+ }
42
+ it_should_behave_like 'access granted'
43
+ end
44
+ end
45
+
46
+ context 'for Image' do
47
+ let(:resource) { Spree::Image }
48
+
49
+ it_should_behave_like 'index allowed'
50
+ it_should_behave_like 'admin granted'
51
+
52
+ context 'requested by another suppliers user' do
53
+ let(:variant) {
54
+ p = create :product
55
+ p.add_supplier! create(:supplier)
56
+ p.master
57
+ }
58
+ let(:resource) { Spree::Image.new({viewable_id: variant.id, viewable_type: variant.class.to_s}, without_protection: true) }
59
+ it_should_behave_like 'create only'
60
+ end
61
+
62
+ context 'requested by suppliers user' do
63
+ let(:variant) {
64
+ p = create :product
65
+ p.add_supplier! user.supplier
66
+ p.master
67
+ }
68
+ let(:resource) { Spree::Image.new({viewable_id: variant.id, viewable_type: variant.class.to_s}, without_protection: true) }
69
+ it_should_behave_like 'access granted'
70
+ end
71
+ end
72
+
73
+ context 'for GroupPricing' do
74
+ let(:resource) { Spree::GroupPrice }
75
+
76
+ context 'requested by another suppliers user' do
77
+ let(:resource) {
78
+ p = create :product
79
+ p.add_supplier! create(:supplier)
80
+ Spree::GroupPrice.new(variant: p.reload.master)
81
+ }
82
+ it_should_behave_like 'access denied'
83
+ end
84
+
85
+ context 'requested by suppliers user' do
86
+ let(:resource) {
87
+ p = create :product
88
+ p.add_supplier! user.supplier
89
+ Spree::GroupPrice.new(variant: p.reload.master)
90
+ }
91
+ it_should_behave_like 'access granted'
92
+ end
93
+ end
94
+
95
+ context 'for Product' do
96
+ let(:resource) { Spree::Product }
97
+
98
+ it_should_behave_like 'index allowed'
99
+ it_should_behave_like 'admin granted'
100
+
101
+ context 'requested by another suppliers user' do
102
+ let(:resource) {
103
+ p = create :product
104
+ p.add_supplier! create(:supplier)
105
+ p
106
+ }
107
+ it { ability.should be_able_to :create, resource }
108
+ end
109
+
110
+ context 'requested by suppliers user' do
111
+ let(:resource) {
112
+ p = create :product
113
+ p.add_supplier! user.supplier
114
+ p.reload
115
+ }
116
+ it_should_behave_like 'access granted'
117
+ end
118
+ end
119
+
120
+ context 'for ProductProperty' do
121
+ let(:resource) { Spree::ProductProperty }
122
+
123
+ it_should_behave_like 'index allowed'
124
+ it_should_behave_like 'admin granted'
125
+
126
+ context 'requested by another suppliers user' do
127
+ let(:resource) {
128
+ p = create :product
129
+ p.add_supplier! create(:supplier)
130
+ Spree::ProductProperty.new(product: p.reload)
131
+ }
132
+ it_should_behave_like 'access denied'
133
+ end
134
+
135
+ context 'requested by suppliers user' do
136
+ let(:resource) {
137
+ p = create :product
138
+ p.add_supplier! user.supplier
139
+ Spree::ProductProperty.new(product: p.reload)
140
+ }
141
+ it_should_behave_like 'access granted'
142
+ end
143
+ end
144
+
145
+ context 'for Property' do
146
+ let(:resource) { Spree::Property }
147
+
148
+ it_should_behave_like 'admin granted'
149
+ it_should_behave_like 'read only'
150
+ end
151
+
152
+ context 'for Prototype' do
153
+ let(:resource) { Spree::Prototype }
154
+
155
+ it_should_behave_like 'admin granted'
156
+ it_should_behave_like 'read only'
157
+ end
158
+
159
+ context 'for Relation' do
160
+ let(:resource) { Spree::Relation }
161
+
162
+ it_should_behave_like 'index allowed'
163
+ it_should_behave_like 'admin granted'
164
+
165
+ context 'requested by another suppliers user' do
166
+ let(:resource) {
167
+ p = create :product
168
+ p.add_supplier! create(:supplier)
169
+ Spree::Relation.new(relatable: p.reload)
170
+ }
171
+ it_should_behave_like 'access denied'
172
+ end
173
+
174
+ context 'requested by suppliers user' do
175
+ let(:resource) {
176
+ p = create :product
177
+ p.add_supplier! user.supplier
178
+ Spree::Relation.new(relatable: p.reload)
179
+ }
180
+ it_should_behave_like 'access granted'
181
+ end
182
+ end
183
+
184
+ context 'for Shipment' do
185
+ context 'requested by another suppliers user' do
186
+ let(:resource) { Spree::Shipment.new({stock_location: create(:stock_location, supplier: create(:supplier))}, without_protection: true) }
187
+ it_should_behave_like 'access denied'
188
+ it_should_behave_like 'no index allowed'
189
+ it_should_behave_like 'admin denied'
190
+ it { ability.should_not be_able_to :ready, resource }
191
+ it { ability.should_not be_able_to :ship, resource }
192
+ end
193
+
194
+ context 'requested by suppliers user' do
195
+ context 'when order is complete' do
196
+ let(:resource) {
197
+ order = create(:completed_order_for_drop_ship_with_totals)
198
+ order.stock_locations.first.update_attribute :supplier, user.supplier
199
+ Spree::Shipment.new({order: order, stock_location: order.stock_locations.first }, without_protection: true)
200
+ }
201
+ it_should_behave_like 'access granted'
202
+ it_should_behave_like 'index allowed'
203
+ it_should_behave_like 'admin granted'
204
+ it { ability.should be_able_to :ready, resource }
205
+ it { ability.should be_able_to :ship, resource }
206
+ end
207
+
208
+ context 'when order is incomplete' do
209
+ let(:resource) { Spree::Shipment.new({stock_location: create(:stock_location, supplier: user.supplier)}, without_protection: true) }
210
+ it_should_behave_like 'access denied'
211
+ it { ability.should_not be_able_to :ready, resource }
212
+ it { ability.should_not be_able_to :ship, resource }
213
+ end
214
+ end
215
+ end
216
+
217
+ context 'for StockItem' do
218
+ let(:resource) { Spree::StockItem }
219
+
220
+ it_should_behave_like 'index allowed'
221
+ it_should_behave_like 'admin granted'
222
+
223
+ context 'requested by another suppliers user' do
224
+ let(:resource) {
225
+ p = create :product
226
+ supplier = create(:supplier)
227
+ p.add_supplier! supplier
228
+ Spree::StockItem.new(stock_location_id: supplier.stock_locations.first.id)
229
+ }
230
+ it_should_behave_like 'access denied'
231
+ end
232
+
233
+ context 'requested by suppliers user' do
234
+ let(:resource) {
235
+ p = create :product
236
+ p.add_supplier! user.supplier
237
+ Spree::StockItem.new(stock_location_id: user.supplier.stock_locations.first.id)
238
+ }
239
+ it_should_behave_like 'access granted'
240
+ end
241
+ end
242
+
243
+ context 'for StockLocation' do
244
+ context 'requsted by another suppliers user' do
245
+ let(:resource) { Spree::StockLocation.new({supplier: create(:supplier)}, without_protection: true) }
246
+ it_should_behave_like 'create only'
247
+ end
248
+
249
+ context 'requested by suppliers user' do
250
+ let(:resource) { Spree::StockLocation.new({supplier: user.supplier}, without_protection: true) }
251
+ it_should_behave_like 'access granted'
252
+ it_should_behave_like 'admin granted'
253
+ it_should_behave_like 'index allowed'
254
+ end
255
+ end
256
+
257
+ context 'for StockMovement' do
258
+ let(:resource) { Spree::StockMovement }
259
+
260
+ it_should_behave_like 'index allowed'
261
+ it_should_behave_like 'admin granted'
262
+
263
+ context 'requested by another suppliers user' do
264
+ let(:resource) {
265
+ p = create :product
266
+ supplier = create(:supplier)
267
+ p.add_supplier! supplier
268
+ Spree::StockMovement.new stock_item: p.master.stock_items.where(stock_location_id: supplier.stock_locations.pluck(:id)).first
269
+ }
270
+ it_should_behave_like 'create only'
271
+ end
272
+
273
+ context 'requested by suppliers user' do
274
+ let(:resource) {
275
+ p = create :product
276
+ p.add_supplier! user.supplier
277
+ Spree::StockMovement.new stock_item: p.master.stock_items.where(stock_location_id: user.supplier.stock_locations.pluck(:id)).first
278
+ }
279
+ it_should_behave_like 'access granted'
280
+ end
281
+ end
282
+
283
+ context 'for Supplier' do
284
+ context 'requested by any user' do
285
+ let(:ability) { Spree::SupplierAbility.new(create(:user)) }
286
+ let(:resource) { Spree::Supplier }
287
+
288
+ it_should_behave_like 'admin denied'
289
+
290
+ context 'w/ Marketplace Config[:allow_signup] == false (the default)' do
291
+ it_should_behave_like 'access denied'
292
+ end
293
+
294
+ context 'w/ Marketplace Config[:allow_signup] == true' do
295
+ after do
296
+ SpreeMarketplace::Config.set allow_signup: false
297
+ end
298
+ before do
299
+ SpreeMarketplace::Config.set allow_signup: true
300
+ end
301
+ it_should_behave_like 'create only'
302
+ end
303
+ end
304
+
305
+ context 'requested by suppliers user' do
306
+ let(:resource) { user.supplier }
307
+ it_should_behave_like 'admin granted'
308
+ it_should_behave_like 'update only'
309
+ end
310
+ end
311
+
312
+ context 'for Variant' do
313
+ let(:resource) { Spree::Variant }
314
+
315
+ it_should_behave_like 'index allowed'
316
+ it_should_behave_like 'admin granted'
317
+
318
+ context 'requested by another suppliers user' do
319
+ let(:resource) {
320
+ p = create :product
321
+ p.add_supplier! create(:supplier)
322
+ p.reload.master
323
+ }
324
+ it_should_behave_like 'access denied'
325
+ end
326
+
327
+ context 'requested by suppliers user' do
328
+ let(:resource) {
329
+ p = create :product
330
+ p.add_supplier! user.supplier
331
+ p.reload.master
332
+ }
333
+ it_should_behave_like 'access granted'
334
+ end
335
+ end
336
+
337
+ end