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,12 @@
1
+ ---
2
+ fr:
3
+ spree:
4
+ available_parts: Parties disponibles
5
+ can_be_part: Peut faire partie d'un package
6
+ individual_sale: Vente individuelle
7
+ no_variants: Pas de variants
8
+ parts: Parties
9
+ assembly_cannot_be_part: ensemble ne peut pas être partie
10
+ product_bundles: Ensembles du produit
11
+ parts_included: Les pièces comprises
12
+ part_of_bundle: 'Une partie du %{sku}'
@@ -0,0 +1,12 @@
1
+ ---
2
+ ru:
3
+ spree:
4
+ available_parts: Доступные составные части
5
+ can_be_part: Может входить в состав других продуктов
6
+ individual_sale: Может продаваться отдельно
7
+ no_variants: Нет вариантов
8
+ parts: Составные части
9
+ assembly_cannot_be_part: сборка не может быть частью
10
+ product_bundles: Продукт cвязки
11
+ parts_included: 'Детали, входящие'
12
+ part_of_bundle: 'Часть пучка %{sku}'
@@ -0,0 +1,12 @@
1
+ ---
2
+ sv:
3
+ spree:
4
+ available_parts: Tillgängliga delar
5
+ can_be_part: Kan vara del
6
+ individual_sale: Individuell försäljning
7
+ no_variants: Inga varianter
8
+ parts: Delar
9
+ assembly_cannot_be_part: enheten kan inte vara en del
10
+ product_bundles: Produktpaket
11
+ parts_included: Delar som ingår
12
+ part_of_bundle: 'Del av %{sku}'
data/config/routes.rb ADDED
@@ -0,0 +1,19 @@
1
+ Spree::Core::Engine.add_routes do
2
+
3
+ namespace :admin do
4
+ resources :products do
5
+ resources :parts do
6
+ member do
7
+ post :select
8
+ post :remove
9
+ post :set_count
10
+ end
11
+ collection do
12
+ post :available
13
+ get :selected
14
+ end
15
+ end
16
+ end
17
+ end
18
+
19
+ end
@@ -0,0 +1,13 @@
1
+ class AddManyToManyRelationToProducts < ActiveRecord::Migration
2
+ def self.up
3
+ create_table :assemblies_parts, :id => false do |t|
4
+ t.integer "assembly_id", :null => false
5
+ t.integer "part_id", :null => false
6
+ t.integer "count", :null => false, :default => 1
7
+ end
8
+ end
9
+
10
+ def self.down
11
+ drop_table :assemblies_parts
12
+ end
13
+ end
@@ -0,0 +1,27 @@
1
+ class AddPartsFieldsToProducts < ActiveRecord::Migration
2
+ def self.up
3
+ table = if table_exists?(:products)
4
+ 'products'
5
+ elsif table_exists?(:spree_products)
6
+ 'spree_products'
7
+ end
8
+
9
+ change_table(table) do |t|
10
+ t.column :can_be_part, :boolean, :default => false, :null => false
11
+ t.column :individual_sale, :boolean, :default => true, :null => false
12
+ end
13
+ end
14
+
15
+ def self.down
16
+ table = if table_exists?(:products)
17
+ 'products'
18
+ elsif table_exists?(:spree_products)
19
+ 'spree_products'
20
+ end
21
+
22
+ change_table(table) do |t|
23
+ t.remove :can_be_part
24
+ t.remove :individual_sale
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,9 @@
1
+ class NamespaceProductAssemblyForSpreeOne < ActiveRecord::Migration
2
+ def up
3
+ rename_table :assemblies_parts, :spree_assemblies_parts
4
+ end
5
+
6
+ def down
7
+ rename_table :spree_assemblies_parts, :assemblies_parts
8
+ end
9
+ end
@@ -0,0 +1,9 @@
1
+ class AddIdToSpreeAssembliesParts < ActiveRecord::Migration
2
+ def up
3
+ add_column :spree_assemblies_parts, :id, :primary_key
4
+ end
5
+
6
+ def down
7
+ add_column :spree_assemblies_parts, :id
8
+ end
9
+ end
@@ -0,0 +1,5 @@
1
+ class AddVariantSelectionDeferredToAssembliesParts < ActiveRecord::Migration
2
+ def change
3
+ add_column :spree_assemblies_parts, :variant_selection_deferred, :boolean
4
+ end
5
+ end
@@ -0,0 +1,9 @@
1
+ class CreatePartLineItems < ActiveRecord::Migration
2
+ def change
3
+ create_table :spree_part_line_items do |t|
4
+ t.belongs_to :line_item, null: false
5
+ t.belongs_to :variant, null: false
6
+ t.integer :quantity, default: 1
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,24 @@
1
+ module SpreeProductAssembly
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+ class_option :auto_run_migrations, :type => :boolean, :default => false
5
+
6
+ def add_migrations
7
+ run 'rake railties:install:migrations FROM=spree_product_assembly'
8
+ end
9
+
10
+ def add_javascripts
11
+ append_file "vendor/assets/javascripts/spree/backend/all.js", "//= require spree/backend/spree_product_assembly\n"
12
+ end
13
+
14
+ def run_migrations
15
+ run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask 'Would you like to run the migrations now? [Y/n]')
16
+ if run_migrations
17
+ run 'rake db:migrate'
18
+ else
19
+ puts "Skiping rake db:migrate, don't forget to run it!"
20
+ end
21
+ end
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,21 @@
1
+ module SpreeProductAssembly
2
+ class Engine < Rails::Engine
3
+ engine_name 'spree_product_assembly'
4
+
5
+ config.autoload_paths += %W(#{config.root}/lib)
6
+
7
+ def self.activate
8
+ Dir.glob(File.join(File.dirname(__FILE__), "../../app/**/*_decorator.rb")) do |c|
9
+ Rails.configuration.cache_classes ? require(c) : load(c)
10
+ end
11
+
12
+ if ::Rails::Engine.subclasses.map(&:name).include? "Spree::Wombat::Engine"
13
+ Dir.glob(File.join(File.dirname(__FILE__), "../../lib/**/*_serializer.rb")) do |serializer|
14
+ Rails.env.production? ? require(serializer) : load(serializer)
15
+ end
16
+ end
17
+ end
18
+
19
+ config.to_prepare &method(:activate).to_proc
20
+ end
21
+ end
@@ -0,0 +1,4 @@
1
+ require 'spree_core'
2
+ require 'spree_product_assembly/engine'
3
+ require 'coffee_script'
4
+ require 'sass/rails'
@@ -0,0 +1,29 @@
1
+ namespace :spree_product_assembly do
2
+ desc 'Link legacy inventory units to an order line item'
3
+ task :upgrade => :environment do
4
+ shipments = Spree::Shipment.includes(:inventory_units).where("spree_inventory_units.line_item_id IS NULL")
5
+
6
+ shipments.each do |shipment|
7
+ shipment.inventory_units.includes(:variant).group_by(&:variant).each do |variant, units|
8
+
9
+ line_item = shipment.order.line_items.detect { |line_item| line_item.variant_id == variant.id }
10
+
11
+ unless line_item
12
+
13
+ begin
14
+ master = shipment.order.products.detect { |p| variant.assemblies.include? p }.master
15
+ supposed_line_item = shipment.order.line_items.detect { |line_item| line_item.variant_id == master.id }
16
+
17
+ if supposed_line_item
18
+ Spree::InventoryUnit.where(id: units.map(&:id)).update_all "line_item_id = #{supposed_line_item.id}"
19
+ else
20
+ puts "Couldn't find a matching line item for #{variant.name}"
21
+ end
22
+ rescue
23
+ puts "Couldn't find a matching line item for #{variant.name}"
24
+ end
25
+ end
26
+ end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,33 @@
1
+ Gem::Specification.new do |s|
2
+ s.platform = Gem::Platform::RUBY
3
+ s.name = 'solidus_product_bundle'
4
+ s.version = '1.0.0'
5
+ s.summary = 'Adds oportunity to make bundle of products to your Spree store'
6
+ s.description = s.summary
7
+ s.required_ruby_version = '>= 1.9.3'
8
+
9
+ s.author = 'Sapna Tomar'
10
+ s.email = 'ystomar12488@gmail.com'
11
+ s.homepage = 'https://github.com/YSTomar/solidus_product_bundle'
12
+
13
+ s.files = `git ls-files`.split("\n")
14
+ s.test_files = `git ls-files -- spec/*`.split("\n")
15
+ s.require_path = 'lib'
16
+ s.requirements << 'none'
17
+
18
+ s.add_dependency "solidus_api", [">= 1.0.0.pre", "< 2"]
19
+ s.add_dependency "solidus_backend", [">= 1.0.0.pre", "< 2"]
20
+ s.add_dependency "solidus_core", [">= 1.0.0.pre", "< 2"]
21
+
22
+ s.add_development_dependency 'rspec-rails', '~> 3.1.0'
23
+ s.add_development_dependency 'sqlite3'
24
+ s.add_development_dependency 'ffaker'
25
+ s.add_development_dependency 'factory_girl', '~> 4.4'
26
+ s.add_development_dependency 'coffee-rails', '~> 4.0.0'
27
+ s.add_development_dependency 'sass-rails', '~> 4.0.0'
28
+ s.add_development_dependency 'capybara', '~> 2.4'
29
+ s.add_development_dependency 'poltergeist', '~> 1.6'
30
+ s.add_development_dependency 'database_cleaner', '~> 1.3'
31
+ s.add_development_dependency 'simplecov'
32
+ s.add_development_dependency 'pg'
33
+ end
@@ -0,0 +1,203 @@
1
+ require "spec_helper"
2
+
3
+ describe "Adding items to the cart", type: :feature do
4
+ context "when adding a bundle to the cart" do
5
+ context "when none of the bundle items are packs or have options" do
6
+ specify "the cart lists the contents of the bundle" 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
+ within("tbody tr:first-child") do
25
+ expect(page).to have_content(bundle.name)
26
+ expect(page).to have_css("input[value='1']")
27
+ expect(page).to have_content("(1) Keychain (KEYCHAIN)")
28
+ expect(page).to have_content("(1) Shirt (SHIRT)")
29
+ end
30
+ end
31
+ end
32
+ end
33
+
34
+ context "when one of the variants is a pack" do
35
+ specify "the cart displays the same quantity for part line items" do
36
+ bundle = create(:product_in_stock, name: "Bundle", sku: "BUNDLE")
37
+
38
+ keychain = create(:product_in_stock, name: "Keychain",
39
+ sku: "KEYCHAIN",
40
+ can_be_part: true)
41
+ _shirt, shirts_by_size = create_bundle_product_with_options(
42
+ name: "Shirt",
43
+ sku: "SHIRT",
44
+ option_type: "Size",
45
+ option_values: ["Small"]
46
+ )
47
+
48
+ add_part_to_bundle(bundle, keychain.master, count: 2)
49
+ add_part_to_bundle(bundle, shirts_by_size["small"])
50
+
51
+ visit spree.product_path(bundle)
52
+
53
+ click_button "add-to-cart-button"
54
+
55
+ within("#cart-detail tbody tr:first-child") do
56
+ expect(page).to have_content(bundle.name)
57
+ expect(page).to have_css("input[value='1']")
58
+ expect(page).to have_content("(2) Keychain (KEYCHAIN)")
59
+ expect(page).to have_content("(1) Shirt (Size: Small) (SHIRT-SMALL)")
60
+ end
61
+ end
62
+
63
+ context "when ordering more than one of the bundle" do
64
+ specify "the part quantity is multiplied by the bundle quantity" do
65
+ bundle = create(:product_in_stock, name: "Bundle", sku: "BUNDLE")
66
+
67
+ keychain = create(:product_in_stock, name: "Keychain",
68
+ sku: "KEYCHAIN",
69
+ can_be_part: true)
70
+ shirt = create(:product_in_stock, name: "Shirt",
71
+ sku: "SHIRT",
72
+ can_be_part: true)
73
+
74
+ add_part_to_bundle(bundle, keychain.master, count: 2)
75
+ add_part_to_bundle(bundle, shirt.master)
76
+
77
+ visit spree.product_path(bundle)
78
+
79
+ fill_in "quantity", with: 2
80
+
81
+ click_button "add-to-cart-button"
82
+
83
+ within("#cart-detail tbody tr:first-child") do
84
+ expect(page).to have_content(bundle.name)
85
+ expect(page).to have_css("input[value='2']")
86
+ expect(page).to have_content("(4) Keychain (KEYCHAIN)")
87
+ expect(page).to have_content("(2) Shirt (SHIRT)")
88
+ end
89
+ end
90
+ end
91
+ end
92
+
93
+ context "when a bundle items has a variant (that is not user-selectable)" do
94
+ specify "the cart includes the variant when listing items bundle items" do
95
+ bundle = create(:product_in_stock, name: "Bundle", sku: "BUNDLE")
96
+
97
+ keychain = create(:product_in_stock, name: "Keychain",
98
+ sku: "KEYCHAIN",
99
+ can_be_part: true)
100
+ _shirt, shirts_by_size = create_bundle_product_with_options(
101
+ name: "Shirt",
102
+ sku: "SHIRT",
103
+ option_type: "Size",
104
+ option_values: ["Small"]
105
+ )
106
+
107
+ add_part_to_bundle(bundle, keychain.master)
108
+ add_part_to_bundle(bundle, shirts_by_size["small"])
109
+
110
+ visit spree.product_path(bundle)
111
+
112
+ click_button "add-to-cart-button"
113
+
114
+ within("#cart-detail tbody tr:first-child") do
115
+ expect(page).to have_content(bundle.name)
116
+ expect(page).to have_css("input[value='1']")
117
+ expect(page).to have_content("(1) Keychain (KEYCHAIN)")
118
+ expect(page).to have_content("(1) Shirt (Size: Small) (SHIRT-SMALL)")
119
+ end
120
+ end
121
+ end
122
+
123
+ context "when one of the bundle items has a user-selectable variant" do
124
+ specify "the cart includes the variant when listing bundle items" do
125
+ bundle = create(:product_in_stock, name: "Bundle", sku: "BUNDLE")
126
+
127
+ keychain = create(:product_in_stock, name: "Keychain",
128
+ sku: "KEYCHAIN",
129
+ can_be_part: true)
130
+ shirt, _shirts_by_size = create_bundle_product_with_options(
131
+ name: "Shirt",
132
+ sku: "SHIRT",
133
+ option_type: "Size",
134
+ option_values: ["Small", "Medium"]
135
+ )
136
+
137
+ add_part_to_bundle(bundle, keychain.master, count: 1)
138
+ add_part_to_bundle(
139
+ bundle,
140
+ shirt.master,
141
+ variant_selection_deferred: true
142
+ )
143
+
144
+ visit spree.product_path(bundle)
145
+
146
+ select "Size: Medium", from: "Variant"
147
+
148
+ click_button "add-to-cart-button"
149
+
150
+ within("#cart-detail tbody tr:first-child") do
151
+ expect(page).to have_content(bundle.name)
152
+ expect(page).to have_css("input[value='1']")
153
+ expect(page).to have_content("(1) Keychain (KEYCHAIN)")
154
+ expect(page).to(
155
+ have_content("(1) Shirt (Size: Medium) (SHIRT-MEDIUM)")
156
+ )
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ def create_bundle_product_with_options(args)
163
+ option_type_presentation = args.fetch(:option_type)
164
+ option_value_presentations = args.fetch(:option_values)
165
+ option_values = option_value_presentations.map do |presentation|
166
+ create(:option_value, presentation: presentation)
167
+ end
168
+ option_type = create(:option_type,
169
+ presentation: option_type_presentation,
170
+ name: option_type_presentation.downcase,
171
+ option_values: option_values)
172
+ product_attributes = args.slice(:name, :sku).merge(
173
+ option_types: [option_type],
174
+ can_be_part: true
175
+ )
176
+ product = create(:product, product_attributes)
177
+
178
+ variants = variants_by_option(product, option_values)
179
+
180
+ [product, variants]
181
+ end
182
+
183
+ def variants_by_option(product, option_values)
184
+ option_values.each_with_object({}) do |value, hash|
185
+ hash[value.presentation.downcase] = create(
186
+ :variant_in_stock,
187
+ product: product,
188
+ sku: "#{product.sku}-#{value.presentation.upcase}",
189
+ option_values: [value]
190
+ )
191
+ end
192
+ end
193
+
194
+ def add_part_to_bundle(bundle, variant, options = {})
195
+ attributes = options.reverse_merge(
196
+ assembly_id: bundle.id,
197
+ part_id: variant.id,
198
+ )
199
+ create(:assemblies_part, attributes).tap do |_part|
200
+ bundle.reload
201
+ end
202
+ end
203
+ end
@@ -0,0 +1,29 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Orders", type: :feature, js: true do
4
+ stub_authorization!
5
+
6
+ let(:order) { create(:order_with_line_items) }
7
+ let(:line_item) { order.line_items.first }
8
+ let(:bundle) { line_item.product }
9
+ let(:parts) { (1..3).map { create(:variant) } }
10
+
11
+ before do
12
+ bundle.parts << [parts]
13
+ line_item.update_attributes!(quantity: 3)
14
+ order.reload.create_proposed_shipments
15
+ order.finalize!
16
+ end
17
+
18
+ it "allows admin to edit product bundle" do
19
+ visit spree.edit_admin_order_path(order)
20
+
21
+ within("table.product-bundles") do
22
+ find(".edit-line-item").click
23
+ fill_in "quantity", :with => "2"
24
+ find(".save-line-item").click
25
+
26
+ sleep(1) # avoid odd "cannot rollback - no transaction is active: rollback transaction"
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,183 @@
1
+ require 'spec_helper'
2
+
3
+ describe "Managing parts for a product bundle", type: :feature, js: true do
4
+ stub_authorization!
5
+
6
+ let!(:tshirt) { create(:product, :name => "T-Shirt") }
7
+ let!(:mug) { create(:product, :name => "Mug", can_be_part: true) }
8
+
9
+ context "when searching for parts" do
10
+ before do
11
+ visit spree.admin_product_path(tshirt)
12
+ click_on "Parts"
13
+ end
14
+
15
+ it "returns empty results when there is no query" do
16
+ fill_in "searchtext", with: ""
17
+ click_on "Search"
18
+
19
+ page.should have_content("No Match Found.")
20
+ end
21
+
22
+ it "displays no-match feedback when it does not find any products" do
23
+ fill_in "searchtext", with: "Foo"
24
+ click_on "Search"
25
+
26
+ page.should have_content("No Match Found.")
27
+ end
28
+
29
+ it "shows any products that were found" do
30
+ fill_in "searchtext", with: mug.name
31
+ click_on "Search"
32
+
33
+ page.should have_content(mug.name)
34
+ end
35
+ end
36
+
37
+ context "when adding parts to a bundle" do
38
+ it "allows adding a product with no variants" do
39
+ visit spree.admin_product_path(tshirt)
40
+ click_on "Parts"
41
+ fill_in "searchtext", with: mug.name
42
+ click_on "Search"
43
+
44
+ within("#search_hits") { click_on "Select" }
45
+ page.should have_content(mug.sku)
46
+ end
47
+
48
+ context "when a part has multiple variants" do
49
+ def build_option(options)
50
+ option_type_name = options.fetch(:type)
51
+ option_type = create(:option_type,
52
+ presentation: option_type_name,
53
+ name: option_type_name
54
+ )
55
+ option_value = options.fetch(:value)
56
+ option_type.option_values.create(
57
+ name: option_value.downcase,
58
+ presentation: option_value
59
+ )
60
+
61
+ option_type
62
+ end
63
+
64
+ def build_part_with_options(product_name, option_type)
65
+ product = create(:product,
66
+ can_be_part: true,
67
+ name: product_name,
68
+ option_types: [option_type]
69
+ )
70
+ create(:variant,
71
+ product: product,
72
+ option_values: option_type.option_values
73
+ )
74
+ end
75
+
76
+ it "allows a specific variant to be selected as part of the bundle" do
77
+ bundle = create(:product)
78
+ option = build_option(type: "Color", value: "Red")
79
+ part = build_part_with_options("Shirt", option)
80
+
81
+ visit spree.admin_product_path(bundle)
82
+ click_on "Parts"
83
+ fill_in "searchtext", with: "Shirt"
84
+ click_on "Search"
85
+
86
+ within("#search_hits") do
87
+ select "Color: Red", from: "part_id"
88
+ click_on "Select"
89
+ end
90
+
91
+ page.should have_content(part.sku)
92
+ end
93
+
94
+ it "allows admin to specify that user can select any variant" do
95
+ bundle = create(:product)
96
+ option = build_option(type: "Color", value: "Red")
97
+ part = build_part_with_options("Shirt", option)
98
+
99
+ visit spree.admin_product_path(bundle)
100
+ click_on "Parts"
101
+ fill_in "searchtext", with: "Shirt"
102
+ click_on "Search"
103
+
104
+ within("#search_hits") do
105
+ select Spree.t(:user_selectable), from: "part_id"
106
+ fill_in "part_count", with: 666
107
+ click_on "Select"
108
+ end
109
+
110
+ within("#product_parts") do
111
+ page.should have_content("Shirt")
112
+ page.should have_content(part.product.sku)
113
+ page.should have_content(Spree.t(:user_selectable))
114
+
115
+ input = find_field("count")
116
+ input[:value].should eq("666")
117
+ end
118
+ end
119
+ end
120
+ end
121
+
122
+ it "allows parts to be removed from the bundle" do
123
+ visit spree.admin_product_path(tshirt)
124
+ click_on "Parts"
125
+ fill_in "searchtext", with: mug.name
126
+ click_on "Search"
127
+
128
+ within("#search_hits") { click_on "Select" }
129
+ page.should have_content(mug.sku)
130
+
131
+ within("#product_parts") do
132
+ find(".remove_admin_product_part_link").click
133
+
134
+ page.should_not have_content(mug.sku)
135
+ end
136
+ end
137
+
138
+ context "when updating part quantities" do
139
+ before do
140
+ visit spree.admin_product_path(tshirt)
141
+ click_on "Parts"
142
+ fill_in "searchtext", with: mug.name
143
+ click_on "Search"
144
+ within("#search_hits") { click_on "Select" }
145
+ end
146
+
147
+ it "updates the quantity to match the newly-supplied value" do
148
+ within("#product_parts") do
149
+ fill_in "count", with: "5"
150
+ find(".set_count_admin_product_part_link").click
151
+
152
+ expect(find_field('count').value).to eq "5"
153
+ end
154
+ end
155
+
156
+ it "rejects a negative quantity" do
157
+ within("#product_parts") do
158
+ fill_in "count", with: "-1"
159
+ find(".set_count_admin_product_part_link").click
160
+ end
161
+
162
+ expect(page).to have_content("Quantity must be greater than 0")
163
+ end
164
+
165
+ it "rejects a part quantity of `0`" do
166
+ within("#product_parts") do
167
+ fill_in "count", with: "0"
168
+ find(".set_count_admin_product_part_link").click
169
+ end
170
+
171
+ expect(page).to have_content("Quantity must be greater than 0")
172
+ end
173
+
174
+ it "rejects a non-numeric part quantity" do
175
+ within("#product_parts") do
176
+ fill_in "count", with: "non-numeric"
177
+ find(".set_count_admin_product_part_link").click
178
+ end
179
+
180
+ expect(page).to have_content("Quantity must be greater than 0")
181
+ end
182
+ end
183
+ end