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,82 @@
1
+ require "spec_helper"
2
+
3
+ describe Spree::OrderContents do
4
+ describe "#add_to_line_item" do
5
+ context "given a variant which is an assembly" do
6
+ it "creates a PartLineItem for each part of the assembly" do
7
+ order = create(:order)
8
+ assembly = create(:product)
9
+ pieces = create_list(:product, 2)
10
+ pieces.each do |piece|
11
+ create(:assemblies_part, assembly: assembly, part: piece.master)
12
+ end
13
+
14
+ contents = described_class.new(order)
15
+
16
+ line_item = contents.add_to_line_item_with_parts(assembly.master, 1)
17
+
18
+ part_line_items = line_item.part_line_items
19
+
20
+ expect(part_line_items[0].line_item_id).to eq line_item.id
21
+ expect(part_line_items[0].variant_id).to eq pieces[0].master.id
22
+ expect(part_line_items[0].quantity).to eq 1
23
+ expect(part_line_items[1].line_item_id).to eq line_item.id
24
+ expect(part_line_items[1].variant_id).to eq pieces[1].master.id
25
+ expect(part_line_items[1].quantity).to eq 1
26
+ end
27
+ end
28
+
29
+ context "given parts of an assembly" do
30
+ it "creates a PartLineItem for each part" do
31
+ order = create(:order)
32
+ assembly = create(:product)
33
+
34
+ red_option = create(:option_value, presentation: "Red")
35
+ blue_option = create(:option_value, presentation: "Blue")
36
+
37
+ option_type = create(:option_type,
38
+ presentation: "Color",
39
+ name: "color",
40
+ option_values: [
41
+ red_option,
42
+ blue_option
43
+ ])
44
+
45
+ keychain = create(:product_in_stock)
46
+
47
+ shirt = create(:product_in_stock,
48
+ option_types: [option_type],
49
+ can_be_part: true)
50
+
51
+ create(:variant_in_stock, product: shirt, option_values: [red_option])
52
+ create(:variant_in_stock, product: shirt, option_values: [blue_option])
53
+
54
+ create(:assemblies_part,
55
+ assembly_id: assembly.id,
56
+ part_id: keychain.master.id)
57
+ create(:assemblies_part,
58
+ assembly_id: assembly.id,
59
+ part_id: shirt.master.id,
60
+ variant_selection_deferred: true)
61
+ assembly.reload
62
+
63
+ contents = Spree::OrderContents.new(order)
64
+
65
+ line_item = contents.add_to_line_item_with_parts(assembly.master, 1, {
66
+ "selected_variants" => {
67
+ "#{assembly.assemblies_parts.last.id}" => "#{shirt.variants.last.id}"
68
+ }
69
+ })
70
+
71
+ part_line_items = line_item.part_line_items
72
+
73
+ expect(part_line_items[0].line_item_id).to eq line_item.id
74
+ expect(part_line_items[0].variant_id).to eq keychain.master.id
75
+ expect(part_line_items[0].quantity).to eq 1
76
+ expect(part_line_items[1].line_item_id).to eq line_item.id
77
+ expect(part_line_items[1].variant_id).to eq shirt.variants.last.id
78
+ expect(part_line_items[1].quantity).to eq 1
79
+ end
80
+ end
81
+ end
82
+ end
@@ -0,0 +1,321 @@
1
+ require 'spec_helper'
2
+
3
+ module Spree
4
+ describe OrderInventoryAssembly do
5
+ describe "#verify" do
6
+ context "when line item involves variants that are not user-selectable" do
7
+ context "when a shipment is provided" do
8
+ context "when the bundle is created" do
9
+ it "produces inventory units for each item in the bundle" do
10
+ shipment, line_item, variants = create_line_item_for_bundle(
11
+ parts: [{ count: 1 }, { count: 1 }, { count: 3 }]
12
+ )
13
+ inventory = OrderInventoryAssembly.new(line_item)
14
+ inventory.verify(shipment)
15
+
16
+ expect(inventory.inventory_units.count).to eq 5
17
+
18
+ expect(shipment.inventory_units_for(variants[0]).count).to eq 1
19
+ expect(shipment.inventory_units_for(variants[1]).count).to eq 1
20
+ expect(shipment.inventory_units_for(variants[2]).count).to eq 3
21
+ end
22
+ end
23
+
24
+ context "when the bundle quantity is increased" do
25
+ it "adds [difference in quantity] sets of inventory units" do
26
+ shipment, line_item, variants = create_line_item_for_bundle(
27
+ parts: [{ count: 1 }, { count: 1 }, { count: 3 }]
28
+ )
29
+ inventory = OrderInventoryAssembly.new(line_item)
30
+ inventory.verify(shipment)
31
+
32
+ expect(inventory.inventory_units.count).to eq 5
33
+
34
+ expect(shipment.inventory_units_for(variants[0]).count).to eq 1
35
+ expect(shipment.inventory_units_for(variants[1]).count).to eq 1
36
+ expect(shipment.inventory_units_for(variants[2]).count).to eq 3
37
+
38
+ line_item.update_column(:quantity, 2)
39
+ inventory.verify(shipment)
40
+
41
+ expect(inventory.inventory_units.count).to eq 10
42
+
43
+ expect(shipment.inventory_units_for(variants[0]).count).to eq 2
44
+ expect(shipment.inventory_units_for(variants[1]).count).to eq 2
45
+ expect(shipment.inventory_units_for(variants[2]).count).to eq 6
46
+ end
47
+ end
48
+
49
+ context "when the bundle quantity is decreased" do
50
+ it "removes [difference in quantity] sets of inventory units" do
51
+ shipment, line_item, variants = create_line_item_for_bundle(
52
+ line_item_quantity: 2,
53
+ parts: [{ count: 1 }, { count: 1 }, { count: 3 }]
54
+ )
55
+ inventory = OrderInventoryAssembly.new(line_item)
56
+ inventory.verify(shipment)
57
+
58
+ expect(inventory.inventory_units.count).to eq 10
59
+
60
+ expect(shipment.inventory_units_for(variants[0]).count).to eq 2
61
+ expect(shipment.inventory_units_for(variants[1]).count).to eq 2
62
+ expect(shipment.inventory_units_for(variants[2]).count).to eq 6
63
+
64
+ line_item.update_column(:quantity, 1)
65
+ inventory.verify(shipment)
66
+
67
+ expect(inventory.inventory_units.count).to eq 5
68
+
69
+ expect(shipment.inventory_units_for(variants[0]).count).to eq 1
70
+ expect(shipment.inventory_units_for(variants[1]).count).to eq 1
71
+ expect(shipment.inventory_units_for(variants[2]).count).to eq 3
72
+ end
73
+ end
74
+ end
75
+
76
+ context "when a shipment is not provided" do
77
+ context "when the bundle is created" do
78
+ it "produces inventory units for each item in the bundle" do
79
+ shipment, line_item, variants = create_line_item_for_bundle(
80
+ parts: [{ count: 1 }, { count: 1 }, { count: 3 }]
81
+ )
82
+ inventory = OrderInventoryAssembly.new(line_item)
83
+ inventory.verify
84
+
85
+ expect(inventory.inventory_units.count).to eq 5
86
+
87
+ expect(shipment.inventory_units_for(variants[0]).count).to eq 1
88
+ expect(shipment.inventory_units_for(variants[1]).count).to eq 1
89
+ expect(shipment.inventory_units_for(variants[2]).count).to eq 3
90
+ end
91
+ end
92
+
93
+ context "when the bundle quantity is increased" do
94
+ it "adds [difference in quantity] sets of inventory units" do
95
+ shipment, line_item, variants = create_line_item_for_bundle(
96
+ parts: [{ count: 1 }, { count: 1 }, { count: 3 }]
97
+ )
98
+ inventory = OrderInventoryAssembly.new(line_item)
99
+ inventory.verify
100
+
101
+ expect(inventory.inventory_units.count).to eq 5
102
+
103
+ expect(shipment.inventory_units_for(variants[0]).count).to eq 1
104
+ expect(shipment.inventory_units_for(variants[1]).count).to eq 1
105
+ expect(shipment.inventory_units_for(variants[2]).count).to eq 3
106
+
107
+ line_item.update_column(:quantity, 2)
108
+ inventory.verify
109
+
110
+ expect(inventory.inventory_units.count).to eq 10
111
+
112
+ expect(shipment.inventory_units_for(variants[0]).count).to eq 2
113
+ expect(shipment.inventory_units_for(variants[1]).count).to eq 2
114
+ expect(shipment.inventory_units_for(variants[2]).count).to eq 6
115
+ end
116
+ end
117
+
118
+ context "when the bundle quantity is decreased" do
119
+ it "removes [difference in quantity] sets of inventory units" do
120
+ shipment, line_item, variants = create_line_item_for_bundle(
121
+ line_item_quantity: 2,
122
+ parts: [{ count: 1 }, { count: 1 }, { count: 3 }]
123
+ )
124
+ inventory = OrderInventoryAssembly.new(line_item)
125
+ inventory.verify
126
+
127
+ expect(inventory.inventory_units.count).to eq 10
128
+
129
+ expect(shipment.inventory_units_for(variants[0]).count).to eq 2
130
+ expect(shipment.inventory_units_for(variants[1]).count).to eq 2
131
+ expect(shipment.inventory_units_for(variants[2]).count).to eq 6
132
+
133
+ line_item.update_column(:quantity, 1)
134
+ inventory.verify
135
+
136
+ expect(inventory.inventory_units.count).to eq 5
137
+
138
+ expect(shipment.inventory_units_for(variants[0]).count).to eq 1
139
+ expect(shipment.inventory_units_for(variants[1]).count).to eq 1
140
+ expect(shipment.inventory_units_for(variants[2]).count).to eq 3
141
+ end
142
+
143
+ context "when the bundle has shipped and unshipped shipments" do
144
+ it "removes the items from only the unshipped shipments" do
145
+ unshipped_shipment,
146
+ line_item,
147
+ variants = create_line_item_for_bundle(
148
+ line_item_quantity: 2,
149
+ parts: [{ count: 1 }, { count: 1 }, { count: 3 }]
150
+ )
151
+ shipped_shipment = create(:shipment, state: 'shipped')
152
+ InventoryUnit.all[0..2].each do |unit|
153
+ unit.update_attribute(:shipment_id, shipped_shipment.id)
154
+ end
155
+
156
+ inventory = OrderInventoryAssembly.new(line_item)
157
+
158
+ line_item.update_column(:quantity, 1)
159
+ inventory.verify
160
+
161
+ expect(inventory.inventory_units.count).to eq 6
162
+
163
+ unshipped_units = unshipped_shipment.inventory_units
164
+ expect(unshipped_units.count).to eq 3
165
+ unshipped_units.each do |unit|
166
+ expect(unit.variant).to eq variants[2]
167
+ end
168
+
169
+ shipped_units = shipped_shipment.inventory_units
170
+ expect(shipped_units.count).to eq 3
171
+ shipped_units[0..1].each do |unit|
172
+ expect(unit.variant).to eq variants[0]
173
+ end
174
+ expect(shipped_units[2].variant).to eq variants[1]
175
+ end
176
+ end
177
+ end
178
+ end
179
+ end
180
+
181
+ context "when line item involves user-selectable variants" do
182
+ context "when a shipment is provided" do
183
+ context "when the bundle is created" do
184
+ it "produces inventory units for each item in the bundle" do
185
+ shipment, line_item, variants = create_line_item_for_bundle(
186
+ parts: [
187
+ { count: 1 },
188
+ { count: 1 },
189
+ { count: 3, variant_selection_deferred: true }
190
+ ]
191
+ )
192
+
193
+ inventory = OrderInventoryAssembly.new(line_item)
194
+ inventory.verify(shipment)
195
+
196
+ expect(shipment.inventory_units_for(variants[0]).count).to eq 1
197
+ expect(shipment.inventory_units_for(variants[1]).count).to eq 1
198
+ expect(shipment.inventory_units_for(variants[2]).count).to eq 3
199
+ end
200
+ end
201
+
202
+ context "when the bundle quantity is increased" do
203
+ it "adds [difference in quantity] sets of inventory units" do
204
+ shipment, line_item, variants = create_line_item_for_bundle(
205
+ parts: [
206
+ { count: 1 },
207
+ { count: 1 },
208
+ { count: 3, variant_selection_deferred: true }
209
+ ]
210
+ )
211
+
212
+ inventory = OrderInventoryAssembly.new(line_item)
213
+ inventory.verify(shipment)
214
+
215
+ expect(shipment.inventory_units_for(variants[0]).count).to eq 1
216
+ expect(shipment.inventory_units_for(variants[1]).count).to eq 1
217
+ expect(shipment.inventory_units_for(variants[2]).count).to eq 3
218
+
219
+ line_item.update_column(:quantity, 2)
220
+ inventory.verify(shipment)
221
+
222
+ expect(shipment.inventory_units_for(variants[0]).count).to eq 2
223
+ expect(shipment.inventory_units_for(variants[1]).count).to eq 2
224
+ expect(shipment.inventory_units_for(variants[2]).count).to eq 6
225
+ end
226
+ end
227
+
228
+ context "when the bundle quantity is decreased" do
229
+ it "removes [difference in quantity] sets of inventory units" do
230
+ shipment, line_item, variants = create_line_item_for_bundle(
231
+ line_item_quantity: 2,
232
+ parts: [
233
+ { count: 1 },
234
+ { count: 1 },
235
+ { count: 3, variant_selection_deferred: true }
236
+ ]
237
+ )
238
+
239
+ inventory = OrderInventoryAssembly.new(line_item)
240
+ inventory.verify(shipment)
241
+
242
+ expect(shipment.inventory_units_for(variants[0]).count).to eq 2
243
+ expect(shipment.inventory_units_for(variants[1]).count).to eq 2
244
+ expect(shipment.inventory_units_for(variants[2]).count).to eq 6
245
+
246
+ line_item.update_column(:quantity, 1)
247
+ inventory.verify(shipment)
248
+
249
+ expect(shipment.inventory_units_for(variants[0]).count).to eq 1
250
+ expect(shipment.inventory_units_for(variants[1]).count).to eq 1
251
+ expect(shipment.inventory_units_for(variants[2]).count).to eq 3
252
+ end
253
+ end
254
+ end
255
+ end
256
+ end
257
+
258
+ def create_line_item_for_bundle(args)
259
+ parts = args.fetch(:parts)
260
+ line_item_quantity = args.fetch(:line_item_quantity, 1)
261
+ order = create(:order, completed_at: Time.now)
262
+ shipment = create(:shipment, order: order)
263
+ bundle = create(:product, name: "Bundle")
264
+
265
+ red_option = create(:option_value, presentation: "Red")
266
+ blue_option = create(:option_value, presentation: "Blue")
267
+
268
+ option_type = create(:option_type, presentation: "Color",
269
+ name: "color",
270
+ option_values: [
271
+ red_option,
272
+ blue_option
273
+ ])
274
+
275
+ variants = []
276
+ selected_variants = {}
277
+ parts.each do |part|
278
+ product_properties = { can_be_part: true }
279
+ if part[:variant_selection_deferred]
280
+ product_properties[:option_types] = [option_type]
281
+ end
282
+
283
+ product = create(:product_in_stock, product_properties)
284
+
285
+ assemblies_part_attributes = { assembly: bundle }.merge(part)
286
+
287
+ if part[:variant_selection_deferred]
288
+ create(:variant_in_stock, product: product,
289
+ option_values: [red_option])
290
+ variants << create(:variant_in_stock, product: product,
291
+ option_values: [blue_option])
292
+ else
293
+ variants << product.master
294
+ end
295
+
296
+ assemblies_part_attributes[:part] = product.master
297
+ create(:assemblies_part, assemblies_part_attributes)
298
+
299
+ if part[:variant_selection_deferred]
300
+ selected_variants = {
301
+ "selected_variants" => {
302
+ "#{bundle.assemblies_parts.last.id}" => "#{variants.last.id}"
303
+ }
304
+ }
305
+ end
306
+ end
307
+
308
+ bundle.reload
309
+
310
+ contents = Spree::OrderContents.new(order)
311
+ line_item = contents.add_to_line_item_with_parts(
312
+ bundle.master,
313
+ line_item_quantity,
314
+ selected_variants
315
+ )
316
+ line_item.reload
317
+
318
+ [shipment, line_item, variants]
319
+ end
320
+ end
321
+ end
@@ -0,0 +1,34 @@
1
+ require 'spec_helper'
2
+
3
+ module Spree
4
+ describe OrderInventory do
5
+ let(:order) { Order.create }
6
+
7
+ subject { OrderInventory.new(order, order.line_items.first) }
8
+
9
+ context "same variant within bundle and as regular product" do
10
+ let(:contents) { OrderContents.new(order) }
11
+ let(:guitar) { create(:variant) }
12
+ let(:bass) { create(:variant) }
13
+
14
+ let(:bundle) { create(:product) }
15
+
16
+ before { bundle.parts.push [guitar, bass] }
17
+
18
+ let!(:bundle_item) { contents.add(bundle.master, 5) }
19
+ let!(:guitar_item) { contents.add(guitar, 3) }
20
+
21
+ let!(:shipment) { order.create_proposed_shipments.first }
22
+
23
+ context "completed order" do
24
+ before { order.touch :completed_at }
25
+
26
+ it "removes only units associated with provided line item" do
27
+ expect {
28
+ subject.send(:remove_from_shipment, shipment, 5)
29
+ }.not_to change { bundle_item.inventory_units.count }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,40 @@
1
+ require 'spec_helper'
2
+
3
+ describe Spree::Product do
4
+ before(:each) do
5
+ @product = FactoryGirl.create(:product, :name => "Foo Bar")
6
+ @master_variant = Spree::Variant.where(is_master: true).find_by_product_id(@product.id)
7
+ end
8
+
9
+ describe "Spree::Product Assembly" do
10
+ before(:each) do
11
+ @product = create(:product)
12
+ @part1 = create(:product, :can_be_part => true)
13
+ @part2 = create(:product, :can_be_part => true)
14
+
15
+ create(:assemblies_part,
16
+ assembly: @product,
17
+ part: @part1.master,
18
+ count: 1
19
+ )
20
+ create(:assemblies_part,
21
+ assembly: @product,
22
+ part: @part2.master,
23
+ count: 4
24
+ )
25
+ @product.reload
26
+ end
27
+
28
+ it "is an assembly" do
29
+ @product.should be_assembly
30
+ end
31
+
32
+
33
+ it "cannot be part" do
34
+ @product.should be_assembly
35
+ @product.can_be_part = true
36
+ @product.valid?
37
+ @product.errors[:can_be_part].should == ["assembly can't be part"]
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,113 @@
1
+ require 'spec_helper'
2
+
3
+ module Spree
4
+ describe Shipment do
5
+ context "order has one product assembly" do
6
+ let(:order) { Order.create }
7
+ let(:bundle) { create(:variant) }
8
+ let!(:parts) { (1..2).map { create(:variant) } }
9
+ let!(:bundle_parts) { bundle.product.parts << parts }
10
+
11
+ let!(:line_item) { order.contents.add(bundle, 1) }
12
+ let!(:shipment) { order.create_proposed_shipments.first }
13
+
14
+ before { order.update_column :state, 'complete' }
15
+
16
+
17
+ xit "shipment item cost equals line item amount" do
18
+ expect(shipment.item_cost).to eq line_item.amount
19
+ end
20
+ end
21
+
22
+ context "manifests" do
23
+ include_context "product is ordered as individual and within a bundle"
24
+
25
+ let(:shipments) { order.create_proposed_shipments }
26
+
27
+ context "default" do
28
+ let(:expected_variants) { order.variants - [bundle_variant] + bundle.parts }
29
+
30
+ it "separates variant purchased individually from the bundle one" do
31
+ expect(shipments.count).to eql 1
32
+ shipments.first.manifest.map(&:variant).sort.should == expected_variants.sort
33
+ end
34
+ end
35
+
36
+ context "line items manifest" do
37
+ let(:expected_variants) { order.variants }
38
+
39
+ it "groups units by line_item only" do
40
+ expect(shipments.count).to eql 1
41
+ shipments.first.line_item_manifest.map(&:variant).sort.should == expected_variants.sort
42
+ end
43
+ end
44
+
45
+ context "units are not associated with a line item" do
46
+ let(:order) { create(:shipped_order) }
47
+ let(:shipment) { order.shipments.first }
48
+
49
+ it "searches for line item if inventory unit doesn't have one" do
50
+ shipment.manifest.last.line_item.should_not be_blank
51
+ end
52
+ end
53
+ end
54
+
55
+ context "set up new inventory units" do
56
+ let(:line_item) { create(:line_item) }
57
+ let(:variant) { line_item.variant }
58
+ let(:order) { line_item.order }
59
+ let(:shipment) { create(:shipment) }
60
+
61
+ it "assigns variant, order and line_item" do
62
+ unit = shipment.set_up_inventory('on_hand', variant, order, line_item)
63
+
64
+ expect(unit.line_item).to eq line_item
65
+ expect(unit.variant).to eq variant
66
+ expect(unit.order).to eq order
67
+ expect(unit.state).to eq 'on_hand'
68
+ end
69
+ end
70
+
71
+ context "unit states for variant sold as part of an assembly and separately" do
72
+ let(:assembly_line_item) { create(:line_item) }
73
+ let(:shirt) { create(:variant) }
74
+
75
+ let(:assembly_shirts) do
76
+ 5.times.map {
77
+ create(:inventory_unit,
78
+ variant: shirt,
79
+ line_item: assembly_line_item,
80
+ state: :on_hand)
81
+ }
82
+ end
83
+
84
+ let(:standalone_line_item) { create(:line_item, variant: shirt) }
85
+
86
+ let(:standalone_shirts) do
87
+ 2.times.map {
88
+ create(:inventory_unit,
89
+ variant: shirt,
90
+ line_item: standalone_line_item,
91
+ state: :on_hand)
92
+ }
93
+ end
94
+
95
+ let(:shipment) { create(:shipment) }
96
+
97
+ before do
98
+ shipment.inventory_units << assembly_shirts
99
+ shipment.inventory_units << standalone_shirts
100
+ end
101
+
102
+ it "set states numbers properly for all items" do
103
+ shipment.manifest.each do |item|
104
+ if item.line_item.id == standalone_line_item.id
105
+ expect(item.states["on_hand"]).to eq standalone_shirts.count
106
+ else
107
+ expect(item.states["on_hand"]).to eq assembly_shirts.count
108
+ end
109
+ end
110
+ end
111
+ end
112
+ end
113
+ end
@@ -0,0 +1,71 @@
1
+ require 'spec_helper'
2
+
3
+ module Spree
4
+ module Stock
5
+ describe AvailabilityValidator, :type => :model do
6
+ context "line item has no parts" do
7
+ let!(:line_item) { create(:line_item, quantity: 5) }
8
+ let!(:product) { line_item.product }
9
+
10
+ subject { described_class.new }
11
+
12
+ it 'should be valid when supply is sufficient' do
13
+ Stock::Quantifier.any_instance.stub(can_supply?: true)
14
+ expect(line_item).not_to receive(:errors)
15
+ subject.validate(line_item)
16
+ end
17
+
18
+ it 'should be invalid when supply is insufficent' do
19
+ Stock::Quantifier.any_instance.stub(can_supply?: false)
20
+ expect(line_item.errors).to receive(:[]).exactly(1).times.with(:quantity).and_return([])
21
+ subject.validate(line_item)
22
+ end
23
+
24
+ it 'should consider existing inventory_units sufficient' do
25
+ Stock::Quantifier.stub(can_supply?: false)
26
+ line_item.inventory_units.stub(where: [double] * 5)
27
+ expect(line_item).not_to receive(:errors)
28
+ subject.validate(line_item)
29
+ end
30
+ end
31
+
32
+ context "line item has parts" do
33
+ let!(:order) { create(:order_with_line_items) }
34
+ let(:line_item) { order.line_items.first }
35
+ let(:product) { line_item.product }
36
+ let(:variant) { line_item.variant }
37
+ let(:parts) { (1..2).map { create(:variant) } }
38
+ before { product.parts << parts }
39
+
40
+ subject { described_class.new }
41
+
42
+ it 'should be valid when supply of all parts is sufficient' do
43
+ Stock::Quantifier.any_instance.stub(can_supply?: true)
44
+ expect(line_item).not_to receive(:errors)
45
+ subject.validate(line_item)
46
+ end
47
+
48
+ it 'should be invalid when supplies of all parts are insufficent' do
49
+ Stock::Quantifier.any_instance.stub(can_supply?: false)
50
+ expect(line_item.errors).to receive(:[]).exactly(line_item.parts.size).times.with(:quantity).and_return([])
51
+ subject.validate(line_item)
52
+ end
53
+
54
+ it 'should be invalid when supply of 1 part is insufficient' do
55
+ Stock::Quantifier.any_instance.stub(can_supply?: false)
56
+ inventory_units = [double(variant: line_item.parts.first)] * 5
57
+ line_item.parts.first.stub(inventory_units: inventory_units)
58
+ expect(line_item.inventory_units).to receive(:where).and_return(inventory_units, [])
59
+ expect(line_item.errors).to receive(:[]).exactly(1).times.with(:quantity).and_return([])
60
+ subject.validate(line_item)
61
+ end
62
+
63
+ it 'should be valid when supply of each part is sufficient' do
64
+ line_item.parts.each { |part| part.stub(inventory_units: [double(variant: part)] * 5) }
65
+ expect(line_item).not_to receive(:errors)
66
+ subject.validate(line_item)
67
+ end
68
+ end
69
+ end
70
+ end
71
+ end