solidus_product_bundle 1.0.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.
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