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
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 9a9bb87d164bab435ae5b5dae93c8f25003fbacf
|
4
|
+
data.tar.gz: 364c684f10939d8d693b326ebe6f6e006b69f88e
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: a0dd50bd5b5ead6023050936ac7847512b8a32980d1cbc23071bdf07a7e5f266a77fabdc245956ed506c469fed3591ec9687d15f94a4d7d9de3b21eb7399c9d6
|
7
|
+
data.tar.gz: d7c521af96a68c35f539ea7c7fe1250ab6274929c5ec09742d5592ea40f23e4ff80c1cdb1bfe018c70286626a6496dadcfa5a42034401ff9cb2d8a74ba9005fb
|
data/.gitignore
ADDED
data/.rspec
ADDED
data/.travis.yml
ADDED
data/Gemfile
ADDED
data/LICENSE.md
ADDED
@@ -0,0 +1,13 @@
|
|
1
|
+
Spree Product Assembly License
|
2
|
+
==============================
|
3
|
+
|
4
|
+
Copyright © 2007-2014, Spree Commerce Inc. and other contributors.
|
5
|
+
All rights reserved.
|
6
|
+
|
7
|
+
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met:
|
8
|
+
|
9
|
+
* Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
|
10
|
+
* Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
|
11
|
+
* Neither the name of Spree Commerce Inc. nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission.
|
12
|
+
|
13
|
+
_This software is provided by the copyright holders and contributors "as is" and any express or implied warranties, including, but not limited to, the implied warranties of merchantability and fitness for a particular purpose are disclaimed. In no event shall the copyright owner of contributors be liable for any direct, indirect, incidental, special, exemplary, or consequential damages (including, but not limited to, procurement of substitute goods or services; loss of use, data, or profits; or business interruption) however caused and on any theory of liability, whether in contract, strict liability, or tort (including negligence or otherwise) arising in any way out of the use of this software, even if advised of the possibility of such damage._
|
data/README.markdown
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
# Product Assembly
|
2
|
+
|
3
|
+
[![Build Status](https://secure.travis-ci.org/spree-contrib/spree-product-assembly.png)](https://travis-ci.org/spree-contrib/spree-product-assembly)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/spree-contrib/spree-product-assembly.png)](https://codeclimate.com/github/spree-contrib/spree-product-assembly)
|
5
|
+
|
6
|
+
Create a product which is composed of other products.
|
7
|
+
|
8
|
+
## Installation
|
9
|
+
|
10
|
+
Add the following line to your `Gemfile`
|
11
|
+
```ruby
|
12
|
+
gem 'spree_product_assembly', github: 'spree-contrib/spree-product-assembly', branch: 'master'
|
13
|
+
```
|
14
|
+
|
15
|
+
Run bundle install as well as the extension intall command to copy and run migrations and
|
16
|
+
append spree_product_assembly to your js manifest file
|
17
|
+
|
18
|
+
bundle install
|
19
|
+
rails g spree_product_assembly:install
|
20
|
+
|
21
|
+
_master branch is compatible with spree edge and rails 4 only. Please use
|
22
|
+
2-0-stable for Spree 2.0.x or 1-3-stable branch for Spree 1.3.x compatibility_
|
23
|
+
|
24
|
+
_In case you're upgrading from 1-3-stable of this extension you might want to run a
|
25
|
+
rake task which assigns a line item to your previous inventory units from bundle
|
26
|
+
products. That is so you have a better view on the current backend UI and avoid
|
27
|
+
exceptions. No need to run this task if you're not upgrading from product assembly
|
28
|
+
1-3-stable_
|
29
|
+
|
30
|
+
rake spree_product_assembly:upgrade
|
31
|
+
|
32
|
+
## Use
|
33
|
+
|
34
|
+
To build a bundle (assembly product) you'd need to first check the "Can be part"
|
35
|
+
flag on each product you want to be part of the bundle. Then create a product
|
36
|
+
and add parts to it. By doing that you're making that product an assembly.
|
37
|
+
|
38
|
+
The store will treat assemblies a bit different than regular products on checkout.
|
39
|
+
Spree will create and track inventory units for its parts rather than for the product itself.
|
40
|
+
That means you essentially have a product composed of other products. From a
|
41
|
+
customer perspective it's like they are paying a single amount for a collection
|
42
|
+
of products.
|
43
|
+
|
44
|
+
## Using with spree_wombat
|
45
|
+
|
46
|
+
If you use this with spree_wombat make sure that you add this extension after
|
47
|
+
spree_wombat in your `Gemfile`
|
48
|
+
|
49
|
+
This extension provides a specific serializer for shipments `assembly_shipment_serializer`, to use this in your Spree storefront make sure
|
50
|
+
you configure spree_wombat like this:
|
51
|
+
|
52
|
+
```ruby
|
53
|
+
config.payload_builder = {
|
54
|
+
"Spree::Shipment" => {:serializer => "Spree::Wombat::AssemblyShipmentSerializer", :root => "shipments"}
|
55
|
+
}
|
56
|
+
```
|
57
|
+
|
58
|
+
Contributing
|
59
|
+
------------
|
60
|
+
|
61
|
+
Spree is an open source project and we encourage contributions. Please see the [contributors guidelines][1] before contributing.
|
62
|
+
|
63
|
+
In the spirit of [free software][2], **everyone** is encouraged to help improve this project.
|
64
|
+
|
65
|
+
Here are some ways *you* can contribute:
|
66
|
+
|
67
|
+
* by using prerelease versions
|
68
|
+
* by reporting [bugs][3]
|
69
|
+
* by suggesting new features
|
70
|
+
* by writing translations
|
71
|
+
* by writing or editing documentation
|
72
|
+
* by writing specifications
|
73
|
+
* by writing code (*no patch is too small*: fix typos, add comments, clean up inconsistent whitespace)
|
74
|
+
* by refactoring code
|
75
|
+
* by resolving [issues][3]
|
76
|
+
* by reviewing patches
|
77
|
+
|
78
|
+
Starting point:
|
79
|
+
|
80
|
+
* Fork the repo
|
81
|
+
* Clone your repo
|
82
|
+
* Run `bundle install`
|
83
|
+
* Run `bundle exec rake test_app` to create the test application in `spec/test_app`
|
84
|
+
* Make your changes
|
85
|
+
* Ensure specs pass by running `bundle exec rspec spec`
|
86
|
+
* Submit your pull request
|
87
|
+
|
88
|
+
Copyright (c) 2014 [Spree Commerce Inc.][4] and [contributors][5], released under the [New BSD License][6]
|
89
|
+
|
90
|
+
[1]: http://guides.spreecommerce.com/developer/contributing.html
|
91
|
+
[2]: http://www.fsf.org/licensing/essays/free-sw.html
|
92
|
+
[3]: https://github.com/spree/spree-product-assembly/issues
|
93
|
+
[4]: https://github.com/spree
|
94
|
+
[5]: https://github.com/spree/spree-product-assembly/graphs/contributors
|
95
|
+
[6]: https://github.com/spree/spree-product-assembly/blob/master/LICENSE.md
|
data/README.md
ADDED
data/Rakefile
ADDED
@@ -0,0 +1,15 @@
|
|
1
|
+
require 'bundler'
|
2
|
+
Bundler::GemHelper.install_tasks
|
3
|
+
|
4
|
+
require 'rspec/core/rake_task'
|
5
|
+
require 'spree/testing_support/common_rake'
|
6
|
+
|
7
|
+
RSpec::Core::RakeTask.new
|
8
|
+
|
9
|
+
task :default => [:spec]
|
10
|
+
|
11
|
+
desc "Generates a dummy app for testing"
|
12
|
+
task :test_app do
|
13
|
+
ENV['LIB_NAME'] = 'spree_product_assembly'
|
14
|
+
Rake::Task['common:test_app'].invoke
|
15
|
+
end
|
Binary file
|
@@ -0,0 +1,82 @@
|
|
1
|
+
#= require ./translations
|
2
|
+
|
3
|
+
$(document).ready ->
|
4
|
+
Spree.routes.available_admin_product_parts = (productSlug) ->
|
5
|
+
Spree.pathFor("admin/products/" + productSlug + "/parts/available")
|
6
|
+
|
7
|
+
showErrorMessages = (xhr) ->
|
8
|
+
response = JSON.parse(xhr.responseText)
|
9
|
+
show_flash("error", response)
|
10
|
+
|
11
|
+
partsTable = $("#product_parts")
|
12
|
+
searchResults = $("#search_hits")
|
13
|
+
|
14
|
+
searchForParts = ->
|
15
|
+
productSlug = partsTable.data("product-slug")
|
16
|
+
searchUrl = Spree.routes.available_admin_product_parts(productSlug)
|
17
|
+
|
18
|
+
$.ajax
|
19
|
+
data:
|
20
|
+
q: $("#searchtext").val()
|
21
|
+
dataType: 'html'
|
22
|
+
success: (request) ->
|
23
|
+
searchResults.html(request)
|
24
|
+
searchResults.show()
|
25
|
+
type: 'POST'
|
26
|
+
url: searchUrl
|
27
|
+
|
28
|
+
$("#searchtext").keypress (e) ->
|
29
|
+
if (e.which && e.which == 13) || (e.keyCode && e.keyCode == 13)
|
30
|
+
searchForParts()
|
31
|
+
false
|
32
|
+
else
|
33
|
+
true
|
34
|
+
|
35
|
+
$("#search_parts_button").click (e) ->
|
36
|
+
e.preventDefault()
|
37
|
+
searchForParts()
|
38
|
+
|
39
|
+
makePostRequest = (link, post_params = {}) ->
|
40
|
+
spinner = $("img.spinner", link.parent())
|
41
|
+
spinner.show()
|
42
|
+
|
43
|
+
request = $.ajax
|
44
|
+
type: "POST"
|
45
|
+
url: link.attr("href")
|
46
|
+
data: post_params
|
47
|
+
dateType: "script"
|
48
|
+
request.fail showErrorMessages
|
49
|
+
request.always -> spinner.hide()
|
50
|
+
|
51
|
+
false
|
52
|
+
|
53
|
+
searchResults.on "click", "a.add_product_part_link", (event) ->
|
54
|
+
event.preventDefault()
|
55
|
+
|
56
|
+
part = {}
|
57
|
+
link = $(this)
|
58
|
+
row = $("#" + link.data("target"))
|
59
|
+
loadingIndicator = $("img.spinner", link.parent())
|
60
|
+
quantityField = $('input:last', row)
|
61
|
+
|
62
|
+
part.count = quantityField.val()
|
63
|
+
|
64
|
+
if row.hasClass("with-variants")
|
65
|
+
selectedVariantOption = $('select option:selected', row)
|
66
|
+
part.variant_id = selectedVariantOption.val()
|
67
|
+
|
68
|
+
if selectedVariantOption.text() == Spree.translations.user_selectable
|
69
|
+
part.variant_selection_deferred = "t"
|
70
|
+
part.variant_id = link.data("master-variant-id")
|
71
|
+
|
72
|
+
else
|
73
|
+
part.variant_id = $('input[name="part[id]"]', row).val()
|
74
|
+
|
75
|
+
makePostRequest(link, {assemblies_part: part})
|
76
|
+
|
77
|
+
partsTable.on "click", "a.set_count_admin_product_part_link", ->
|
78
|
+
params = { count: $("input", $(this).parent().parent()).val() }
|
79
|
+
makePostRequest($(this), params)
|
80
|
+
|
81
|
+
partsTable.on "click", "a.remove_admin_product_part_link", ->
|
82
|
+
makePostRequest($(this))
|
@@ -0,0 +1 @@
|
|
1
|
+
//= require spree/frontend
|
@@ -0,0 +1,63 @@
|
|
1
|
+
class Spree::Admin::PartsController < Spree::Admin::BaseController
|
2
|
+
helper_method :product
|
3
|
+
|
4
|
+
def index
|
5
|
+
@parts = product.assemblies_parts
|
6
|
+
end
|
7
|
+
|
8
|
+
def remove
|
9
|
+
assembly_part = Spree::AssembliesPart.find(params[:id])
|
10
|
+
assembly_part.destroy
|
11
|
+
render 'spree/admin/parts/update_parts_table'
|
12
|
+
end
|
13
|
+
|
14
|
+
def set_count
|
15
|
+
save_part(existing_part_params)
|
16
|
+
end
|
17
|
+
|
18
|
+
def available
|
19
|
+
if params[:q].blank?
|
20
|
+
@available_products = []
|
21
|
+
else
|
22
|
+
query = "%#{params[:q]}%"
|
23
|
+
@available_products = Spree::Product.search_can_be_part(query)
|
24
|
+
@available_products.uniq!
|
25
|
+
end
|
26
|
+
respond_to do |format|
|
27
|
+
format.html {render :layout => false}
|
28
|
+
format.js {render :layout => false}
|
29
|
+
end
|
30
|
+
end
|
31
|
+
|
32
|
+
def create
|
33
|
+
save_part(new_part_params)
|
34
|
+
end
|
35
|
+
|
36
|
+
private
|
37
|
+
|
38
|
+
def save_part(part_params)
|
39
|
+
form = Spree::AssignPartToBundleForm.new(product, part_params)
|
40
|
+
if form.submit
|
41
|
+
render 'spree/admin/parts/update_parts_table'
|
42
|
+
else
|
43
|
+
error_message = form.errors.full_messages.to_sentence
|
44
|
+
render json: error_message.to_json, status: 422
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
def product
|
49
|
+
@product ||= Spree::Product.find_by(slug: params[:product_id])
|
50
|
+
end
|
51
|
+
|
52
|
+
def new_part_params
|
53
|
+
params.require(:assemblies_part).permit(
|
54
|
+
:count,
|
55
|
+
:variant_id,
|
56
|
+
:variant_selection_deferred
|
57
|
+
)
|
58
|
+
end
|
59
|
+
|
60
|
+
def existing_part_params
|
61
|
+
params.permit(:id, :count)
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,33 @@
|
|
1
|
+
module Spree
|
2
|
+
class AssembliesPart < ActiveRecord::Base
|
3
|
+
belongs_to :assembly, class_name: "Spree::Product",
|
4
|
+
foreign_key: "assembly_id",
|
5
|
+
touch: true
|
6
|
+
|
7
|
+
belongs_to :part, class_name: "Spree::Variant", foreign_key: "part_id"
|
8
|
+
|
9
|
+
delegate :name, :sku, to: :part
|
10
|
+
|
11
|
+
after_create :set_master_unlimited_stock
|
12
|
+
|
13
|
+
def self.get(assembly_id, part_id)
|
14
|
+
find_or_initialize_by(assembly_id: assembly_id, part_id: part_id)
|
15
|
+
end
|
16
|
+
|
17
|
+
def options_text
|
18
|
+
if variant_selection_deferred?
|
19
|
+
Spree.t(:user_selectable)
|
20
|
+
else
|
21
|
+
part.options_text
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
private
|
26
|
+
|
27
|
+
def set_master_unlimited_stock
|
28
|
+
if part.product.variants.any?
|
29
|
+
part.product.master.update_attribute :track_inventory, false
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module Spree
|
2
|
+
class AssignPartToBundleForm
|
3
|
+
include ActiveModel::Validations
|
4
|
+
|
5
|
+
validates :quantity, numericality: {greater_than: 0}
|
6
|
+
|
7
|
+
attr_reader :product, :part_options
|
8
|
+
|
9
|
+
def initialize(product, part_options)
|
10
|
+
@product = product
|
11
|
+
@part_options = part_options
|
12
|
+
end
|
13
|
+
|
14
|
+
def submit
|
15
|
+
if valid?
|
16
|
+
assemblies_part.update_attributes(attributes)
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
private
|
21
|
+
|
22
|
+
def attributes
|
23
|
+
part_options.reject {|k, v| k.to_sym == :variant_id}
|
24
|
+
end
|
25
|
+
|
26
|
+
def given_id?
|
27
|
+
part_options[:id].present?
|
28
|
+
end
|
29
|
+
|
30
|
+
def product_id
|
31
|
+
product.id
|
32
|
+
end
|
33
|
+
|
34
|
+
def part_id
|
35
|
+
variant.id
|
36
|
+
end
|
37
|
+
|
38
|
+
def variant
|
39
|
+
Spree::Variant.find(part_options[:variant_id])
|
40
|
+
end
|
41
|
+
|
42
|
+
def variant_selection_deferred?
|
43
|
+
part_options[:variant_selection_deferred]
|
44
|
+
end
|
45
|
+
|
46
|
+
def quantity
|
47
|
+
part_options[:count].to_i
|
48
|
+
end
|
49
|
+
|
50
|
+
def assemblies_part
|
51
|
+
@assemblies_part ||= begin
|
52
|
+
if given_id?
|
53
|
+
Spree::AssembliesPart.find(part_options[:id])
|
54
|
+
else
|
55
|
+
Spree::AssembliesPart.find_or_initialize_by(
|
56
|
+
variant_selection_deferred: variant_selection_deferred?,
|
57
|
+
assembly_id: product_id,
|
58
|
+
part_id: part_id
|
59
|
+
)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,13 @@
|
|
1
|
+
module Spree
|
2
|
+
InventoryUnit.class_eval do
|
3
|
+
def percentage_of_line_item
|
4
|
+
product = line_item.product
|
5
|
+
if product.assembly?
|
6
|
+
total_value = line_item.quantity_by_variant.map { |part, quantity| part.price * quantity }.sum
|
7
|
+
variant.price / total_value
|
8
|
+
else
|
9
|
+
1 / BigDecimal.new(line_item.quantity)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
end
|
13
|
+
end
|
@@ -0,0 +1,65 @@
|
|
1
|
+
module Spree
|
2
|
+
LineItem.class_eval do
|
3
|
+
scope :assemblies, -> { joins(product: :parts).uniq }
|
4
|
+
|
5
|
+
has_many :part_line_items, dependent: :destroy
|
6
|
+
|
7
|
+
def any_units_shipped?
|
8
|
+
inventory_units.any? { |unit| unit.shipped? }
|
9
|
+
end
|
10
|
+
|
11
|
+
# The parts that apply to this particular LineItem. Usually `product#parts`,
|
12
|
+
# but provided as a hook if you want to override and customize the parts for
|
13
|
+
# a specific LineItem.
|
14
|
+
def parts
|
15
|
+
product.parts
|
16
|
+
end
|
17
|
+
|
18
|
+
# The number of the specified variant that make up this LineItem. By
|
19
|
+
# default, calls `product#count_of`, but provided as a hook if you want to
|
20
|
+
# override and customize the parts available for a specific LineItem. Note
|
21
|
+
# that if you only customize whether a variant is included in the LineItem,
|
22
|
+
# and don't customize the quantity of that part per LineItem, you shouldn't
|
23
|
+
# need to override this method.
|
24
|
+
def count_of(variant)
|
25
|
+
product.count_of(variant)
|
26
|
+
end
|
27
|
+
|
28
|
+
def quantity_by_variant
|
29
|
+
if product.assembly?
|
30
|
+
if part_line_items.any?
|
31
|
+
quantity_with_part_line_items(quantity)
|
32
|
+
else
|
33
|
+
quantity_without_part_line_items(quantity)
|
34
|
+
end
|
35
|
+
else
|
36
|
+
{ variant => quantity }
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def update_inventory
|
43
|
+
if (changed? || target_shipment.present?) &&
|
44
|
+
order.has_checkout_step?("delivery")
|
45
|
+
if product.assembly?
|
46
|
+
OrderInventoryAssembly.new(self).verify(target_shipment)
|
47
|
+
else
|
48
|
+
OrderInventory.new(order, self).verify(target_shipment)
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
def quantity_with_part_line_items(quantity)
|
54
|
+
part_line_items.each_with_object({}) do |ap, hash|
|
55
|
+
hash[ap.variant] = ap.quantity * quantity
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def quantity_without_part_line_items(quantity)
|
60
|
+
product.assemblies_parts.each_with_object({}) do |ap, hash|
|
61
|
+
hash[ap.part] = ap.count * quantity
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
end
|
@@ -0,0 +1,35 @@
|
|
1
|
+
module Spree
|
2
|
+
OrderContents.class_eval do
|
3
|
+
def add_to_line_item_with_parts(variant, quantity, options = {})
|
4
|
+
add_to_line_item_without_parts(variant, quantity, options).
|
5
|
+
tap do |line_item|
|
6
|
+
populate_part_line_items(
|
7
|
+
line_item,
|
8
|
+
variant.product.assemblies_parts,
|
9
|
+
options["selected_variants"]
|
10
|
+
)
|
11
|
+
end
|
12
|
+
end
|
13
|
+
alias_method_chain :add_to_line_item, :parts
|
14
|
+
|
15
|
+
private
|
16
|
+
|
17
|
+
def populate_part_line_items(line_item, parts, selected_variants)
|
18
|
+
parts.each do |part|
|
19
|
+
line_item.part_line_items.create!(
|
20
|
+
line_item: line_item,
|
21
|
+
variant_id: variant_id_for(part, selected_variants),
|
22
|
+
quantity: part.count
|
23
|
+
)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
def variant_id_for(part, selected_variants)
|
28
|
+
if part.variant_selection_deferred?
|
29
|
+
selected_variants[part.id.to_s]
|
30
|
+
else
|
31
|
+
part.part.id
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,54 @@
|
|
1
|
+
module Spree
|
2
|
+
# This class has basically the same functionality of Spree core OrderInventory
|
3
|
+
# except that it takes account of bundle parts and properly creates and
|
4
|
+
# removes inventory unit for each parts of a bundle
|
5
|
+
class OrderInventoryAssembly < OrderInventory
|
6
|
+
attr_reader :product
|
7
|
+
|
8
|
+
def initialize(line_item)
|
9
|
+
@order = line_item.order
|
10
|
+
@line_item = line_item
|
11
|
+
@product = line_item.product
|
12
|
+
end
|
13
|
+
|
14
|
+
def verify(shipment = nil)
|
15
|
+
if order.completed? || shipment.present?
|
16
|
+
line_item.quantity_by_variant.each do |part, total_parts|
|
17
|
+
existing_parts = line_item.inventory_units.where(variant: part).count
|
18
|
+
|
19
|
+
self.variant = part
|
20
|
+
|
21
|
+
verify_parts(shipment, total_parts, existing_parts)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
private
|
27
|
+
|
28
|
+
def verify_parts(shipment, total_parts, existing_parts)
|
29
|
+
if existing_parts < total_parts
|
30
|
+
verifiy_add_to_shipment(shipment, total_parts, existing_parts)
|
31
|
+
elsif existing_parts > total_parts
|
32
|
+
verify_remove_from_shipment(shipment, total_parts, existing_parts)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def verifiy_add_to_shipment(shipment, total_parts, existing_parts)
|
37
|
+
shipment = determine_target_shipment unless shipment
|
38
|
+
add_to_shipment(shipment, total_parts - existing_parts)
|
39
|
+
end
|
40
|
+
|
41
|
+
def verify_remove_from_shipment(shipment, total_parts, existing_parts)
|
42
|
+
quantity = existing_parts - total_parts
|
43
|
+
|
44
|
+
if shipment.present?
|
45
|
+
remove_from_shipment(shipment, quantity)
|
46
|
+
else
|
47
|
+
order.shipments.each do |shpment|
|
48
|
+
break if quantity == 0
|
49
|
+
quantity -= remove_from_shipment(shpment, quantity)
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
end
|
@@ -0,0 +1,37 @@
|
|
1
|
+
Spree::Product.class_eval do
|
2
|
+
has_and_belongs_to_many :parts, :class_name => "Spree::Variant",
|
3
|
+
:join_table => "spree_assemblies_parts",
|
4
|
+
:foreign_key => "assembly_id", :association_foreign_key => "part_id"
|
5
|
+
|
6
|
+
has_many :assemblies_parts, :class_name => "Spree::AssembliesPart",
|
7
|
+
:foreign_key => "assembly_id"
|
8
|
+
|
9
|
+
scope :individual_saled, -> { where(individual_sale: true) }
|
10
|
+
|
11
|
+
scope :search_can_be_part, ->(query){ not_deleted.available.joins(:master)
|
12
|
+
.where(arel_table["name"].matches("%#{query}%").or(Spree::Variant.arel_table["sku"].matches("%#{query}%")))
|
13
|
+
.where(can_be_part: true)
|
14
|
+
.limit(30)
|
15
|
+
}
|
16
|
+
|
17
|
+
validate :assembly_cannot_be_part, :if => :assembly?
|
18
|
+
|
19
|
+
def assembly?
|
20
|
+
parts.present?
|
21
|
+
end
|
22
|
+
|
23
|
+
def count_of(variant)
|
24
|
+
ap = assemblies_part(variant)
|
25
|
+
# This checks persisted because the default count is 1
|
26
|
+
ap.persisted? ? ap.count : 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def assembly_cannot_be_part
|
30
|
+
errors.add(:can_be_part, Spree.t(:assembly_cannot_be_part)) if can_be_part
|
31
|
+
end
|
32
|
+
|
33
|
+
private
|
34
|
+
def assemblies_part(variant)
|
35
|
+
Spree::AssembliesPart.get(self.id, variant.id)
|
36
|
+
end
|
37
|
+
end
|