solidus_product_bundle 1.0.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +5 -0
- data/.rspec +3 -0
- data/.travis.yml +11 -0
- data/Gemfile +9 -0
- data/LICENSE.md +13 -0
- data/README.markdown +95 -0
- data/README.md +2 -0
- data/Rakefile +15 -0
- data/app/assets/images/spinner.gif +0 -0
- data/app/assets/javascripts/spree/backend/spree_product_assembly/index.js.coffee +82 -0
- data/app/assets/javascripts/spree/backend/spree_product_assembly/translations.js.erb +3 -0
- data/app/assets/javascripts/spree/frontend/spree_product_assembly.js +1 -0
- data/app/assets/stylesheets/spree/backend/spree_product_assembly.css +3 -0
- data/app/assets/stylesheets/spree/frontend/spree_product_assembly.css +3 -0
- data/app/controllers/spree/admin/parts_controller.rb +63 -0
- data/app/controllers/spree/checkout_controller_decorator.rb +9 -0
- data/app/helpers/spree/admin/orders_helper_decorator.rb +9 -0
- data/app/models/spree/assemblies_part.rb +33 -0
- data/app/models/spree/assign_part_to_bundle_form.rb +64 -0
- data/app/models/spree/inventory_unit_decorator.rb +13 -0
- data/app/models/spree/line_item_decorator.rb +65 -0
- data/app/models/spree/order_contents_decorator.rb +35 -0
- data/app/models/spree/order_inventory_assembly.rb +54 -0
- data/app/models/spree/part_line_item.rb +6 -0
- data/app/models/spree/product_decorator.rb +37 -0
- data/app/models/spree/shipment_decorator.rb +50 -0
- data/app/models/spree/stock/availability_validator.rb +27 -0
- data/app/models/spree/stock/inventory_unit_builder_decorator.rb +30 -0
- data/app/models/spree/variant_decorator.rb +13 -0
- data/app/overrides/add_admin_product_form_fields.rb +5 -0
- data/app/overrides/add_admin_tabs.rb +5 -0
- data/app/overrides/add_line_item_description.rb +5 -0
- data/app/overrides/spree/admin/orders/_form/inject_product_assemblies.html.erb.deface +3 -0
- data/app/overrides/spree/admin/orders/_shipment/stock_contents.html.erb.deface +2 -0
- data/app/overrides/spree/checkout/_delivery/remove_unshippable_markup.html.erb.deface +1 -0
- data/app/overrides/spree/checkout/_delivery/render_line_item_manifest.html.erb.deface +3 -0
- data/app/overrides/spree/products/add_links_to_parts.rb +6 -0
- data/app/overrides/spree/products/show/remove_add_to_cart_button_for_non_individual_sale_products.html.erb.deface +4 -0
- data/app/overrides/spree/shared/_order_details/part_description.html.erb.deface +16 -0
- data/app/serializers/spree/wombat/assembly_shipment_serializer.rb +37 -0
- data/app/views/spree/admin/orders/_assemblies.html.erb +62 -0
- data/app/views/spree/admin/orders/_stock_contents.html.erb +69 -0
- data/app/views/spree/admin/orders/_stock_item.html.erb +46 -0
- data/app/views/spree/admin/parts/_parts_table.html.erb +33 -0
- data/app/views/spree/admin/parts/available.html.erb +43 -0
- data/app/views/spree/admin/parts/index.html.erb +19 -0
- data/app/views/spree/admin/parts/update_parts_table.js.erb +2 -0
- data/app/views/spree/admin/products/_product_assembly_fields.html.erb +23 -0
- data/app/views/spree/admin/shared/_product_assembly_product_tabs.html.erb +3 -0
- data/app/views/spree/checkout/_line_item_manifest.html.erb +17 -0
- data/app/views/spree/orders/_cart_description.html.erb +16 -0
- data/app/views/spree/products/show/_parts.html.erb +38 -0
- data/bin/rails +7 -0
- data/config/locales/en.yml +15 -0
- data/config/locales/fr.yml +12 -0
- data/config/locales/ru.yml +12 -0
- data/config/locales/sv.yml +12 -0
- data/config/routes.rb +19 -0
- data/db/migrate/20091028152124_add_many_to_many_relation_to_products.rb +13 -0
- data/db/migrate/20091029165620_add_parts_fields_to_products.rb +27 -0
- data/db/migrate/20120316141830_namespace_product_assembly_for_spree_one.rb +9 -0
- data/db/migrate/20140620223938_add_id_to_spree_assemblies_parts.rb +9 -0
- data/db/migrate/20150219192418_add_variant_selection_deferred_to_assemblies_parts.rb +5 -0
- data/db/migrate/20150303105615_create_part_line_items.rb +9 -0
- data/lib/generators/spree_product_assembly/install/install_generator.rb +24 -0
- data/lib/spree_product_assembly/engine.rb +21 -0
- data/lib/spree_product_assembly.rb +4 -0
- data/lib/tasks/spree2_upgrade.rake +29 -0
- data/solidus_product_bundle.gemspec +33 -0
- data/spec/features/adding_items_to_cart_spec.rb +203 -0
- data/spec/features/admin/orders_spec.rb +29 -0
- data/spec/features/admin/parts_spec.rb +183 -0
- data/spec/features/checkout_spec.rb +249 -0
- data/spec/features/updating_items_in_cart_spec.rb +199 -0
- data/spec/models/spree/assemblies_part_spec.rb +18 -0
- data/spec/models/spree/assign_part_to_bundle_form_spec.rb +51 -0
- data/spec/models/spree/inventory_unit_spec.rb +32 -0
- data/spec/models/spree/line_item_spec.rb +88 -0
- data/spec/models/spree/order_contents_spec.rb +82 -0
- data/spec/models/spree/order_inventory_assembly_spec.rb +321 -0
- data/spec/models/spree/order_inventory_spec.rb +34 -0
- data/spec/models/spree/product_spec.rb +40 -0
- data/spec/models/spree/shipment_spec.rb +113 -0
- data/spec/models/spree/stock/availability_validator_spec.rb +71 -0
- data/spec/models/spree/stock/coordinator_spec.rb +46 -0
- data/spec/models/spree/stock/inventory_unit_builder_spec.rb +36 -0
- data/spec/models/spree/variant_spec.rb +28 -0
- data/spec/serializers/spree/wombat/assembly_shipment_serializer_spec.rb +36 -0
- data/spec/spec_helper.rb +56 -0
- data/spec/support/factories/assemblies_part_factory.rb +10 -0
- data/spec/support/factories/part_line_item_factory.rb +9 -0
- data/spec/support/factories/variant_factory.rb +15 -0
- data/spec/support/shared_contexts/order_with_bundle.rb +13 -0
- 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
|