atkhayar-dev-spree-multi-vendor 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (134) hide show
  1. checksums.yaml +7 -0
  2. data/Gemfile +10 -0
  3. data/LICENSE +26 -0
  4. data/README.md +181 -0
  5. data/Rakefile +21 -0
  6. data/app/controllers/spree/admin/vendor_settings_controller.rb +34 -0
  7. data/app/controllers/spree/admin/vendors_controller.rb +68 -0
  8. data/app/controllers/spree/api/v1/vendors_controller.rb +62 -0
  9. data/app/controllers/spree/api/v2/platform/vendors_controller.rb +32 -0
  10. data/app/controllers/spree/api/v2/storefront/vendors_controller.rb +33 -0
  11. data/app/controllers/spree_multi_vendor/spree/admin/base_controller_decorator.rb +10 -0
  12. data/app/controllers/spree_multi_vendor/spree/admin/products_controller_decorator.rb +79 -0
  13. data/app/controllers/spree_multi_vendor/spree/admin/resource_controller_decorator.rb +8 -0
  14. data/app/controllers/spree_multi_vendor/spree/admin/shipping_methods_controller_decorator.rb +7 -0
  15. data/app/controllers/spree_multi_vendor/spree/admin/stock_locations_controller_decorator.rb +7 -0
  16. data/app/controllers/spree_multi_vendor/spree/admin/users_controller_decorator.rb +13 -0
  17. data/app/controllers/spree_multi_vendor/spree/admin/variants_controller_decorator.rb +7 -0
  18. data/app/controllers/spree_multi_vendor/spree/api/base_controller_decorator.rb +17 -0
  19. data/app/controllers/spree_multi_vendor/spree/api/v1/products_controller_decorator.rb +9 -0
  20. data/app/controllers/spree_multi_vendor/spree/api/v1/users_controller_decorator.rb +13 -0
  21. data/app/controllers/spree_multi_vendor/spree/api/v2/platform/products_controller_decorator.rb +65 -0
  22. data/app/finders/spree_multi_vendor/products_finder_decorator.rb +31 -0
  23. data/app/helpers/spree/admin/vendor_helper.rb +15 -0
  24. data/app/helpers/spree/api/vendor_helper.rb +13 -0
  25. data/app/helpers/spree_multi_vendor/spree/api/api_helpers_decorator.rb +19 -0
  26. data/app/mailers/spree/vendor_mailer.rb +15 -0
  27. data/app/models/concerns/spree/vendor_concern.rb +11 -0
  28. data/app/models/spree/order_commission.rb +11 -0
  29. data/app/models/spree/vendor.rb +77 -0
  30. data/app/models/spree/vendor_ability.rb +155 -0
  31. data/app/models/spree/vendor_image.rb +18 -0
  32. data/app/models/spree/vendor_order_totals.rb +28 -0
  33. data/app/models/spree/vendor_user.rb +16 -0
  34. data/app/models/spree_multi_vendor/configuration.rb +10 -0
  35. data/app/models/spree_multi_vendor/spree/ability_decorator.rb +9 -0
  36. data/app/models/spree_multi_vendor/spree/base_decorator.rb +13 -0
  37. data/app/models/spree_multi_vendor/spree/line_item_decorator.rb +48 -0
  38. data/app/models/spree_multi_vendor/spree/order_decorator.rb +107 -0
  39. data/app/models/spree_multi_vendor/spree/shipment_decorator.rb +8 -0
  40. data/app/models/spree_multi_vendor/spree/stock/package_decorator.rb +14 -0
  41. data/app/models/spree_multi_vendor/spree/user_decorator.rb +8 -0
  42. data/app/models/spree_multi_vendor/spree/variant_decorator.rb +40 -0
  43. data/app/overrides/spree/admin/orders/_form.rb +13 -0
  44. data/app/overrides/spree/admin/orders/_line_items.rb +6 -0
  45. data/app/overrides/spree/admin/orders/_line_items_edit_form.rb +6 -0
  46. data/app/overrides/spree/admin/orders/index.rb +6 -0
  47. data/app/overrides/spree/admin/products/form.rb +29 -0
  48. data/app/overrides/spree/admin/products/new.rb +16 -0
  49. data/app/overrides/spree/admin/shared/_main_menu.rb +23 -0
  50. data/app/overrides/spree/admin/shared/_order_summary.rb +35 -0
  51. data/app/overrides/spree/admin/users/_form.rb +13 -0
  52. data/app/serializers/spree/api/v2/platform/vendor_serializer.rb +12 -0
  53. data/app/serializers/spree/v2/storefront/vendor_image_serializer.rb +11 -0
  54. data/app/serializers/spree/v2/storefront/vendor_order_totals_serializer.rb +20 -0
  55. data/app/serializers/spree/v2/storefront/vendor_serializer.rb +15 -0
  56. data/app/serializers/spree_multi_vendor/cart_serializer_decorator.rb +19 -0
  57. data/app/serializers/spree_multi_vendor/serializer_decorator.rb +23 -0
  58. data/app/services/spree/orders/generate_commissions.rb +34 -0
  59. data/app/views/spree/admin/translations/vendor.html.erb +21 -0
  60. data/app/views/spree/admin/vendor_settings/edit.html.erb +47 -0
  61. data/app/views/spree/admin/vendors/_form.html.erb +36 -0
  62. data/app/views/spree/admin/vendors/edit.html.erb +11 -0
  63. data/app/views/spree/admin/vendors/index.html.erb +83 -0
  64. data/app/views/spree/admin/vendors/new.html.erb +11 -0
  65. data/app/views/spree/api/v1/vendors/index.rabl +3 -0
  66. data/app/views/spree/api/v1/vendors/show.rabl +2 -0
  67. data/app/views/spree/vendor_mailer/vendor_notification_email.html.erb +81 -0
  68. data/app/views/spree/vendor_mailer/vendor_notification_email.text.erb +36 -0
  69. data/bin/rails +7 -0
  70. data/config/initializers/spree.rb +31 -0
  71. data/config/initializers/spree_permitted_attributes.rb +12 -0
  72. data/config/locales/en.yml +34 -0
  73. data/config/locales/es.yml +36 -0
  74. data/config/locales/fr.yml +33 -0
  75. data/config/routes.rb +29 -0
  76. data/db/default/vendors.rb +13 -0
  77. data/db/migrate/20170406102250_create_spree_vendors.rb +9 -0
  78. data/db/migrate/20170406102944_add_vendor_id_to_spree_models.rb +10 -0
  79. data/db/migrate/20170410111150_add_state_to_vendors.rb +6 -0
  80. data/db/migrate/20170412124925_add_deleted_at_to_spree_vendors.rb +6 -0
  81. data/db/migrate/20170413094447_create_spree_vendor_users.rb +11 -0
  82. data/db/migrate/20190212161426_add_slug_to_spree_vendors.rb +6 -0
  83. data/db/migrate/20190214120226_add_about_us_to_spree_vendors.rb +5 -0
  84. data/db/migrate/20190214142526_add_contact_us_to_spree_vendors.rb +5 -0
  85. data/db/migrate/20190305120337_add_commission_to_spree_vendors.rb +5 -0
  86. data/db/migrate/20190308091546_add_priority_to_vendor.rb +8 -0
  87. data/db/migrate/20190312130754_create_spree_order_commissions.rb +14 -0
  88. data/db/migrate/20190322133128_add_notification_email_to_vendors.rb +5 -0
  89. data/db/migrate/20200416030721_add_translations_to_vendor.rb +19 -0
  90. data/db/migrate/20210728120649_change_vendorized_models_vendor_id_type.rb +10 -0
  91. data/db/migrate/20210728120910_change_vendor_id_and_user_id_type_for_spree_vendor_users.rb +14 -0
  92. data/db/migrate/20210728121024_change_vendor_id_and_order_id_type_for_spree_order_commissions.rb +14 -0
  93. data/db/migrate/20221123095256_add_code_to_vendors.rb +6 -0
  94. data/lib/generators/spree_multi_vendor/install/install_generator.rb +21 -0
  95. data/lib/generators/spree_multi_vendor/mailers_preview/mailers_preview_generator.rb +22 -0
  96. data/lib/generators/spree_multi_vendor/mailers_preview/templates/mailers/previews/vendor_notification_preview.rb +5 -0
  97. data/lib/spree_multi_vendor/engine.rb +24 -0
  98. data/lib/spree_multi_vendor/factories.rb +7 -0
  99. data/lib/spree_multi_vendor/version.rb +11 -0
  100. data/lib/spree_multi_vendor.rb +16 -0
  101. data/lib/tasks/vendors.rake +8 -0
  102. data/spec/factories/spree/vendor_factory.rb +28 -0
  103. data/spec/factories/spree/vendor_image_factory.rb +5 -0
  104. data/spec/factories/spree/vendor_user_factory.rb +6 -0
  105. data/spec/features/spree/admin/orders_spec.rb +86 -0
  106. data/spec/features/spree/admin/product_spec.rb +177 -0
  107. data/spec/features/spree/admin/shipping_method_spec.rb +72 -0
  108. data/spec/features/spree/admin/stock_location_spec.rb +90 -0
  109. data/spec/features/spree/admin/vendor_settings_spec.rb +74 -0
  110. data/spec/features/spree/admin/vendor_spec.rb +125 -0
  111. data/spec/fixtures/thinking-cat.jpg +0 -0
  112. data/spec/mailers/vendor_mailer_spec.rb +45 -0
  113. data/spec/models/spree/line_item_spec.rb +32 -0
  114. data/spec/models/spree/order_commission_spec.rb +8 -0
  115. data/spec/models/spree/order_spec.rb +193 -0
  116. data/spec/models/spree/product_spec.rb +19 -0
  117. data/spec/models/spree/variant_spec.rb +104 -0
  118. data/spec/models/spree/vendor_order_totals_spec.rb +33 -0
  119. data/spec/models/spree/vendor_spec.rb +57 -0
  120. data/spec/models/spree/vendor_user_spec.rb +12 -0
  121. data/spec/requests/spree/api/v1/products_spec.rb +221 -0
  122. data/spec/requests/spree/api/v1/users_spec.rb +31 -0
  123. data/spec/requests/spree/api/v1/vendors_spec.rb +210 -0
  124. data/spec/requests/spree/api/v2/storefront/cart_spec.rb +85 -0
  125. data/spec/requests/spree/api/v2/storefront/products_spec.rb +96 -0
  126. data/spec/requests/spree/api/v2/storefront/vendors_spec.rb +118 -0
  127. data/spec/serializers/spree/v2/storefront/stock_location_serializer_spec.rb +32 -0
  128. data/spec/serializers/spree/v2/storefront/vendor_image_serializer_spec.rb +18 -0
  129. data/spec/serializers/spree/v2/storefront/vendor_order_totals_serializer_spec.rb +54 -0
  130. data/spec/serializers/spree/v2/storefront/vendor_serializer_spec.rb +34 -0
  131. data/spec/spec_helper.rb +13 -0
  132. data/spec/support/shoulda_matchers.rb +6 -0
  133. data/spree_multi_vendor.gemspec +42 -0
  134. metadata +308 -0
@@ -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
+ module VendorConcern
3
+ extend ActiveSupport::Concern
4
+
5
+ included do
6
+ belongs_to :vendor, class_name: 'Spree::Vendor', touch: true, optional: true
7
+
8
+ scope :with_vendor, ->(vendor_id) { where(vendor_id: vendor_id) }
9
+ end
10
+ end
11
+ 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,9 @@
1
+ module SpreeMultiVendor::Spree::AbilityDecorator
2
+ private
3
+
4
+ def abilities_to_register
5
+ super << Spree::VendorAbility
6
+ end
7
+ end
8
+
9
+ Spree::Ability.prepend SpreeMultiVendor::Spree::AbilityDecorator
@@ -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