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