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.
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