spree_marketplace 3.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (55) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.rspec +1 -0
  4. data/.travis.yml +13 -0
  5. data/Gemfile +14 -0
  6. data/LICENSE +21 -0
  7. data/README.md +87 -0
  8. data/Rakefile +15 -0
  9. data/Versionfile +12 -0
  10. data/app/assets/javascripts/spree/backend/spree_marketplace.js.coffee +24 -0
  11. data/app/assets/javascripts/spree/frontend/spree_marketplace.js +2 -0
  12. data/app/assets/javascripts/spree/frontend/supplier_address.js.coffee +56 -0
  13. data/app/assets/stylesheets/spree/backend/spree_marketplace.css +3 -0
  14. data/app/assets/stylesheets/spree/frontend/spree_marketplace.css +10 -0
  15. data/app/controllers/spree/admin/marketplace_settings_controller.rb +19 -0
  16. data/app/controllers/spree/admin/supplier_bank_accounts_controller.rb +24 -0
  17. data/app/controllers/spree/suppliers_controller.rb +67 -0
  18. data/app/models/spree/marketplace_ability.rb +14 -0
  19. data/app/models/spree/marketplace_configuration.rb +27 -0
  20. data/app/models/spree/supplier_ability.rb +76 -0
  21. data/app/models/spree/supplier_bank_account.rb +15 -0
  22. data/app/models/spree/supplier_decorator.rb +55 -0
  23. data/app/overrides/spree/admin/shared/_configuration_menu/add_marketplace_settings.html.erb.deface +3 -0
  24. data/app/overrides/spree/admin/suppliers/edit/add_bank_accounts.html.erb.deface +14 -0
  25. data/app/overrides/spree/layouts/admin/add_stripe_js_to_head.html.erb.deface +6 -0
  26. data/app/overrides/spree/users/show/add_supplier_info.html.erb.deface +17 -0
  27. data/app/views/spree/admin/marketplace_settings/edit.html.erb +23 -0
  28. data/app/views/spree/admin/supplier_bank_accounts/new.html.erb +28 -0
  29. data/app/views/spree/suppliers/new.html.erb +29 -0
  30. data/config/locales/en.yml +14 -0
  31. data/config/locales/es.yml +30 -0
  32. data/config/routes.rb +9 -0
  33. data/db/migrate/20130424201333_create_supplier_bank_accounts.rb +12 -0
  34. data/db/migrate/20131209022116_convert_to_stripe.rb +6 -0
  35. data/lib/generators/spree_marketplace/install/install_generator.rb +31 -0
  36. data/lib/spree_marketplace.rb +4 -0
  37. data/lib/spree_marketplace/engine.rb +47 -0
  38. data/lib/spree_marketplace/factories.rb +29 -0
  39. data/script/rails +7 -0
  40. data/spec/features/admin/bank_accounts_spec.rb +50 -0
  41. data/spec/features/admin/products_spec.rb +225 -0
  42. data/spec/features/admin/properties_spec.rb +91 -0
  43. data/spec/features/admin/relations_spec.rb +31 -0
  44. data/spec/features/admin/return_authorizations_spec.rb +7 -0
  45. data/spec/features/admin/settings_spec.rb +29 -0
  46. data/spec/features/admin/suppliers_controller_spec.rb +43 -0
  47. data/spec/features/supplier_signup_spec.rb +134 -0
  48. data/spec/models/spree/marketplace_ability_spec.rb +32 -0
  49. data/spec/models/spree/supplier_ability_spec.rb +337 -0
  50. data/spec/models/spree/supplier_bank_account_spec.rb +13 -0
  51. data/spec/models/spree/supplier_spec.rb +7 -0
  52. data/spec/spec_helper.rb +85 -0
  53. data/spec/support/integration_helpers.rb +15 -0
  54. data/spree_marketplace.gemspec +90 -0
  55. metadata +391 -0
@@ -0,0 +1,76 @@
1
+ module Spree
2
+ class SupplierAbility
3
+ include CanCan::Ability
4
+
5
+ def initialize(user)
6
+ user ||= Spree.user_class.new
7
+
8
+ if user.supplier
9
+ if SpreeMarketplace::Engine.spree_digital_available?
10
+ # can [:admin, :manage], Spree::Digital, variant: { supplier_ids: user.supplier_id }
11
+ can [:admin, :manage], Spree::Digital do |digital|
12
+ digital.variant.supplier_ids.include?(user.supplier_id)
13
+ end
14
+ can :create, Spree::Digital
15
+ end
16
+ can [:admin, :manage], Spree::Image do |image|
17
+ image.viewable.product.supplier_ids.include?(user.supplier_id)
18
+ end
19
+ can :create, Spree::Image
20
+ if SpreeMarketplace::Engine.spree_group_price_available?
21
+ # can [:admin, :manage], Spree::GroupPrice, variant: { supplier_ids: user.supplier_id }
22
+ can [:admin, :manage], Spree::GroupPrice do |price|
23
+ price.variant.supplier_ids.include?(user.supplier_id)
24
+ end
25
+ end
26
+ if SpreeMarketplace::Engine.spree_related_products_available?
27
+ # can [:admin, :manage], Spree::Relation, relatable: { supplier_ids: user.supplier_id }
28
+ can [:admin, :manage], Spree::Relation do |relation|
29
+ relation.relatable.supplier_ids.include?(user.supplier_id)
30
+ end
31
+ end
32
+ # TODO: Want this to be inline like:
33
+ # can [:admin, :manage, :stock], Spree::Product, suppliers: { id: user.supplier_id }
34
+ can [:admin, :manage, :stock], Spree::Product do |product|
35
+ product.supplier_ids.include?(user.supplier_id)
36
+ end
37
+ can [:admin, :create, :index], Spree::Product
38
+ # can [:admin, :manage], Spree::ProductProperty, product: { supplier_ids: user.supplier_id }
39
+ can [:admin, :manage, :stock], Spree::ProductProperty do |property|
40
+ property.product.supplier_ids.include?(user.supplier_id)
41
+ end
42
+ can [:admin, :index, :read], Spree::Property
43
+ can [:admin, :read], Spree::Prototype
44
+ can [:admin, :manage, :read, :ready, :ship], Spree::Shipment, order: { state: 'complete' }, stock_location: { supplier_id: user.supplier_id }
45
+ can [:admin, :create, :update], :stock_items
46
+ can [:admin, :manage], Spree::StockItem, stock_location_id: user.supplier.stock_locations.pluck(:id)
47
+ can [:admin, :manage], Spree::StockLocation, supplier_id: user.supplier_id
48
+ can :create, Spree::StockLocation
49
+ can [:admin, :manage], Spree::StockMovement, stock_item: { stock_location_id: user.supplier.stock_locations.pluck(:id) }
50
+ can :create, Spree::StockMovement
51
+ can [:admin, :update], Spree::Supplier, id: user.supplier_id
52
+ # TODO: Want this to be inline like:
53
+ # can [:admin, :manage], Spree::Variant, supplier_ids: user.supplier_id
54
+ can [:admin, :create, :index], Spree::Variant
55
+ can [:admin, :manage], Spree::Variant do |variant|
56
+ variant.supplier_ids.include?(user.supplier_id)
57
+ end
58
+ end
59
+
60
+ if SpreeMarketplace::Config[:allow_signup]
61
+ can :create, Spree::Supplier
62
+ end
63
+
64
+ if SpreeMarketplace::Engine.ckeditor_available?
65
+ can :access, :ckeditor
66
+
67
+ can :create, Ckeditor::AttachmentFile
68
+ can [:read, :index, :destroy], Ckeditor::AttachmentFile, supplier_id: user.supplier_id
69
+
70
+ can :create, Ckeditor::Picture
71
+ can [:read, :index, :destroy], Ckeditor::Picture, supplier_id: user.supplier_id
72
+ end
73
+ end
74
+
75
+ end
76
+ end
@@ -0,0 +1,15 @@
1
+ module Spree
2
+ class SupplierBankAccount < ActiveRecord::Base
3
+
4
+ attr_accessor :account_number, :routing_number
5
+
6
+ belongs_to :supplier
7
+
8
+ validates :country_iso, presence: true
9
+ validates :masked_number, presence: true
10
+ validates :name, presence: true
11
+ validates :supplier, presence: true
12
+ validates :token, presence: true, uniqueness: true
13
+
14
+ end
15
+ end
@@ -0,0 +1,55 @@
1
+ Spree::Supplier.class_eval do
2
+
3
+ attr_accessor :first_name, :last_name, :merchant_type
4
+
5
+ has_many :bank_accounts, class_name: 'Spree::SupplierBankAccount'
6
+ has_attached_file :profile_picture, dependent: :destroy, :styles => {:medium => "300x300>", :thumb => "100x100>"}, :path => ":rails_root/public/assets/profile_pictures/:style/:filename", :url => "/assets/profile_pictures/:style/:filename"
7
+ validates_attachment :profile_picture, :content_type => { :content_type => /\Aimage\/.*\Z/ }, :size => { :in => 0..500.kilobytes }
8
+
9
+ validates :tax_id, length: { is: 9, allow_blank: true }
10
+
11
+ before_create :assign_name
12
+
13
+ private
14
+
15
+ def assign_name
16
+ self.address = Spree::Address.default unless self.address.present?
17
+ self.address.first_name = self.first_name unless self.address.first_name.present?
18
+ self.address.last_name = self.last_name unless self.address.last_name.present?
19
+ end
20
+
21
+ def stripe_recipient_setup
22
+ return if self.tax_id.blank? and self.address.blank?
23
+
24
+ recipient = Stripe::Recipient.create(
25
+ :name => (self.merchant_type == 'business' ? self.name : "#{self.address.first_name} #{self.address.last_name}"),
26
+ :type => (self.merchant_type == 'business' ? 'corporation' : "individual"),
27
+ :email => self.email,
28
+ :bank_account => self.bank_accounts.first.try(:token)
29
+ )
30
+
31
+ if new_record?
32
+ self.token = recipient.id
33
+ else
34
+ self.update_column :token, recipient.id
35
+ end
36
+ end
37
+
38
+ def stripe_recipient_update
39
+ unless new_record? or !changed?
40
+ if token.present?
41
+ rp = Stripe::Recipient.retrieve(token)
42
+ rp.name = name
43
+ rp.email = email
44
+ if tax_id.present?
45
+ rp.tax_id = tax_id
46
+ end
47
+ rp.bank_account = bank_accounts.first.token if bank_accounts.first
48
+ rp.save
49
+ else
50
+ stripe_recipient_setup
51
+ end
52
+ end
53
+ end
54
+
55
+ end
@@ -0,0 +1,3 @@
1
+ <!-- insert_bottom "[data-hook='admin_configurations_sidebar_menu'], #admin_configurations_sidebar_menu[data-hook]" -->
2
+
3
+ <%= configurations_sidebar_menu_item Spree.t(:marketplace_settings), spree.edit_admin_marketplace_settings_path %>
@@ -0,0 +1,14 @@
1
+ <!-- insert_bottom "[data-hook='supplier_right_column_wrapper']" -->
2
+
3
+ <fieldset class="no-border-bottom supplier-banks">
4
+ <legend><%= I18n.t('.bank_accounts') %></legend>
5
+ <ol>
6
+ <% @supplier.bank_accounts.each do |bank_account| %>
7
+ <li>
8
+ <%= bank_account.name %> - <%= bank_account.masked_number %>
9
+ <!-- TODO Add a remove link? -->
10
+ </li>
11
+ <% end %>
12
+ </ol>
13
+ <%= button_link_to t('.add_bank_account'), spree.new_admin_supplier_bank_account_path(@supplier) %>
14
+ </fieldset>
@@ -0,0 +1,6 @@
1
+ <!-- insert_bottom 'head' -->
2
+
3
+ <script type="text/javascript" src="https://js.stripe.com/v2/"></script>
4
+ <script type="text/javascript">
5
+ Stripe.setPublishableKey("<%= SpreeMarketplace::Config[:stripe_publishable_key] %>");
6
+ </script>
@@ -0,0 +1,17 @@
1
+ <!-- insert_bottom '#user-info' -->
2
+
3
+ <% if SpreeMarketplace::Config[:allow_signup] or @user.supplier? %>
4
+ <br/>
5
+ <dt class='supplier-info'><%= Spree.t(:supplier_info) %></dt>
6
+ <dd class='supplier-info'>
7
+ <% if @user.supplier? %>
8
+ <%= @user.supplier.name %>
9
+ (<%= link_to Spree.t(:edit), spree.edit_admin_supplier_path(@user.supplier) %>)
10
+ <%= link_to @user.supplier.url, @user.supplier.url, target: '_blank' if @user.supplier.url.present? %>
11
+ <br/>
12
+ Shipments: <b><%= @user.supplier.shipments.size %></b> (<%= link_to Spree.t(:manage), spree.admin_shipments_path %>)
13
+ <% else %>
14
+ <%= link_to Spree.t(:signup_to_become_a_supplier), spree.new_supplier_path %>
15
+ <% end %>
16
+ </dd>
17
+ <% end %>
@@ -0,0 +1,23 @@
1
+ <%= render partial: 'spree/admin/shared/configuration_menu' %>
2
+
3
+ <% content_for :page_title do %>
4
+ <%= Spree.t(:marketplace_settings) %>
5
+ <% end %>
6
+
7
+ <%= form_tag(spree.admin_marketplace_settings_path, method: :put) do %>
8
+ <fieldset class="general no-border-top">
9
+ <% %w(allow_signup ).each do |key|
10
+ type = SpreeMarketplace::Config.preference_type(key) %>
11
+ <div class="field">
12
+ <%= label_tag(key, Spree.t(key) + ': ') + tag(:br) if type != :boolean %>
13
+ <%= preference_field_tag(key, SpreeMarketplace::Config[key], :type => type) %>
14
+ <%= label_tag(key, Spree.t(key)) + tag(:br) if type == :boolean %>
15
+ </div>
16
+ <% end %>
17
+ <div class="form-buttons filter-actions actions" data-hook="buttons">
18
+ <%= button Spree.t(:update), 'fa fa-refresh' %>
19
+ <span class="or"><%= Spree.t(:or) %></span>
20
+ <%= link_to_with_icon 'remove', Spree.t(:cancel), spree.edit_admin_marketplace_settings_url, :class => 'button' %>
21
+ </div>
22
+ </fieldset>
23
+ <% end %>
@@ -0,0 +1,28 @@
1
+ <% content_for :page_actions do %>
2
+ <li><%= button_link_to Spree.t(:cancel), spree.edit_admin_supplier_path(@supplier), :icon => 'icon-arrow-left' %></li>
3
+ <% end %>
4
+
5
+ <% content_for :page_title do %>
6
+ <%= I18n.t '.new_bank_account' %>
7
+ <% end %>
8
+
9
+ <div id='stripeError' class='errorExplanation' style='display:none;'></div>
10
+
11
+ <%= form_for @object, url: spree.admin_supplier_bank_accounts_path(@supplier) do |f| %>
12
+ <%= f.field_container :account_number do %>
13
+ <%= f.label :account_number %>:<br />
14
+ <%= f.text_field :account_number %>
15
+ <% end %>
16
+ <%= f.field_container :routing_number do %>
17
+ <%= f.label :routing_number %>:<br />
18
+ <%= f.text_field :routing_number %>
19
+ <% end %>
20
+ <%= f.field_container :country_iso do %>
21
+ <%= f.label :country_iso, Spree.t(:country) %>:<br />
22
+ <%= f.collection_select :country_iso, available_countries, :iso, :name, {selected: 'US'}, {class: 'select2'} %>
23
+ <% end %>
24
+ <%= f.hidden_field :masked_number %>
25
+ <%= f.hidden_field :name %>
26
+ <%= f.hidden_field :token %>
27
+ <%= f.submit Spree.t(:save) %>
28
+ <% end %>
@@ -0,0 +1,29 @@
1
+ <% content_for :head do -%>
2
+ <meta name="title" content="<%= Spree.t(:supplier_signup) %>">
3
+ <% end -%>
4
+ <%= render 'spree/shared/error_messages', :target => @supplier %>
5
+
6
+ <div class="col-md-6 col-md-offset-3">
7
+ <div class="panel panel-default">
8
+ <div class="panel-heading">
9
+ <h1><%= Spree.t(:supplier_signup) %></h1>
10
+ </div>
11
+ <div class="panel">
12
+ <%= render :partial => 'spree/signup_text' %>
13
+ </div>
14
+ <div id="new-supplier" class="panel-body" data-hook="supplier_fieldset_wrapper">
15
+ <%= form_for @supplier do |form| %>
16
+ <div data-hook="signup_inside_form">
17
+ <%= render :partial => 'spree/shared/supplier_form', :locals => { :form => form } %>
18
+ <p>
19
+ <%= form.submit Spree.t(:signup), :class => 'btn btn-lg btn-success btn-block' %>
20
+ </p>
21
+ </div>
22
+ <% end %>
23
+ <div class="text-center">
24
+ <%= Spree.t('or') %> <%= link_to Spree.t('cancel'), spree.account_path %>
25
+ </div>
26
+ <div data-hook="login_extras"></div>
27
+ </div>
28
+ </div>
29
+ </div>
@@ -0,0 +1,14 @@
1
+ # Sample localization file for English. Add more files in this directory for other locales.
2
+ # See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
3
+
4
+ en:
5
+ add_bank_account: Add Bank Account
6
+ bank_accounts: Bank Accounts
7
+ new_bank_account: New Bank Account
8
+ spree:
9
+ admin:
10
+ marketplace_settings:
11
+ update:
12
+ success: Marketplace settings successfully updated.
13
+ contacts_first_name: "Contact's First Name"
14
+ contacts_last_name: "Contact's Last Name"
@@ -0,0 +1,30 @@
1
+ ---
2
+ es:
3
+ bank_accounts: "Cuentas bancarias"
4
+ add_bank_account: "Añadir cuenta bancaria"
5
+ new_bank_account: "Nueva cuenta bancaria"
6
+ spree:
7
+ supplier_signup: "Registro de proveedores"
8
+ name: "Nombre de la marca o la empresa"
9
+ password: "Contraseña"
10
+ password_confirmation: "Contraseña (otra vez)"
11
+ brand: "Nombre de la marca"
12
+ profile_picture: "Imagen de perfil"
13
+ admin:
14
+ marketplace_settings:
15
+ update:
16
+ contacts_first_name: "Nombre (persona de contacto)"
17
+ contacts_last_name: "Apellidos (persona de contacto)"
18
+ tax_id: "Número de identificación fiscal: CIF, NIF o equivalente"
19
+ success: "Ajustes de marketplace actualizados con éxito."
20
+ signup: "Enviar"
21
+ save: "Guardar"
22
+ supplier_since: "Miembro desde hace"
23
+ seconds_ago: "segundos"
24
+ minutes_ago: "minutos"
25
+ hours_ago: "horas"
26
+ days_ago: "días"
27
+ months_ago: "meses"
28
+ years_ago: "años"
29
+ details: "Detalles"
30
+ profile_picture: "Imagen de perfil"
@@ -0,0 +1,9 @@
1
+ Spree::Core::Engine.routes.draw do
2
+ namespace :admin do
3
+ resource :marketplace_settings
4
+ resources :suppliers do
5
+ resources :bank_accounts, controller: 'supplier_bank_accounts'
6
+ end
7
+ end
8
+ resources :suppliers, only: [:create, :new, :index, :show]
9
+ end
@@ -0,0 +1,12 @@
1
+ class CreateSupplierBankAccounts < ActiveRecord::Migration
2
+ def change
3
+ create_table :spree_supplier_bank_accounts do |t|
4
+ t.string :masked_number
5
+ t.belongs_to :supplier
6
+ t.string :token
7
+ t.timestamps
8
+ end
9
+ add_index :spree_supplier_bank_accounts, :supplier_id
10
+ add_index :spree_supplier_bank_accounts, :token
11
+ end
12
+ end
@@ -0,0 +1,6 @@
1
+ class ConvertToStripe < ActiveRecord::Migration
2
+ def change
3
+ add_column :spree_supplier_bank_accounts, :country_iso, :integer
4
+ add_column :spree_supplier_bank_accounts, :name, :string
5
+ end
6
+ end
@@ -0,0 +1,31 @@
1
+ module SpreeMarketplace
2
+ module Generators
3
+ class InstallGenerator < Rails::Generators::Base
4
+
5
+ class_option :auto_run_migrations, :type => :boolean, :default => false
6
+
7
+ def add_javascripts
8
+ append_file 'vendor/assets/javascripts/spree/frontend/all.js', "//= require spree/frontend/spree_marketplace\n"
9
+ append_file 'vendor/assets/javascripts/spree/backend/all.js', "//= require spree/backend/spree_marketplace\n"
10
+ end
11
+
12
+ def add_stylesheets
13
+ inject_into_file 'vendor/assets/stylesheets/spree/frontend/all.css', " *= require spree/frontend/spree_marketplace\n", :before => /\*\//, :verbose => true
14
+ inject_into_file 'vendor/assets/stylesheets/spree/backend/all.css', " *= require spree/backend/spree_marketplace\n", :before => /\*\//, :verbose => true
15
+ end
16
+
17
+ def add_migrations
18
+ run 'bundle exec rake railties:install:migrations FROM=spree_marketplace'
19
+ end
20
+
21
+ def run_migrations
22
+ run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask 'Would you like to run the migrations now? [Y/n]')
23
+ if run_migrations
24
+ run 'bundle exec rake db:migrate'
25
+ else
26
+ puts 'Skipping rake db:migrate, don\'t forget to run it!'
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
@@ -0,0 +1,4 @@
1
+ require 'stripe'
2
+ require 'spree_core'
3
+ require 'spree_drop_ship'
4
+ require 'spree_marketplace/engine'
@@ -0,0 +1,47 @@
1
+ module SpreeMarketplace
2
+ class Engine < Rails::Engine
3
+ require 'spree/core'
4
+ isolate_namespace Spree
5
+ engine_name 'spree_marketplace'
6
+
7
+ config.autoload_paths += %W(#{config.root}/lib)
8
+
9
+ # use rspec for tests
10
+ config.generators do |g|
11
+ g.test_framework :rspec
12
+ end
13
+
14
+ initializer "spree_marketplace.preferences", before: :load_config_initializers do |app|
15
+ SpreeMarketplace::Config = Spree::MarketplaceConfiguration.new
16
+ end
17
+
18
+ config.after_initialize do
19
+ Stripe.api_key = SpreeMarketplace::Config[:stripe_secret_key]
20
+ end
21
+
22
+ def self.activate
23
+ Dir.glob(File.join(File.dirname(__FILE__), '../../app/**/*_decorator*.rb')) do |c|
24
+ Rails.configuration.cache_classes ? require(c) : load(c)
25
+ end
26
+ Spree::Ability.register_ability(Spree::MarketplaceAbility)
27
+ end
28
+
29
+ def self.ckeditor_available?
30
+ @@ckeditor_available ||= ::Rails::Engine.subclasses.map(&:instance).map{ |e| e.class.to_s }.include?('Ckeditor::Engine')
31
+ end
32
+
33
+ def self.spree_digital_available?
34
+ @@spree_digital_available ||= ::Rails::Engine.subclasses.map(&:instance).map{ |e| e.class.to_s }.include?('SpreeDigital::Engine')
35
+ end
36
+
37
+ def self.spree_group_price_available?
38
+ @@spree_group_price_available ||= ::Rails::Engine.subclasses.map(&:instance).map{ |e| e.class.to_s }.include?('SpreeGroupPricing::Engine')
39
+ end
40
+
41
+ def self.spree_related_products_available?
42
+ @@spree_related_procues_available ||= ::Rails::Engine.subclasses.map(&:instance).map{ |e| e.class.to_s }.include?('SpreeRelatedProducts::Engine')
43
+ end
44
+
45
+ config.to_prepare &method(:activate).to_proc
46
+ end
47
+ end