solidus_product_assembly 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 +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
|