atkhayar-dev-spree-multi-vendor 0.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/Gemfile +10 -0
- data/LICENSE +26 -0
- data/README.md +181 -0
- data/Rakefile +21 -0
- data/app/controllers/spree/admin/vendor_settings_controller.rb +34 -0
- data/app/controllers/spree/admin/vendors_controller.rb +68 -0
- data/app/controllers/spree/api/v1/vendors_controller.rb +62 -0
- data/app/controllers/spree/api/v2/platform/vendors_controller.rb +32 -0
- data/app/controllers/spree/api/v2/storefront/vendors_controller.rb +33 -0
- data/app/controllers/spree_multi_vendor/spree/admin/base_controller_decorator.rb +10 -0
- data/app/controllers/spree_multi_vendor/spree/admin/products_controller_decorator.rb +79 -0
- data/app/controllers/spree_multi_vendor/spree/admin/resource_controller_decorator.rb +8 -0
- data/app/controllers/spree_multi_vendor/spree/admin/shipping_methods_controller_decorator.rb +7 -0
- data/app/controllers/spree_multi_vendor/spree/admin/stock_locations_controller_decorator.rb +7 -0
- data/app/controllers/spree_multi_vendor/spree/admin/users_controller_decorator.rb +13 -0
- data/app/controllers/spree_multi_vendor/spree/admin/variants_controller_decorator.rb +7 -0
- data/app/controllers/spree_multi_vendor/spree/api/base_controller_decorator.rb +17 -0
- data/app/controllers/spree_multi_vendor/spree/api/v1/products_controller_decorator.rb +9 -0
- data/app/controllers/spree_multi_vendor/spree/api/v1/users_controller_decorator.rb +13 -0
- data/app/controllers/spree_multi_vendor/spree/api/v2/platform/products_controller_decorator.rb +65 -0
- data/app/finders/spree_multi_vendor/products_finder_decorator.rb +31 -0
- data/app/helpers/spree/admin/vendor_helper.rb +15 -0
- data/app/helpers/spree/api/vendor_helper.rb +13 -0
- data/app/helpers/spree_multi_vendor/spree/api/api_helpers_decorator.rb +19 -0
- data/app/mailers/spree/vendor_mailer.rb +15 -0
- data/app/models/concerns/spree/vendor_concern.rb +11 -0
- data/app/models/spree/order_commission.rb +11 -0
- data/app/models/spree/vendor.rb +77 -0
- data/app/models/spree/vendor_ability.rb +155 -0
- data/app/models/spree/vendor_image.rb +18 -0
- data/app/models/spree/vendor_order_totals.rb +28 -0
- data/app/models/spree/vendor_user.rb +16 -0
- data/app/models/spree_multi_vendor/configuration.rb +10 -0
- data/app/models/spree_multi_vendor/spree/ability_decorator.rb +9 -0
- data/app/models/spree_multi_vendor/spree/base_decorator.rb +13 -0
- data/app/models/spree_multi_vendor/spree/line_item_decorator.rb +48 -0
- data/app/models/spree_multi_vendor/spree/order_decorator.rb +107 -0
- data/app/models/spree_multi_vendor/spree/shipment_decorator.rb +8 -0
- data/app/models/spree_multi_vendor/spree/stock/package_decorator.rb +14 -0
- data/app/models/spree_multi_vendor/spree/user_decorator.rb +8 -0
- data/app/models/spree_multi_vendor/spree/variant_decorator.rb +40 -0
- data/app/overrides/spree/admin/orders/_form.rb +13 -0
- data/app/overrides/spree/admin/orders/_line_items.rb +6 -0
- data/app/overrides/spree/admin/orders/_line_items_edit_form.rb +6 -0
- data/app/overrides/spree/admin/orders/index.rb +6 -0
- data/app/overrides/spree/admin/products/form.rb +29 -0
- data/app/overrides/spree/admin/products/new.rb +16 -0
- data/app/overrides/spree/admin/shared/_main_menu.rb +23 -0
- data/app/overrides/spree/admin/shared/_order_summary.rb +35 -0
- data/app/overrides/spree/admin/users/_form.rb +13 -0
- data/app/serializers/spree/api/v2/platform/vendor_serializer.rb +12 -0
- data/app/serializers/spree/v2/storefront/vendor_image_serializer.rb +11 -0
- data/app/serializers/spree/v2/storefront/vendor_order_totals_serializer.rb +20 -0
- data/app/serializers/spree/v2/storefront/vendor_serializer.rb +15 -0
- data/app/serializers/spree_multi_vendor/cart_serializer_decorator.rb +19 -0
- data/app/serializers/spree_multi_vendor/serializer_decorator.rb +23 -0
- data/app/services/spree/orders/generate_commissions.rb +34 -0
- data/app/views/spree/admin/translations/vendor.html.erb +21 -0
- data/app/views/spree/admin/vendor_settings/edit.html.erb +47 -0
- data/app/views/spree/admin/vendors/_form.html.erb +36 -0
- data/app/views/spree/admin/vendors/edit.html.erb +11 -0
- data/app/views/spree/admin/vendors/index.html.erb +83 -0
- data/app/views/spree/admin/vendors/new.html.erb +11 -0
- data/app/views/spree/api/v1/vendors/index.rabl +3 -0
- data/app/views/spree/api/v1/vendors/show.rabl +2 -0
- data/app/views/spree/vendor_mailer/vendor_notification_email.html.erb +81 -0
- data/app/views/spree/vendor_mailer/vendor_notification_email.text.erb +36 -0
- data/bin/rails +7 -0
- data/config/initializers/spree.rb +31 -0
- data/config/initializers/spree_permitted_attributes.rb +12 -0
- data/config/locales/en.yml +34 -0
- data/config/locales/es.yml +36 -0
- data/config/locales/fr.yml +33 -0
- data/config/routes.rb +29 -0
- data/db/default/vendors.rb +13 -0
- data/db/migrate/20170406102250_create_spree_vendors.rb +9 -0
- data/db/migrate/20170406102944_add_vendor_id_to_spree_models.rb +10 -0
- data/db/migrate/20170410111150_add_state_to_vendors.rb +6 -0
- data/db/migrate/20170412124925_add_deleted_at_to_spree_vendors.rb +6 -0
- data/db/migrate/20170413094447_create_spree_vendor_users.rb +11 -0
- data/db/migrate/20190212161426_add_slug_to_spree_vendors.rb +6 -0
- data/db/migrate/20190214120226_add_about_us_to_spree_vendors.rb +5 -0
- data/db/migrate/20190214142526_add_contact_us_to_spree_vendors.rb +5 -0
- data/db/migrate/20190305120337_add_commission_to_spree_vendors.rb +5 -0
- data/db/migrate/20190308091546_add_priority_to_vendor.rb +8 -0
- data/db/migrate/20190312130754_create_spree_order_commissions.rb +14 -0
- data/db/migrate/20190322133128_add_notification_email_to_vendors.rb +5 -0
- data/db/migrate/20200416030721_add_translations_to_vendor.rb +19 -0
- data/db/migrate/20210728120649_change_vendorized_models_vendor_id_type.rb +10 -0
- data/db/migrate/20210728120910_change_vendor_id_and_user_id_type_for_spree_vendor_users.rb +14 -0
- data/db/migrate/20210728121024_change_vendor_id_and_order_id_type_for_spree_order_commissions.rb +14 -0
- data/db/migrate/20221123095256_add_code_to_vendors.rb +6 -0
- data/lib/generators/spree_multi_vendor/install/install_generator.rb +21 -0
- data/lib/generators/spree_multi_vendor/mailers_preview/mailers_preview_generator.rb +22 -0
- data/lib/generators/spree_multi_vendor/mailers_preview/templates/mailers/previews/vendor_notification_preview.rb +5 -0
- data/lib/spree_multi_vendor/engine.rb +24 -0
- data/lib/spree_multi_vendor/factories.rb +7 -0
- data/lib/spree_multi_vendor/version.rb +11 -0
- data/lib/spree_multi_vendor.rb +16 -0
- data/lib/tasks/vendors.rake +8 -0
- data/spec/factories/spree/vendor_factory.rb +28 -0
- data/spec/factories/spree/vendor_image_factory.rb +5 -0
- data/spec/factories/spree/vendor_user_factory.rb +6 -0
- data/spec/features/spree/admin/orders_spec.rb +86 -0
- data/spec/features/spree/admin/product_spec.rb +177 -0
- data/spec/features/spree/admin/shipping_method_spec.rb +72 -0
- data/spec/features/spree/admin/stock_location_spec.rb +90 -0
- data/spec/features/spree/admin/vendor_settings_spec.rb +74 -0
- data/spec/features/spree/admin/vendor_spec.rb +125 -0
- data/spec/fixtures/thinking-cat.jpg +0 -0
- data/spec/mailers/vendor_mailer_spec.rb +45 -0
- data/spec/models/spree/line_item_spec.rb +32 -0
- data/spec/models/spree/order_commission_spec.rb +8 -0
- data/spec/models/spree/order_spec.rb +193 -0
- data/spec/models/spree/product_spec.rb +19 -0
- data/spec/models/spree/variant_spec.rb +104 -0
- data/spec/models/spree/vendor_order_totals_spec.rb +33 -0
- data/spec/models/spree/vendor_spec.rb +57 -0
- data/spec/models/spree/vendor_user_spec.rb +12 -0
- data/spec/requests/spree/api/v1/products_spec.rb +221 -0
- data/spec/requests/spree/api/v1/users_spec.rb +31 -0
- data/spec/requests/spree/api/v1/vendors_spec.rb +210 -0
- data/spec/requests/spree/api/v2/storefront/cart_spec.rb +85 -0
- data/spec/requests/spree/api/v2/storefront/products_spec.rb +96 -0
- data/spec/requests/spree/api/v2/storefront/vendors_spec.rb +118 -0
- data/spec/serializers/spree/v2/storefront/stock_location_serializer_spec.rb +32 -0
- data/spec/serializers/spree/v2/storefront/vendor_image_serializer_spec.rb +18 -0
- data/spec/serializers/spree/v2/storefront/vendor_order_totals_serializer_spec.rb +54 -0
- data/spec/serializers/spree/v2/storefront/vendor_serializer_spec.rb +34 -0
- data/spec/spec_helper.rb +13 -0
- data/spec/support/shoulda_matchers.rb +6 -0
- data/spree_multi_vendor.gemspec +42 -0
- metadata +308 -0
data/app/controllers/spree_multi_vendor/spree/api/v2/platform/products_controller_decorator.rb
ADDED
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
module SpreeMultiVendor::Spree::Api::V2::Platform::ProductsControllerDecorator
|
|
2
|
+
def self.prepended(base)
|
|
3
|
+
base.before_action :initiate_ts_client
|
|
4
|
+
base.after_action :create_ts_product, only: [:create]
|
|
5
|
+
base.after_action :update_ts_product, only: [:update]
|
|
6
|
+
base.after_action :delete_ts_product, only: [:destroy]
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def initiate_ts_client
|
|
10
|
+
@typesense = Typesense::Client.new(
|
|
11
|
+
nodes: [
|
|
12
|
+
{
|
|
13
|
+
host: 'iraxmydlt35g8v6qp-1.a1.typesense.net',
|
|
14
|
+
port: 443,
|
|
15
|
+
protocol: 'https'
|
|
16
|
+
}
|
|
17
|
+
],
|
|
18
|
+
api_key: 'P524ViQst3aE0xlpbVY7or8bCAWbVtqv',
|
|
19
|
+
num_retries: 10,
|
|
20
|
+
healthcheck_interval_seconds: 1,
|
|
21
|
+
retry_interval_seconds: 0.01,
|
|
22
|
+
connection_timeout_seconds: 10
|
|
23
|
+
)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def create_ts_product
|
|
27
|
+
@product_vendor = Spree::Vendor.find_by(id: params[:product][:vendor_id])
|
|
28
|
+
@product = Spree::Product.find_by(slug: params[:product][:slug])
|
|
29
|
+
document = {
|
|
30
|
+
'id' => @product.id.to_s,
|
|
31
|
+
'name' => params[:product][:name].to_s,
|
|
32
|
+
'status' => @product.status,
|
|
33
|
+
'categories' => [],
|
|
34
|
+
'tag' => ['CC'],
|
|
35
|
+
'type'=> 'product',
|
|
36
|
+
'store_id' => @product_vendor.code
|
|
37
|
+
}
|
|
38
|
+
document = @typesense.collections['dev-products'].documents.create(document)
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def update_ts_product
|
|
42
|
+
product_id = params[:id]
|
|
43
|
+
@product = Spree::Product.find_by(id: product_id)
|
|
44
|
+
classifications = Spree::Classification.where(product_id: product_id)
|
|
45
|
+
categories = []
|
|
46
|
+
if !classifications.empty?
|
|
47
|
+
classifications.each do |classification|
|
|
48
|
+
taxon = Spree::Taxon.find_by(id: classification.taxon_id)
|
|
49
|
+
categories.push(taxon.name)
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
@typesense.collections['dev-products'].documents[product_id].update(
|
|
54
|
+
'name' => @product.name,
|
|
55
|
+
'categories' => categories,
|
|
56
|
+
'status' => @product.status
|
|
57
|
+
)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def delete_ts_product
|
|
61
|
+
@typesense.collections['dev-products'].documents[params[:id]].delete()
|
|
62
|
+
end
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
Spree::Api::V2::Platform::ProductsController.prepend SpreeMultiVendor::Spree::Api::V2::Platform::ProductsControllerDecorator
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
module SpreeMultiVendor
|
|
2
|
+
module ProductsFinderDecorator
|
|
3
|
+
def initialize(scope:, params:, current_currency: nil)
|
|
4
|
+
super
|
|
5
|
+
|
|
6
|
+
@vendors = params.dig(:filter, :vendor_ids)&.split(',')
|
|
7
|
+
end
|
|
8
|
+
|
|
9
|
+
def execute
|
|
10
|
+
products = by_vendors(super)
|
|
11
|
+
|
|
12
|
+
products.distinct
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
attr_reader :vendors
|
|
18
|
+
|
|
19
|
+
def vendors?
|
|
20
|
+
vendors.present?
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def by_vendors(products)
|
|
24
|
+
return products unless vendors?
|
|
25
|
+
|
|
26
|
+
products.where(vendor_id: vendors)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
::Spree::Products::Find.prepend(SpreeMultiVendor::ProductsFinderDecorator)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Admin
|
|
3
|
+
module VendorHelper
|
|
4
|
+
def current_spree_vendor
|
|
5
|
+
if current_spree_user.vendors.any? && (!current_spree_user.respond_to?(:has_spree_role?) || !current_spree_user.has_spree_role?(:admin))
|
|
6
|
+
current_spree_user.vendors.first
|
|
7
|
+
end
|
|
8
|
+
end
|
|
9
|
+
|
|
10
|
+
def vendor_state_options
|
|
11
|
+
@vendor_state_options ||= Spree::Vendor.state_machines[:state].states.collect { |s| [Spree.t("vendor_states.#{s.name}"), s.value] }
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
module Api
|
|
3
|
+
module VendorHelper
|
|
4
|
+
def current_spree_vendor
|
|
5
|
+
# puts 'current'
|
|
6
|
+
# puts current_api_user
|
|
7
|
+
# if current_api_user.vendors.any? && (!current_api_user.respond_to?(:has_spree_role?) || !current_api_user.has_spree_role?(:admin))
|
|
8
|
+
# current_api_user.vendors.first
|
|
9
|
+
# end
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
module SpreeMultiVendor
|
|
2
|
+
module Spree
|
|
3
|
+
module Api
|
|
4
|
+
module ApiHelpersDecorator
|
|
5
|
+
def self.prepended(base)
|
|
6
|
+
base::ATTRIBUTES.push(:vendor_attributes)
|
|
7
|
+
|
|
8
|
+
base.mattr_reader *base::ATTRIBUTES
|
|
9
|
+
|
|
10
|
+
base.user_attributes << :vendor_ids
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
@@vendor_attributes = [:id, :name, :slug, :state, :about_us, :contact_us]
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# ::Spree::Helpers::Spree::Api::V2.prepend(SpreeMultiVendor::Spree::Api::ApiHelpersDecorator)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class VendorMailer < ::Spree::BaseMailer
|
|
3
|
+
def vendor_notification_email(order_id, vendor_id)
|
|
4
|
+
@vendor = Spree::Vendor.find(vendor_id)
|
|
5
|
+
return unless @vendor.notification_email.present?
|
|
6
|
+
|
|
7
|
+
@order = Spree::Order.find(order_id)
|
|
8
|
+
@line_items = @order.line_items.for_vendor(@vendor)
|
|
9
|
+
@subtotal = @order.vendor_subtotal(@vendor)
|
|
10
|
+
@total = @order.vendor_total(@vendor)
|
|
11
|
+
subject = "#{Spree::Store.current.name} #{Spree.t('order_mailer.vendor_notification_email.subject')} ##{@order.number}"
|
|
12
|
+
mail(to: @vendor.notification_email, from: from_address, subject: subject)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class OrderCommission < Base
|
|
3
|
+
belongs_to :order, required: Spree.version.to_f >= 3.5
|
|
4
|
+
belongs_to :vendor, required: Spree.version.to_f >= 3.5
|
|
5
|
+
|
|
6
|
+
validates :order, :vendor, presence: true
|
|
7
|
+
validates :vendor_id, uniqueness: { scope: :order_id }
|
|
8
|
+
|
|
9
|
+
scope :for_vendor, ->(vendor) { where(vendor_id: vendor.id) }
|
|
10
|
+
end
|
|
11
|
+
end
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class Vendor < Spree::Base
|
|
3
|
+
extend FriendlyId
|
|
4
|
+
|
|
5
|
+
acts_as_paranoid
|
|
6
|
+
acts_as_list column: :priority
|
|
7
|
+
friendly_id :slug, use: %i[slugged history]
|
|
8
|
+
validates :name,
|
|
9
|
+
presence: true,
|
|
10
|
+
uniqueness: { case_sensitive: false }
|
|
11
|
+
|
|
12
|
+
validates :slug, uniqueness: true
|
|
13
|
+
# validates :slug, uniqueness: { scope: :store_id, allow_nil: true, case_sensitive: true }
|
|
14
|
+
validates_associated :image if Spree.version.to_f >= 3.6
|
|
15
|
+
|
|
16
|
+
validates :notification_email, email: true, allow_blank: true
|
|
17
|
+
|
|
18
|
+
with_options dependent: :destroy do
|
|
19
|
+
has_one :image, as: :viewable, dependent: :destroy, class_name: 'Spree::VendorImage'
|
|
20
|
+
has_many :commissions, class_name: 'Spree::OrderCommission'
|
|
21
|
+
has_many :vendor_users
|
|
22
|
+
|
|
23
|
+
SpreeMultiVendor::Config[:vendorized_models].uniq.compact.each do |model|
|
|
24
|
+
has_many model.pluralize.to_sym
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
has_many :users, through: :vendor_users, class_name: Spree.user_class.to_s
|
|
29
|
+
|
|
30
|
+
after_create :create_stock_location
|
|
31
|
+
after_update :update_stock_location_names
|
|
32
|
+
|
|
33
|
+
state_machine :state, initial: :pending do
|
|
34
|
+
event :activate do
|
|
35
|
+
transition to: :active
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
event :block do
|
|
39
|
+
transition to: :blocked
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
scope :active, -> { where(state: 'active') }
|
|
44
|
+
|
|
45
|
+
self.whitelisted_ransackable_attributes = %w[name state]
|
|
46
|
+
|
|
47
|
+
def update_notification_email(email)
|
|
48
|
+
update(notification_email: email)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
# Spree Globalize support
|
|
52
|
+
# https://github.com/spree-contrib/spree_multi_vendor/issues/104
|
|
53
|
+
if defined?(SpreeGlobalize)
|
|
54
|
+
attr_accessor :translations_attributes
|
|
55
|
+
translates :name,
|
|
56
|
+
:about_us,
|
|
57
|
+
:contact_us,
|
|
58
|
+
:slug, fallbacks_for_empty_translations: true
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
private
|
|
62
|
+
|
|
63
|
+
def create_stock_location
|
|
64
|
+
stock_locations.where(name: name, country: Spree::Store.default.default_country).first_or_create!
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def should_generate_new_friendly_id?
|
|
68
|
+
slug.blank? || name_changed?
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def update_stock_location_names
|
|
72
|
+
if (Spree.version.to_f < 3.5 && self.name_changed?) || (Spree.version.to_f >= 3.5 && saved_changes&.include?(:name))
|
|
73
|
+
stock_locations.update_all({ name: name })
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
@@ -0,0 +1,155 @@
|
|
|
1
|
+
class Spree::VendorAbility
|
|
2
|
+
include CanCan::Ability
|
|
3
|
+
|
|
4
|
+
def initialize(user)
|
|
5
|
+
@vendor_ids = user.vendors.active.ids
|
|
6
|
+
|
|
7
|
+
if @vendor_ids.any?
|
|
8
|
+
apply_classifications_permissions
|
|
9
|
+
apply_order_permissions
|
|
10
|
+
apply_image_permissions
|
|
11
|
+
apply_option_type_permissions
|
|
12
|
+
apply_price_permissions
|
|
13
|
+
apply_product_option_type_permissions
|
|
14
|
+
apply_product_permissions
|
|
15
|
+
apply_product_properties_permissions
|
|
16
|
+
apply_properties_permissions
|
|
17
|
+
apply_prototypes_permissions
|
|
18
|
+
apply_shipment_permissions
|
|
19
|
+
apply_shipping_methods_permissions
|
|
20
|
+
apply_stock_permissions
|
|
21
|
+
apply_stock_item_permissions
|
|
22
|
+
apply_stock_location_permissions
|
|
23
|
+
apply_stock_movement_permissions
|
|
24
|
+
apply_variant_permissions
|
|
25
|
+
apply_vendor_permissions
|
|
26
|
+
apply_vendor_settings_permissions
|
|
27
|
+
apply_state_changes_permissions
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
protected
|
|
32
|
+
|
|
33
|
+
def apply_classifications_permissions
|
|
34
|
+
can :manage, Spree::Classification, product: { vendor_id: @vendor_ids }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def apply_order_permissions
|
|
38
|
+
cannot :create, Spree::Order
|
|
39
|
+
|
|
40
|
+
order_scope = if ::Spree::Order.reflect_on_association(:vendor)
|
|
41
|
+
{ vendor_id: @vendor_ids }
|
|
42
|
+
elsif ::Spree::LineItem.reflect_on_association(:vendor)
|
|
43
|
+
{ line_items: { vendor_id: @vendor_ids } }
|
|
44
|
+
elsif ::Spree::Product.reflect_on_association(:vendor)
|
|
45
|
+
{ line_items: { product: { vendor_id: @vendor_ids } } }
|
|
46
|
+
elsif ::Spree::Variant.reflect_on_association(:vendor)
|
|
47
|
+
{ line_items: { variant: { vendor_id: @vendor_ids } } }
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
if order_scope.present?
|
|
51
|
+
can %i[admin show index edit update cart], Spree::Order, order_scope.merge(state: 'complete')
|
|
52
|
+
else
|
|
53
|
+
cannot_display_model(Spree::Order)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def apply_image_permissions
|
|
58
|
+
can :create, Spree::Image
|
|
59
|
+
|
|
60
|
+
can [:manage, :modify], Spree::Image, ['viewable_type = ?', 'Spree::Variant'] do |image|
|
|
61
|
+
vendor_id = image.viewable.try(:vendor_id)
|
|
62
|
+
vendor_id.present? && @vendor_ids.include?(vendor_id)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def apply_option_type_permissions
|
|
67
|
+
can :display, Spree::OptionType
|
|
68
|
+
can :display, Spree::OptionValue
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def apply_price_permissions
|
|
72
|
+
can :modify, Spree::Price, variant: { vendor_id: @vendor_ids }
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def apply_product_option_type_permissions
|
|
76
|
+
can :manage, Spree::ProductOptionType, product: { vendor_id: @vendor_ids }
|
|
77
|
+
can :create, Spree::ProductOptionType
|
|
78
|
+
can :manage, Spree::OptionValueVariant, variant: { vendor_id: @vendor_ids }
|
|
79
|
+
can :create, Spree::OptionValueVariant
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def apply_product_permissions
|
|
83
|
+
cannot_display_model(Spree::Product)
|
|
84
|
+
|
|
85
|
+
return unless Spree::Product.reflect_on_association(:vendor)
|
|
86
|
+
|
|
87
|
+
can :manage, Spree::Product, vendor_id: @vendor_ids
|
|
88
|
+
can :create, Spree::Product
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def apply_properties_permissions
|
|
92
|
+
can :display, Spree::Property
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def apply_product_properties_permissions
|
|
96
|
+
can :manage, Spree::ProductProperty, product: { vendor_id: @vendor_ids }
|
|
97
|
+
can :create, Spree::ProductProperty
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def apply_prototypes_permissions
|
|
101
|
+
can [:read, :admin], Spree::Prototype
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def apply_shipment_permissions
|
|
105
|
+
can :update, Spree::Shipment, inventory_units: { line_item: { product: { vendor_id: @vendor_ids } } }
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def apply_shipping_methods_permissions
|
|
109
|
+
can :manage, Spree::ShippingMethod, vendor_id: @vendor_ids
|
|
110
|
+
can :create, Spree::ShippingMethod
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def apply_stock_permissions
|
|
114
|
+
can :admin, Spree::Stock
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def apply_stock_item_permissions
|
|
118
|
+
can [:admin, :modify, :read], Spree::StockItem, stock_location: { vendor_id: @vendor_ids }
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
def apply_stock_location_permissions
|
|
122
|
+
can :manage, Spree::StockLocation, vendor_id: @vendor_ids
|
|
123
|
+
can :create, Spree::StockLocation
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def apply_stock_movement_permissions
|
|
127
|
+
can :create, Spree::StockMovement
|
|
128
|
+
can :manage, Spree::StockMovement, stock_item: { stock_location: { vendor_id: @vendor_ids } }
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def apply_variant_permissions
|
|
132
|
+
cannot_display_model(Spree::Variant)
|
|
133
|
+
can :manage, Spree::Variant, vendor_id: @vendor_ids
|
|
134
|
+
can :create, Spree::Variant
|
|
135
|
+
end
|
|
136
|
+
|
|
137
|
+
def apply_vendor_permissions
|
|
138
|
+
can :manage, Spree::Vendor, id: @vendor_ids
|
|
139
|
+
cannot :create, Spree::Vendor
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def apply_vendor_settings_permissions
|
|
143
|
+
can :manage, :vendor_settings
|
|
144
|
+
end
|
|
145
|
+
|
|
146
|
+
def apply_state_changes_permissions
|
|
147
|
+
can [:admin, :index], Spree::StateChange do |state_change|
|
|
148
|
+
(@vendor_ids & state_change.user.vendor_ids).any?
|
|
149
|
+
end
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def cannot_display_model(model)
|
|
153
|
+
Spree.version.to_f < 4.0 ? (cannot :display, model) : (cannot :read, model)
|
|
154
|
+
end
|
|
155
|
+
end
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class VendorImage < Asset
|
|
3
|
+
include Spree::Image::Configuration::ActiveStorage
|
|
4
|
+
include Rails.application.routes.url_helpers
|
|
5
|
+
|
|
6
|
+
def styles
|
|
7
|
+
self.class.styles.map do |_, size|
|
|
8
|
+
width, height = size[/(\d+)x(\d+)/].split('x')
|
|
9
|
+
|
|
10
|
+
{
|
|
11
|
+
url: polymorphic_path(attachment.variant(resize: size), only_path: true),
|
|
12
|
+
width: width,
|
|
13
|
+
height: height
|
|
14
|
+
}
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class VendorOrderTotals
|
|
3
|
+
include ActiveModel::Model
|
|
4
|
+
|
|
5
|
+
METHOD_NAMES = %w[
|
|
6
|
+
total ship_total subtotal included_tax_total additional_tax_total promo_total
|
|
7
|
+
pre_tax_item_amount pre_tax_ship_amount pre_tax_total
|
|
8
|
+
].freeze
|
|
9
|
+
|
|
10
|
+
attr_accessor :vendor, :order
|
|
11
|
+
|
|
12
|
+
delegate :id, :name, to: :vendor
|
|
13
|
+
|
|
14
|
+
METHOD_NAMES.each do |method_name|
|
|
15
|
+
define_method(method_name) do
|
|
16
|
+
order.send("vendor_#{method_name}", vendor)
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
define_method("display_#{method_name}") do
|
|
20
|
+
order.send("display_vendor_#{method_name}", vendor)
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def item_count
|
|
25
|
+
order.vendor_item_count(vendor)
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
module Spree
|
|
2
|
+
class VendorUser < Spree::Base
|
|
3
|
+
belongs_to :vendor, class_name: 'Spree::Vendor', required: Spree.version.to_f >= 3.5
|
|
4
|
+
belongs_to :user, class_name: Spree.user_class.name, required: Spree.version.to_f >= 3.5, touch: true
|
|
5
|
+
|
|
6
|
+
validates :vendor_id, uniqueness: { scope: :user_id }
|
|
7
|
+
|
|
8
|
+
before_save :set_vendor_notification_email
|
|
9
|
+
|
|
10
|
+
def set_vendor_notification_email
|
|
11
|
+
return if vendor&.notification_email.present? || !vendor&.users&.empty?
|
|
12
|
+
|
|
13
|
+
vendor.update_notification_email(user.email)
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
module SpreeMultiVendor
|
|
2
|
+
class Configuration < ::Spree::Preferences::Configuration
|
|
3
|
+
DEFAULT_VENDORIZED_MODELS ||= %w[product variant stock_location shipping_method].freeze
|
|
4
|
+
|
|
5
|
+
# Some example preferences are shown below, for more information visit:
|
|
6
|
+
# https://guides.spreecommerce.org/developer/core/preferences.html
|
|
7
|
+
|
|
8
|
+
preference :vendorized_models, :array, default: DEFAULT_VENDORIZED_MODELS
|
|
9
|
+
end
|
|
10
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
module SpreeMultiVendor::Spree::BaseDecorator
|
|
2
|
+
SpreeMultiVendor.vendorized_models.each do |model|
|
|
3
|
+
model.include Spree::VendorConcern
|
|
4
|
+
|
|
5
|
+
if model.method_defined?(:whitelisted_ransackable_associations)
|
|
6
|
+
if model.whitelisted_ransackable_associations
|
|
7
|
+
model.whitelisted_ransackable_associations += %w[vendor]
|
|
8
|
+
else
|
|
9
|
+
model.whitelisted_ransackable_associations = %w[vendor]
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
13
|
+
end
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
module SpreeMultiVendor::Spree::LineItemDecorator
|
|
2
|
+
def self.prepended(base)
|
|
3
|
+
base.scope :for_vendor, ->(vendor) do
|
|
4
|
+
if ::Spree::LineItem.reflect_on_association(:vendor)
|
|
5
|
+
where(vendor_id: vendor.id)
|
|
6
|
+
elsif ::Spree::Product.reflect_on_association(:vendor)
|
|
7
|
+
joins(variant: :product).where('spree_products.vendor_id' => vendor.id)
|
|
8
|
+
elsif ::Spree::Variant.reflect_on_association(:vendor)
|
|
9
|
+
joins(:variant).where('spree_variants.vendor_id' => vendor.id)
|
|
10
|
+
else
|
|
11
|
+
none
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
base.scope :not_for_vendor, ->(vendor) do
|
|
15
|
+
if ::Spree::Product.reflect_on_association(:vendor)
|
|
16
|
+
where.not(vendor_id: vendor.id)
|
|
17
|
+
elsif ::Spree::Product.reflect_on_association(:vendor)
|
|
18
|
+
joins(variant: :product).where.not('spree_products.vendor_id' => vendor.id)
|
|
19
|
+
elsif ::Spree::Variant.reflect_on_association(:vendor)
|
|
20
|
+
joins(:variant).where.not('spree_variants.vendor_id' => vendor.id)
|
|
21
|
+
else
|
|
22
|
+
none
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def vendor
|
|
28
|
+
@vendor ||= if self.class.reflect_on_association(:vendor) && self[:vendor_id].present?
|
|
29
|
+
::Spree::Vendor.unscoped.find(self[:vendor_id])
|
|
30
|
+
elsif product.class.reflect_on_association(:vendor) && product.vendor_id.present?
|
|
31
|
+
product.vendor
|
|
32
|
+
elsif variant.class.reflect_on_association(:vendor) && variant.vendor_id.present?
|
|
33
|
+
variant.vendor
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def vendor_id
|
|
38
|
+
@vendor_id ||= if self.class.reflect_on_association(:vendor) && self[:vendor_id].present?
|
|
39
|
+
self[:vendor_id]
|
|
40
|
+
elsif product.class.reflect_on_association(:vendor) && product.vendor_id.present?
|
|
41
|
+
product.vendor_id
|
|
42
|
+
elsif variant.class.reflect_on_association(:vendor) && variant.vendor_id.present?
|
|
43
|
+
variant.vendor_id
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
Spree::LineItem.prepend SpreeMultiVendor::Spree::LineItemDecorator
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
module SpreeMultiVendor::Spree::OrderDecorator
|
|
2
|
+
def self.prepended(base)
|
|
3
|
+
base.has_many :commissions, class_name: 'Spree::OrderCommission'
|
|
4
|
+
base.state_machine.after_transition to: :complete, do: :generate_order_commissions
|
|
5
|
+
base.state_machine.after_transition to: :complete, do: :send_notification_mails_to_vendors
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
def generate_order_commissions
|
|
9
|
+
Spree::Orders::GenerateCommissions.call(order: self)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def vendor_line_items(vendor)
|
|
13
|
+
line_items.for_vendor(vendor)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def vendor_shipments(vendor)
|
|
17
|
+
shipments.for_vendor(vendor)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def vendor_ship_total(vendor)
|
|
21
|
+
vendor_shipments(vendor).sum(&:final_price)
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def vendor_subtotal(vendor)
|
|
25
|
+
vendor_pre_tax_item_amount(vendor) +
|
|
26
|
+
vendor_pre_tax_ship_amount(vendor) +
|
|
27
|
+
vendor_additional_tax_total(vendor)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def vendor_promo_total(vendor)
|
|
31
|
+
vendor_line_items(vendor).sum(:promo_total) + vendor_shipments(vendor).sum(:promo_total)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def vendor_additional_tax_total(vendor)
|
|
35
|
+
vendor_line_items(vendor).sum(:additional_tax_total) + vendor_shipments(vendor).sum(:additional_tax_total)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def vendor_included_tax_total(vendor)
|
|
39
|
+
vendor_line_items(vendor).sum(:included_tax_total) + vendor_shipments(vendor).sum(:included_tax_total)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def vendor_pre_tax_item_amount(vendor)
|
|
43
|
+
vendor_line_items(vendor).sum(:pre_tax_amount)
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def vendor_pre_tax_ship_amount(vendor)
|
|
47
|
+
vendor_shipments(vendor).sum(:pre_tax_amount)
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
def vendor_pre_tax_total(vendor)
|
|
51
|
+
vendor_line_items(vendor).sum(:pre_tax_amount) + vendor_shipments(vendor).sum(:pre_tax_amount)
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
def vendor_item_count(vendor)
|
|
55
|
+
vendor_line_items(vendor).sum(:quantity)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def vendor_total(vendor)
|
|
59
|
+
vendor_line_items(vendor).sum(&:total) + vendor_ship_total(vendor)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def display_order_commission
|
|
63
|
+
Spree::Money.new(commissions.sum(:amount), { currency: currency })
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def vendor_commission(vendor)
|
|
67
|
+
commissions.for_vendor(vendor).sum(:amount)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# money methods
|
|
71
|
+
METHOD_NAMES ||= %w[
|
|
72
|
+
total ship_total subtotal included_tax_total additional_tax_total promo_total
|
|
73
|
+
pre_tax_item_amount pre_tax_ship_amount pre_tax_total commission
|
|
74
|
+
].freeze
|
|
75
|
+
|
|
76
|
+
METHOD_NAMES.each do |method_name|
|
|
77
|
+
define_method("display_vendor_#{method_name}") do |vendor|
|
|
78
|
+
Spree::Money.new(send("vendor_#{method_name}", vendor), { currency: currency })
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def send_notification_mails_to_vendors
|
|
83
|
+
vendor_ids.each do |vendor_id|
|
|
84
|
+
Spree::VendorMailer.vendor_notification_email(id, vendor_id).deliver_later
|
|
85
|
+
end
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# we're leaving this on purpose so it can be easily modified to fit desired scenario
|
|
89
|
+
# eg. scenario A - vendorized products, scenario B - vendorized variants of the same product
|
|
90
|
+
def vendor_ids
|
|
91
|
+
@vendor_ids ||= line_items.map(&:vendor_id).uniq.compact
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def vendor_list
|
|
95
|
+
@vendor_list ||= line_items.map(&:vendor).uniq.compact
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def vendor_totals
|
|
99
|
+
return if vendor_list.none?
|
|
100
|
+
|
|
101
|
+
vendor_list.map do |vendor|
|
|
102
|
+
Spree::VendorOrderTotals.new(vendor: vendor, order: self)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
Spree::Order.prepend SpreeMultiVendor::Spree::OrderDecorator unless Spree::Order.included_modules.include?(SpreeMultiVendor::Spree::OrderDecorator)
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
module SpreeMultiVendor::Spree::ShipmentDecorator
|
|
2
|
+
def self.prepended(base)
|
|
3
|
+
base.scope :for_vendor, ->(vendor) { joins(stock_location: :vendor).where('spree_stock_locations.vendor_id' => vendor.id) }
|
|
4
|
+
base.scope :not_for_vendor, ->(vendor) { joins(stock_location: :vendor).where.not('spree_stock_locations.vendor_id' => vendor.id) }
|
|
5
|
+
end
|
|
6
|
+
end
|
|
7
|
+
|
|
8
|
+
Spree::Shipment.prepend SpreeMultiVendor::Spree::ShipmentDecorator
|