solidus_product_bundle 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (95) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +5 -0
  3. data/.rspec +3 -0
  4. data/.travis.yml +11 -0
  5. data/Gemfile +9 -0
  6. data/LICENSE.md +13 -0
  7. data/README.markdown +95 -0
  8. data/README.md +2 -0
  9. data/Rakefile +15 -0
  10. data/app/assets/images/spinner.gif +0 -0
  11. data/app/assets/javascripts/spree/backend/spree_product_assembly/index.js.coffee +82 -0
  12. data/app/assets/javascripts/spree/backend/spree_product_assembly/translations.js.erb +3 -0
  13. data/app/assets/javascripts/spree/frontend/spree_product_assembly.js +1 -0
  14. data/app/assets/stylesheets/spree/backend/spree_product_assembly.css +3 -0
  15. data/app/assets/stylesheets/spree/frontend/spree_product_assembly.css +3 -0
  16. data/app/controllers/spree/admin/parts_controller.rb +63 -0
  17. data/app/controllers/spree/checkout_controller_decorator.rb +9 -0
  18. data/app/helpers/spree/admin/orders_helper_decorator.rb +9 -0
  19. data/app/models/spree/assemblies_part.rb +33 -0
  20. data/app/models/spree/assign_part_to_bundle_form.rb +64 -0
  21. data/app/models/spree/inventory_unit_decorator.rb +13 -0
  22. data/app/models/spree/line_item_decorator.rb +65 -0
  23. data/app/models/spree/order_contents_decorator.rb +35 -0
  24. data/app/models/spree/order_inventory_assembly.rb +54 -0
  25. data/app/models/spree/part_line_item.rb +6 -0
  26. data/app/models/spree/product_decorator.rb +37 -0
  27. data/app/models/spree/shipment_decorator.rb +50 -0
  28. data/app/models/spree/stock/availability_validator.rb +27 -0
  29. data/app/models/spree/stock/inventory_unit_builder_decorator.rb +30 -0
  30. data/app/models/spree/variant_decorator.rb +13 -0
  31. data/app/overrides/add_admin_product_form_fields.rb +5 -0
  32. data/app/overrides/add_admin_tabs.rb +5 -0
  33. data/app/overrides/add_line_item_description.rb +5 -0
  34. data/app/overrides/spree/admin/orders/_form/inject_product_assemblies.html.erb.deface +3 -0
  35. data/app/overrides/spree/admin/orders/_shipment/stock_contents.html.erb.deface +2 -0
  36. data/app/overrides/spree/checkout/_delivery/remove_unshippable_markup.html.erb.deface +1 -0
  37. data/app/overrides/spree/checkout/_delivery/render_line_item_manifest.html.erb.deface +3 -0
  38. data/app/overrides/spree/products/add_links_to_parts.rb +6 -0
  39. data/app/overrides/spree/products/show/remove_add_to_cart_button_for_non_individual_sale_products.html.erb.deface +4 -0
  40. data/app/overrides/spree/shared/_order_details/part_description.html.erb.deface +16 -0
  41. data/app/serializers/spree/wombat/assembly_shipment_serializer.rb +37 -0
  42. data/app/views/spree/admin/orders/_assemblies.html.erb +62 -0
  43. data/app/views/spree/admin/orders/_stock_contents.html.erb +69 -0
  44. data/app/views/spree/admin/orders/_stock_item.html.erb +46 -0
  45. data/app/views/spree/admin/parts/_parts_table.html.erb +33 -0
  46. data/app/views/spree/admin/parts/available.html.erb +43 -0
  47. data/app/views/spree/admin/parts/index.html.erb +19 -0
  48. data/app/views/spree/admin/parts/update_parts_table.js.erb +2 -0
  49. data/app/views/spree/admin/products/_product_assembly_fields.html.erb +23 -0
  50. data/app/views/spree/admin/shared/_product_assembly_product_tabs.html.erb +3 -0
  51. data/app/views/spree/checkout/_line_item_manifest.html.erb +17 -0
  52. data/app/views/spree/orders/_cart_description.html.erb +16 -0
  53. data/app/views/spree/products/show/_parts.html.erb +38 -0
  54. data/bin/rails +7 -0
  55. data/config/locales/en.yml +15 -0
  56. data/config/locales/fr.yml +12 -0
  57. data/config/locales/ru.yml +12 -0
  58. data/config/locales/sv.yml +12 -0
  59. data/config/routes.rb +19 -0
  60. data/db/migrate/20091028152124_add_many_to_many_relation_to_products.rb +13 -0
  61. data/db/migrate/20091029165620_add_parts_fields_to_products.rb +27 -0
  62. data/db/migrate/20120316141830_namespace_product_assembly_for_spree_one.rb +9 -0
  63. data/db/migrate/20140620223938_add_id_to_spree_assemblies_parts.rb +9 -0
  64. data/db/migrate/20150219192418_add_variant_selection_deferred_to_assemblies_parts.rb +5 -0
  65. data/db/migrate/20150303105615_create_part_line_items.rb +9 -0
  66. data/lib/generators/spree_product_assembly/install/install_generator.rb +24 -0
  67. data/lib/spree_product_assembly/engine.rb +21 -0
  68. data/lib/spree_product_assembly.rb +4 -0
  69. data/lib/tasks/spree2_upgrade.rake +29 -0
  70. data/solidus_product_bundle.gemspec +33 -0
  71. data/spec/features/adding_items_to_cart_spec.rb +203 -0
  72. data/spec/features/admin/orders_spec.rb +29 -0
  73. data/spec/features/admin/parts_spec.rb +183 -0
  74. data/spec/features/checkout_spec.rb +249 -0
  75. data/spec/features/updating_items_in_cart_spec.rb +199 -0
  76. data/spec/models/spree/assemblies_part_spec.rb +18 -0
  77. data/spec/models/spree/assign_part_to_bundle_form_spec.rb +51 -0
  78. data/spec/models/spree/inventory_unit_spec.rb +32 -0
  79. data/spec/models/spree/line_item_spec.rb +88 -0
  80. data/spec/models/spree/order_contents_spec.rb +82 -0
  81. data/spec/models/spree/order_inventory_assembly_spec.rb +321 -0
  82. data/spec/models/spree/order_inventory_spec.rb +34 -0
  83. data/spec/models/spree/product_spec.rb +40 -0
  84. data/spec/models/spree/shipment_spec.rb +113 -0
  85. data/spec/models/spree/stock/availability_validator_spec.rb +71 -0
  86. data/spec/models/spree/stock/coordinator_spec.rb +46 -0
  87. data/spec/models/spree/stock/inventory_unit_builder_spec.rb +36 -0
  88. data/spec/models/spree/variant_spec.rb +28 -0
  89. data/spec/serializers/spree/wombat/assembly_shipment_serializer_spec.rb +36 -0
  90. data/spec/spec_helper.rb +56 -0
  91. data/spec/support/factories/assemblies_part_factory.rb +10 -0
  92. data/spec/support/factories/part_line_item_factory.rb +9 -0
  93. data/spec/support/factories/variant_factory.rb +15 -0
  94. data/spec/support/shared_contexts/order_with_bundle.rb +13 -0
  95. metadata +374 -0
@@ -0,0 +1,249 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Checkout", type: :feature do
4
+ let!(:country) do
5
+ create(:country, name: "United States", states_required: true)
6
+ end
7
+ let!(:state) { create(:state, name: "Ohio", country: country) }
8
+ let!(:shipping_method) { create(:shipping_method) }
9
+ let!(:stock_location) { create(:stock_location) }
10
+ let!(:payment_method) { create(:check_payment_method) }
11
+ let!(:zone) { create(:zone) }
12
+
13
+ let(:product) { create(:product, name: "RoR Mug") }
14
+ let(:variant) { create(:variant) }
15
+
16
+ stub_authorization!
17
+
18
+ before { product.parts.push variant }
19
+
20
+ shared_context "purchases product with part included" do
21
+ before do
22
+ add_product_to_cart
23
+ click_button "Checkout"
24
+
25
+ fill_in "order_email", with: "ryan@spreecommerce.com"
26
+ fill_in_address
27
+
28
+ click_button "Save and Continue"
29
+ expect(current_path).to eql(spree.checkout_state_path("delivery"))
30
+ page.should have_content(variant.product.name)
31
+
32
+ click_button "Save and Continue"
33
+ expect(current_path).to eql(spree.checkout_state_path("payment"))
34
+
35
+ click_button "Save and Continue"
36
+ expect(current_path).to eql(spree.order_path(Spree::Order.last))
37
+ page.should have_content(variant.product.name)
38
+ end
39
+ end
40
+
41
+ context "backend order shipments UI", js: true do
42
+ context "ordering only the product assembly" do
43
+ include_context "purchases product with part included"
44
+
45
+ it "views parts bundled as well" do
46
+ visit spree.admin_orders_path
47
+ click_on Spree::Order.last.number
48
+
49
+ page.should have_content(variant.product.name)
50
+ end
51
+ end
52
+
53
+ context "ordering assembly and the part as individual sale" do
54
+ before do
55
+ visit spree.root_path
56
+ click_link variant.product.name
57
+ click_button "add-to-cart-button"
58
+ end
59
+ include_context "purchases product with part included"
60
+
61
+ it "views parts bundled and not" do
62
+ visit spree.admin_orders_path
63
+ click_on Spree::Order.last.number
64
+
65
+ page.should have_content(variant.product.name)
66
+ end
67
+ end
68
+ end
69
+
70
+ context "when a part allows User to select any variant", js: true do
71
+ it "marks non-deferred parts as out of stock" do
72
+ rock = create(:product_in_stock, name: "Rock",
73
+ can_be_part: true)
74
+ stick = create(:product, name: "Stick",
75
+ can_be_part: true)
76
+
77
+ stick.stock_items.first.update_attribute(:backorderable, false)
78
+
79
+ bundle = create(:product, name: "Bundle")
80
+
81
+ create(:assemblies_part, assembly_id: bundle.id,
82
+ part_id: rock.master.id)
83
+ create(:assemblies_part, assembly_id: bundle.id,
84
+ part_id: stick.master.id)
85
+ bundle.reload
86
+
87
+ visit spree.root_path
88
+ click_link bundle.name
89
+
90
+ within("#products") do
91
+ expect(page).to have_content("Out of Stock")
92
+ end
93
+ end
94
+
95
+ it "marks non-deferred parts as backorderable" do
96
+ rock = create(:product_in_stock, name: "Rock",
97
+ can_be_part: true)
98
+ stick = create(:product, name: "Stick",
99
+ can_be_part: true)
100
+
101
+ stick.stock_items.first.update_attribute(:backorderable, true)
102
+
103
+ bundle = create(:product, name: "Bundle")
104
+
105
+ create(:assemblies_part, assembly_id: bundle.id,
106
+ part_id: rock.master.id)
107
+ create(:assemblies_part, assembly_id: bundle.id,
108
+ part_id: stick.master.id)
109
+
110
+ bundle.reload
111
+
112
+ visit spree.root_path
113
+ click_link bundle.name
114
+
115
+ within("#products") do
116
+ expect(page).to have_content("Backorderable")
117
+ end
118
+ end
119
+
120
+ it "does not allow selection of variants that are out of stock" do
121
+ red_option = create(:option_value, presentation: "Red")
122
+ blue_option = create(:option_value, presentation: "Blue")
123
+ green_option = create(:option_value, presentation: "Green")
124
+
125
+ option_type = create(:option_type,
126
+ presentation: "Color",
127
+ name: "color",
128
+ option_values: [
129
+ red_option,
130
+ blue_option,
131
+ green_option
132
+ ])
133
+
134
+ shirt = create(:product_in_stock,
135
+ name: "Shirt",
136
+ option_types: [option_type],
137
+ can_be_part: true)
138
+
139
+ red = create(:variant_in_stock,
140
+ product: shirt,
141
+ sku: "PART-RED",
142
+ option_values: [red_option])
143
+ blue = create(:variant,
144
+ product: shirt,
145
+ sku: "PART-BLUE",
146
+ option_values: [blue_option])
147
+ green = create(:variant,
148
+ product: shirt,
149
+ sku: "PART-GREEN",
150
+ option_values: [green_option])
151
+
152
+ blue.stock_items.first.update_attribute(:backorderable, true)
153
+ green.stock_items.first.update_attribute(:backorderable, false)
154
+
155
+ bundle = create(:product, name: "Bundle")
156
+
157
+ create(:assemblies_part, assembly_id: bundle.id,
158
+ part_id: shirt.master.id,
159
+ variant_selection_deferred: true)
160
+ bundle.reload
161
+
162
+ visit spree.root_path
163
+ click_link bundle.name
164
+
165
+ first_selectable = bundle.assemblies_parts.first.id
166
+
167
+ within("#options_selected_variants_#{first_selectable}") do
168
+ expect(page).to have_css("option[value='#{red.id}']")
169
+ expect(page).to have_css("option[value='#{blue.id}']")
170
+ expect(page).to(
171
+ have_css("option[value='#{green.id}'][disabled='disabled']")
172
+ )
173
+ end
174
+ end
175
+
176
+ it "shows the part the User selected at all stages of checkout" do
177
+ red_option = create(:option_value, presentation: "Red")
178
+ blue_option = create(:option_value, presentation: "Blue")
179
+
180
+ option_type = create(:option_type,
181
+ presentation: "Color",
182
+ name: "color",
183
+ option_values: [red_option, blue_option])
184
+
185
+ shirt = create(:product_in_stock,
186
+ name: "Shirt",
187
+ option_types: [option_type],
188
+ can_be_part: true)
189
+
190
+ create(:variant_in_stock,
191
+ product: shirt,
192
+ sku: "PART-RED",
193
+ option_values: [red_option])
194
+ create(:variant_in_stock,
195
+ product: shirt,
196
+ sku: "PART-BLUE",
197
+ option_values: [blue_option])
198
+
199
+ bundle = create(:product, name: "Bundle")
200
+
201
+ create(:assemblies_part,
202
+ assembly_id: bundle.id,
203
+ part_id: shirt.master.id,
204
+ variant_selection_deferred: true)
205
+ bundle.reload
206
+
207
+ visit spree.root_path
208
+ click_link bundle.name
209
+
210
+ select "Color: Blue", from: "Variant"
211
+ click_button "add-to-cart-button"
212
+
213
+ click_button "Checkout"
214
+
215
+ fill_in "order_email", with: "ryan@spreecommerce.com"
216
+ fill_in_address
217
+
218
+ click_button "Save and Continue"
219
+ expect(current_path).to eql(spree.checkout_state_path("delivery"))
220
+ expect(page).to have_content(shirt.name)
221
+ expect(page).to have_content("Color: Blue")
222
+
223
+ click_button "Save and Continue"
224
+ expect(current_path).to eql(spree.checkout_state_path("payment"))
225
+
226
+ click_button "Save and Continue"
227
+ expect(current_path).to eql(spree.order_path(Spree::Order.last))
228
+ expect(page).to have_content(shirt.name)
229
+ expect(page).to have_content("Color: Blue")
230
+ end
231
+ end
232
+
233
+ def fill_in_address
234
+ address = "order_bill_address_attributes"
235
+ fill_in "#{address}_firstname", with: "Ryan"
236
+ fill_in "#{address}_lastname", with: "Bigg"
237
+ fill_in "#{address}_address1", with: "143 Swan Street"
238
+ fill_in "#{address}_city", with: "Richmond"
239
+ select "Ohio", from: "#{address}_state_id"
240
+ fill_in "#{address}_zipcode", with: "12345"
241
+ fill_in "#{address}_phone", with: "(555) 555-5555"
242
+ end
243
+
244
+ def add_product_to_cart
245
+ visit spree.root_path
246
+ click_link product.name
247
+ click_button "add-to-cart-button"
248
+ end
249
+ end
@@ -0,0 +1,199 @@
1
+ require "spec_helper"
2
+
3
+ describe "Updating items in the cart", type: :feature do
4
+ context "when updating a bundle's quantity" do
5
+ context "when none of the bundle items are packs or have options" do
6
+ specify "the quantities are multiplied by the bundle's new quantity" do
7
+ bundle = create(:product_in_stock, name: "Bundle", sku: "BUNDLE")
8
+
9
+ keychain = create(:product_in_stock, name: "Keychain",
10
+ sku: "KEYCHAIN",
11
+ can_be_part: true)
12
+ shirt = create(:product_in_stock, name: "Shirt",
13
+ sku: "SHIRT",
14
+ can_be_part: true)
15
+
16
+ add_part_to_bundle(bundle, keychain.master)
17
+ add_part_to_bundle(bundle, shirt.master)
18
+
19
+ visit spree.product_path(bundle)
20
+
21
+ click_button "add-to-cart-button"
22
+
23
+ within("#cart-detail") do
24
+ find("input").set 2
25
+ end
26
+
27
+ click_button "update-button"
28
+
29
+ within("#cart-detail tbody tr:first-child") do
30
+ expect(page).to have_content(bundle.name)
31
+ expect(page).to have_css("input[value='2']")
32
+ expect(page).to have_content("(2) Keychain (KEYCHAIN)")
33
+ expect(page).to have_content("(2) Shirt (SHIRT)")
34
+ end
35
+ end
36
+ end
37
+
38
+ context "when one of the variants is a pack" do
39
+ specify "the pack quantity is multiplied by the bundle's new quantity" do
40
+ bundle = create(:product_in_stock, name: "Bundle", sku: "BUNDLE")
41
+
42
+ keychain = create(:product_in_stock, name: "Keychain",
43
+ sku: "KEYCHAIN",
44
+ can_be_part: true)
45
+
46
+ _shirt, shirts_by_size = create_bundle_product_with_options(
47
+ name: "Shirt",
48
+ sku: "SHIRT",
49
+ option_type: "Size",
50
+ option_values: ["Small"]
51
+ )
52
+
53
+ add_part_to_bundle(bundle, keychain.master, count: 2)
54
+ add_part_to_bundle(bundle, shirts_by_size["small"])
55
+
56
+ visit spree.product_path(bundle)
57
+
58
+ click_button "add-to-cart-button"
59
+
60
+ within("#cart-detail") do
61
+ find("input").set 2
62
+ end
63
+
64
+ click_button "update-button"
65
+
66
+ within("#cart-detail tbody tr:first-child") do
67
+ expect(page).to have_content(bundle.name)
68
+ expect(page).to have_css("input[value='2']")
69
+ expect(page).to have_content("(4) Keychain (KEYCHAIN)")
70
+ expect(page).to have_content("(2) Shirt (Size: Small) (SHIRT-SMALL)")
71
+ end
72
+ end
73
+ end
74
+
75
+ context "when a bundle items has a variant (that is not user-selectable)" do
76
+ specify "the variant quantity is multiplied by the new bundle quantity" do
77
+ bundle = create(:product_in_stock, name: "Bundle", sku: "BUNDLE")
78
+
79
+ keychain = create(:product_in_stock, name: "Keychain",
80
+ sku: "KEYCHAIN",
81
+ can_be_part: true)
82
+
83
+ _shirt, shirts_by_size = create_bundle_product_with_options(
84
+ name: "Shirt",
85
+ sku: "SHIRT",
86
+ option_type: "Size",
87
+ option_values: ["Small"]
88
+ )
89
+
90
+ add_part_to_bundle(bundle, keychain.master)
91
+ add_part_to_bundle(bundle, shirts_by_size["small"])
92
+
93
+ visit spree.product_path(bundle)
94
+
95
+ click_button "add-to-cart-button"
96
+
97
+ within("#cart-detail") do
98
+ find("input").set 2
99
+ end
100
+
101
+ click_button "update-button"
102
+
103
+ within("#cart-detail tbody tr:first-child") do
104
+ expect(page).to have_content(bundle.name)
105
+ expect(page).to have_css("input[value='2']")
106
+ expect(page).to have_content("(2) Keychain (KEYCHAIN)")
107
+ expect(page).to have_content("(2) Shirt (Size: Small) (SHIRT-SMALL)")
108
+ end
109
+ end
110
+ end
111
+
112
+ context "when one of the bundle items has a user-selectable variant" do
113
+ specify "the variant quantity is multiplied by the new bundle quantity" do
114
+ bundle = create(:product_in_stock, name: "Bundle", sku: "BUNDLE")
115
+
116
+ keychain = create(:product_in_stock, name: "Keychain",
117
+ sku: "KEYCHAIN",
118
+ can_be_part: true)
119
+
120
+ shirt, _shirts_by_size = create_bundle_product_with_options(
121
+ name: "Shirt",
122
+ sku: "SHIRT",
123
+ option_type: "Size",
124
+ option_values: ["Small", "Medium"]
125
+ )
126
+
127
+ add_part_to_bundle(bundle, keychain.master, count: 1)
128
+ add_part_to_bundle(
129
+ bundle,
130
+ shirt.master,
131
+ variant_selection_deferred: true
132
+ )
133
+
134
+ visit spree.product_path(bundle)
135
+
136
+ select "Size: Medium", from: "Variant"
137
+
138
+ click_button "add-to-cart-button"
139
+
140
+ within("#cart-detail") do
141
+ find("input").set 2
142
+ end
143
+
144
+ click_button "update-button"
145
+
146
+ within("#cart-detail tbody tr:first-child") do
147
+ expect(page).to have_content(bundle.name)
148
+ expect(page).to have_css("input[value='2']")
149
+ expect(page).to have_content("(2) Keychain (KEYCHAIN)")
150
+ expect(page).to(
151
+ have_content("(2) Shirt (Size: Medium) (SHIRT-MEDIUM)")
152
+ )
153
+ end
154
+ end
155
+ end
156
+ end
157
+
158
+ def create_bundle_product_with_options(args)
159
+ option_type_presentation = args.fetch(:option_type)
160
+ option_value_presentations = args.fetch(:option_values)
161
+ option_values = option_value_presentations.map do |presentation|
162
+ create(:option_value, presentation: presentation)
163
+ end
164
+ option_type = create(:option_type,
165
+ presentation: option_type_presentation,
166
+ name: option_type_presentation.downcase,
167
+ option_values: option_values)
168
+ product_attributes = args.slice(:name, :sku).merge(
169
+ option_types: [option_type],
170
+ can_be_part: true
171
+ )
172
+ product = create(:product, product_attributes)
173
+
174
+ variants = variants_by_option(product, option_values)
175
+
176
+ [product, variants]
177
+ end
178
+
179
+ def variants_by_option(product, option_values)
180
+ option_values.each_with_object({}) do |value, hash|
181
+ hash[value.presentation.downcase] = create(
182
+ :variant_in_stock,
183
+ product: product,
184
+ sku: "#{product.sku}-#{value.presentation.upcase}",
185
+ option_values: [value]
186
+ )
187
+ end
188
+ end
189
+
190
+ def add_part_to_bundle(bundle, variant, options = {})
191
+ attributes = options.reverse_merge(
192
+ assembly_id: bundle.id,
193
+ part_id: variant.id,
194
+ )
195
+ create(:assemblies_part, attributes).tap do |_part|
196
+ bundle.reload
197
+ end
198
+ end
199
+ end
@@ -0,0 +1,18 @@
1
+ require 'spec_helper'
2
+
3
+ module Spree
4
+ describe AssembliesPart do
5
+ let(:product) { create(:product) }
6
+ let(:variant) { create(:variant) }
7
+
8
+ before do
9
+ product.parts.push variant
10
+ end
11
+
12
+ context "get" do
13
+ it "brings part by product and variant id" do
14
+ subject.class.get(product.id, variant.id).part.should == variant
15
+ end
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,51 @@
1
+ require "spec_helper"
2
+
3
+ module Spree
4
+ describe AssignPartToBundleForm do
5
+ describe "#submit" do
6
+ context "when given a quantity < 1" do
7
+ it "is invalid" do
8
+ product = build(:product)
9
+ part_options = { count: -1 }
10
+
11
+ command = AssignPartToBundleForm.new(product, part_options)
12
+
13
+ expect(command).to be_invalid
14
+ end
15
+ end
16
+
17
+ context "when given options for an existing assembly" do
18
+ it "updates attributes on the existing assignment" do
19
+ bundle = create(:product)
20
+ part = create(:product, can_be_part: true)
21
+ assignment = AssembliesPart.create(
22
+ assembly_id: bundle.id,
23
+ count: 1,
24
+ part_id: part.id
25
+ )
26
+
27
+ part_options = { count: 2, id: assignment.id }
28
+
29
+ command = AssignPartToBundleForm.new(bundle, part_options)
30
+ command.submit
31
+ assignment.reload
32
+
33
+ expect(assignment.count).to be(2)
34
+ end
35
+ end
36
+
37
+ context "when given options for an assembly that does not exist" do
38
+ it "creates a new assembly part assignment with the provided options" do
39
+ bundle = create(:product)
40
+ part = create(:product, can_be_part: true)
41
+
42
+ part_options = { count: 2, variant_id: part.id }
43
+
44
+ command = AssignPartToBundleForm.new(bundle, part_options)
45
+
46
+ expect { command.submit }.to change { AssembliesPart.count }.by(1)
47
+ end
48
+ end
49
+ end
50
+ end
51
+ end
@@ -0,0 +1,32 @@
1
+ require 'spec_helper'
2
+
3
+ module Spree
4
+ describe InventoryUnit do
5
+ let!(:order) { create(:order_with_line_items) }
6
+ let(:line_item) { order.line_items.first }
7
+ let(:product) { line_item.product }
8
+
9
+ subject { InventoryUnit.create(line_item: line_item, variant: line_item.variant, order: order) }
10
+
11
+ context 'if the unit is not part of an assembly' do
12
+ it 'it will return the percentage of a line item' do
13
+ expect(subject.percentage_of_line_item).to eql(BigDecimal.new(1))
14
+ end
15
+ end
16
+
17
+ context 'if part of an assembly' do
18
+ let(:parts) { (1..2).map { create(:variant) } }
19
+
20
+ before do
21
+ product.parts << parts
22
+ order.create_proposed_shipments
23
+ order.finalize!
24
+ end
25
+
26
+ it 'it will return the percentage of a line item' do
27
+ subject.line_item = line_item
28
+ expect(subject.percentage_of_line_item).to eql(BigDecimal.new(0.5, 2))
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,88 @@
1
+ require 'spec_helper'
2
+
3
+ module Spree
4
+ describe LineItem do
5
+ let!(:order) { create(:order_with_line_items) }
6
+ let(:line_item) { order.line_items.first }
7
+ let(:product) { line_item.product }
8
+ let(:variant) { line_item.variant }
9
+ let(:inventory) { double('order_inventory') }
10
+
11
+ context "bundle parts stock" do
12
+ let(:parts) { (1..2).map { create(:variant) } }
13
+
14
+ before { product.parts << parts }
15
+
16
+ context "one of them not in stock" do
17
+ before do
18
+ part = product.parts.first
19
+ part.stock_items.update_all backorderable: false
20
+
21
+ expect(part).not_to be_in_stock
22
+ end
23
+
24
+ it "doesn't save line item quantity" do
25
+ expect { order.contents.add(variant, 10) }.to(
26
+ raise_error ActiveRecord::RecordInvalid
27
+ )
28
+ end
29
+ end
30
+
31
+ context "in stock" do
32
+ before do
33
+ parts.each do |part|
34
+ part.stock_items.first.set_count_on_hand(10)
35
+ end
36
+ expect(parts[0]).to be_in_stock
37
+ expect(parts[1]).to be_in_stock
38
+ end
39
+
40
+ it "saves line item quantity" do
41
+ line_item = order.contents.add(variant, 10)
42
+ expect(line_item).to be_valid
43
+ end
44
+ end
45
+ end
46
+
47
+ context "updates bundle product line item" do
48
+ let(:parts) { (1..2).map { create(:variant) } }
49
+
50
+ before do
51
+ product.parts << parts
52
+ order.create_proposed_shipments
53
+ order.finalize!
54
+ end
55
+
56
+ it "verifies inventory units via OrderInventoryAssembly" do
57
+ OrderInventoryAssembly.should_receive(:new).
58
+ with(line_item).
59
+ and_return(inventory)
60
+ inventory.should_receive(:verify).with(line_item.target_shipment)
61
+ line_item.quantity = 2
62
+ line_item.save
63
+ end
64
+ end
65
+
66
+ context "updates regular line item" do
67
+ it "verifies inventory units via OrderInventory" do
68
+ OrderInventory.should_receive(:new).
69
+ with(line_item.order, line_item).
70
+ and_return(inventory)
71
+ inventory.should_receive(:verify).with(line_item.target_shipment)
72
+ line_item.quantity = 2
73
+ line_item.save
74
+ end
75
+ end
76
+
77
+ context "removing line items" do
78
+ it "removes part line items" do
79
+ line_item = create(:line_item)
80
+ create(:part_line_item, line_item: line_item)
81
+
82
+ line_item.destroy
83
+
84
+ expect(Spree::PartLineItem.count).to eq 0
85
+ end
86
+ end
87
+ end
88
+ end