solidus_product_assembly 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 +4 -0
- data/.rspec +3 -0
- data/.travis.yml +21 -0
- data/Gemfile +18 -0
- data/LICENSE.md +13 -0
- data/README.md +80 -0
- data/Rakefile +21 -0
- data/app/assets/images/spinner.gif +0 -0
- data/app/assets/javascripts/spree/backend/solidus_product_assembly.js +1 -0
- data/app/assets/javascripts/spree/frontend/solidus_product_assembly.js +1 -0
- data/app/assets/stylesheets/spree/backend/solidus_product_assembly.css +3 -0
- data/app/assets/stylesheets/spree/frontend/solidus_product_assembly.css +3 -0
- data/app/controllers/spree/admin/parts_controller.rb +49 -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 +12 -0
- data/app/models/spree/inventory_unit_decorator.rb +13 -0
- data/app/models/spree/line_item_decorator.rb +44 -0
- data/app/models/spree/order_inventory_assembly.rb +39 -0
- data/app/models/spree/product_decorator.rb +56 -0
- data/app/models/spree/shipment_decorator.rb +50 -0
- data/app/models/spree/stock/availability_validator.rb +28 -0
- data/app/models/spree/stock/inventory_unit_builder_decorator.rb +30 -0
- data/app/models/spree/stock/inventory_validator_decorator.rb +12 -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/show/add_links_to_parts.html.erb.deface +21 -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 +8 -0
- data/app/views/spree/admin/orders/_assemblies.html.erb +56 -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 +55 -0
- data/app/views/spree/admin/parts/available.js.erb +54 -0
- data/app/views/spree/admin/parts/index.html.erb +70 -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 +8 -0
- data/bin/rails +7 -0
- data/config/locales/en.yml +14 -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/lib/generators/solidus_product_assembly/install/install_generator.rb +24 -0
- data/lib/solidus_product_assembly.rb +3 -0
- data/lib/solidus_product_assembly/engine.rb +15 -0
- data/lib/solidus_product_assembly/version.rb +3 -0
- data/lib/tasks/spree2_upgrade.rake +29 -0
- data/solidus_product_assembly.gemspec +35 -0
- data/spec/features/admin/orders_spec.rb +29 -0
- data/spec/features/admin/parts_spec.rb +28 -0
- data/spec/features/checkout_spec.rb +92 -0
- data/spec/models/spree/assemblies_part_spec.rb +18 -0
- data/spec/models/spree/inventory_unit_spec.rb +32 -0
- data/spec/models/spree/line_item_spec.rb +72 -0
- data/spec/models/spree/order_contents_spec.rb +40 -0
- data/spec/models/spree/order_inventory_assembly_spec.rb +55 -0
- data/spec/models/spree/order_inventory_spec.rb +35 -0
- data/spec/models/spree/product_spec.rb +35 -0
- data/spec/models/spree/shipment_spec.rb +112 -0
- data/spec/models/spree/stock/availability_validator_spec.rb +71 -0
- data/spec/models/spree/stock/coordinator_spec.rb +52 -0
- data/spec/models/spree/stock/inventory_unit_builder_spec.rb +36 -0
- data/spec/models/spree/variant_spec.rb +28 -0
- data/spec/spec_helper.rb +57 -0
- data/spec/support/shared_contexts/order_with_bundle.rb +13 -0
- metadata +272 -0
@@ -0,0 +1,70 @@
|
|
1
|
+
<%# This partial was remove in solidus 1.2 but that is also when Spree.solidus_version was added %>
|
2
|
+
<%= render :partial => 'spree/admin/shared/product_sub_menu' if !Spree.respond_to?(:solidus_version) %>
|
3
|
+
|
4
|
+
<%= render :partial => 'spree/admin/shared/product_tabs', :locals => {:current => "Parts"} %>
|
5
|
+
<div id="product_parts">
|
6
|
+
<%= render :partial => "parts_table", :locals => {:parts => @parts} %>
|
7
|
+
</div>
|
8
|
+
|
9
|
+
<%= form_tag('#') do %>
|
10
|
+
<label><%= Spree.t(:search) %>:</label>
|
11
|
+
<input id="searchtext" size="25">
|
12
|
+
<button id="search_parts_button" class="fa fa-search button" name="button">Search</button>
|
13
|
+
<% end %>
|
14
|
+
|
15
|
+
<br/>
|
16
|
+
<div id="search_hits"></div>
|
17
|
+
<%= javascript_tag do %>
|
18
|
+
/*!
|
19
|
+
* Spree Product Assembly
|
20
|
+
* https://github.com/spree/spree-product-assembly
|
21
|
+
*
|
22
|
+
*/
|
23
|
+
|
24
|
+
function search_for_parts(){
|
25
|
+
$.ajax({
|
26
|
+
data: {q: $("#searchtext").val() },
|
27
|
+
dataType: 'html',
|
28
|
+
success: function(request){
|
29
|
+
jQuery('#search_hits').html(request);
|
30
|
+
$('#search_hits').show();
|
31
|
+
},
|
32
|
+
type: 'POST',
|
33
|
+
url: '<%= available_admin_product_parts_url(@product) %>'
|
34
|
+
});
|
35
|
+
}
|
36
|
+
|
37
|
+
$("#searchtext").keypress(function (e) {
|
38
|
+
if ((e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)) {
|
39
|
+
search_for_parts();
|
40
|
+
return false;
|
41
|
+
} else {
|
42
|
+
return true;
|
43
|
+
}
|
44
|
+
});
|
45
|
+
|
46
|
+
$("#search_parts_button").click(function(e) {
|
47
|
+
e.preventDefault();
|
48
|
+
search_for_parts();
|
49
|
+
});
|
50
|
+
|
51
|
+
function subscribe_product_part_links()
|
52
|
+
{
|
53
|
+
$("a.set_count_admin_product_part_link").click(function(){
|
54
|
+
params = { count : $("input", $(this).parent().parent()).val() };
|
55
|
+
return make_post_request($(this), params);
|
56
|
+
});
|
57
|
+
|
58
|
+
$("a.remove_admin_product_part_link").click(function(){
|
59
|
+
return make_post_request($(this), {});
|
60
|
+
});
|
61
|
+
}
|
62
|
+
|
63
|
+
function make_post_request(link, post_params)
|
64
|
+
{
|
65
|
+
$.post(link.attr("href"), post_params, "script");
|
66
|
+
return false;
|
67
|
+
}
|
68
|
+
|
69
|
+
subscribe_product_part_links();
|
70
|
+
<% end -%>
|
@@ -0,0 +1,23 @@
|
|
1
|
+
<div class="left six columns alpha">
|
2
|
+
<div class="field">
|
3
|
+
<%= f.label :can_be_part, Spree.t(:can_be_part)%>
|
4
|
+
<%= f.check_box(:can_be_part) %>
|
5
|
+
</div>
|
6
|
+
</div>
|
7
|
+
<div class="right six columns omega">
|
8
|
+
<div class="field">
|
9
|
+
<%= f.label :individual_sale, Spree.t(:individual_sale)%>
|
10
|
+
<%= f.check_box(:individual_sale) %>
|
11
|
+
</div>
|
12
|
+
</div>
|
13
|
+
|
14
|
+
<% if @product.assembly? %>
|
15
|
+
<% content_for :head do %>
|
16
|
+
<script type="text/javascript">
|
17
|
+
jQuery(document).ready(function(){
|
18
|
+
$("input[id='product_on_hand']").attr('disabled',true).parent().hide();
|
19
|
+
$("input#product_can_be_part").attr('disabled',true);
|
20
|
+
});
|
21
|
+
</script>
|
22
|
+
<% end %>
|
23
|
+
<% end %>
|
@@ -0,0 +1,17 @@
|
|
1
|
+
<% ship_form.object.line_item_manifest.each do |item| %>
|
2
|
+
<tr class="stock-item">
|
3
|
+
<td class="item-image"><%= mini_image(item.variant) %></td>
|
4
|
+
<td class="item-name">
|
5
|
+
<%= item.variant.name %>
|
6
|
+
<%= render 'spree/orders/cart_description', variant: item.variant, line_item: item.line_item %>
|
7
|
+
</td>
|
8
|
+
<td class="item-qty">
|
9
|
+
<% if item.line_item.product.assembly? %>
|
10
|
+
<%= item.line_item.quantity %>
|
11
|
+
<% else %>
|
12
|
+
<%= item.quantity %>
|
13
|
+
<% end %>
|
14
|
+
</td>
|
15
|
+
<td class="item-price"><%= item.line_item.single_money %></td>
|
16
|
+
</tr>
|
17
|
+
<% end %>
|
data/bin/rails
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
---
|
2
|
+
en:
|
3
|
+
spree:
|
4
|
+
available_parts: Available parts
|
5
|
+
can_be_part: Can be part
|
6
|
+
individual_sale: Individual sale
|
7
|
+
no_variants: No variants
|
8
|
+
parts: Parts
|
9
|
+
assembly_cannot_be_part: assembly can't be part
|
10
|
+
product_bundles: Product Bundles
|
11
|
+
parts_included: Parts included
|
12
|
+
part_of_bundle: 'Part of bundle %{sku}'
|
13
|
+
actions:
|
14
|
+
delete: 'Delete'
|
@@ -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,24 @@
|
|
1
|
+
module SolidusProductAssembly
|
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=solidus_product_assembly'
|
8
|
+
end
|
9
|
+
|
10
|
+
def add_javascripts
|
11
|
+
append_file "vendor/assets/javascripts/spree/backend/all.js", "//= require spree/backend/solidus_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,15 @@
|
|
1
|
+
module SolidusProductAssembly
|
2
|
+
class Engine < Rails::Engine
|
3
|
+
engine_name 'solidus_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
|
+
end
|
12
|
+
|
13
|
+
config.to_prepare &method(:activate).to_proc
|
14
|
+
end
|
15
|
+
end
|
@@ -0,0 +1,29 @@
|
|
1
|
+
namespace :solidus_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,35 @@
|
|
1
|
+
# encoding: UTF-8
|
2
|
+
|
3
|
+
lib = File.expand_path('../lib/', __FILE__)
|
4
|
+
$:.unshift lib unless $:.include?(lib)
|
5
|
+
|
6
|
+
require 'solidus_product_assembly/version'
|
7
|
+
|
8
|
+
Gem::Specification.new do |s|
|
9
|
+
s.platform = Gem::Platform::RUBY
|
10
|
+
s.name = 'solidus_product_assembly'
|
11
|
+
s.version = SolidusProductAssembly::VERSION
|
12
|
+
s.summary = 'Adds oportunity to make bundle of products to your Spree store'
|
13
|
+
s.description = s.summary
|
14
|
+
s.required_ruby_version = '>= 1.9.3'
|
15
|
+
|
16
|
+
s.author = 'Roman Smirnov'
|
17
|
+
s.email = 'roman@railsdog.com'
|
18
|
+
s.homepage = 'https://solidus.io'
|
19
|
+
|
20
|
+
s.files = `git ls-files`.split("\n")
|
21
|
+
s.test_files = `git ls-files -- spec/*`.split("\n")
|
22
|
+
s.require_path = 'lib'
|
23
|
+
s.requirements << 'none'
|
24
|
+
|
25
|
+
s.add_runtime_dependency 'solidus_backend', [">= 1.0", "< 3"]
|
26
|
+
|
27
|
+
s.add_development_dependency 'rspec-rails', '~> 3.4'
|
28
|
+
s.add_development_dependency 'sqlite3'
|
29
|
+
s.add_development_dependency 'ffaker'
|
30
|
+
s.add_development_dependency 'factory_girl', '~> 4.4'
|
31
|
+
s.add_development_dependency 'capybara', '~> 2.7'
|
32
|
+
s.add_development_dependency 'poltergeist', '~> 1.9'
|
33
|
+
s.add_development_dependency 'database_cleaner', '~> 1.3'
|
34
|
+
s.add_development_dependency 'simplecov'
|
35
|
+
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,28 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe "Parts", type: :feature, js: true do
|
4
|
+
stub_authorization!
|
5
|
+
|
6
|
+
let!(:tshirt) { create(:product, :name => "T-Shirt") }
|
7
|
+
let!(:mug) { create(:product, :name => "Mug") }
|
8
|
+
|
9
|
+
before do
|
10
|
+
visit spree.admin_product_path(mug)
|
11
|
+
check "product_can_be_part"
|
12
|
+
click_on "Update"
|
13
|
+
end
|
14
|
+
|
15
|
+
it "add and remove parts" do
|
16
|
+
visit spree.admin_product_path(tshirt)
|
17
|
+
click_on "Parts"
|
18
|
+
fill_in "searchtext", with: mug.name
|
19
|
+
click_on "Search"
|
20
|
+
|
21
|
+
within("#search_hits") { click_on "Select" }
|
22
|
+
page.should have_content(mug.sku)
|
23
|
+
|
24
|
+
within("#product_parts") do
|
25
|
+
find(".remove_admin_product_part_link").click
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|