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,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