├── .hound.yml
├── app
├── views
│ └── spree
│ │ ├── shared
│ │ └── unauthorized.erb
│ │ ├── admin
│ │ ├── suppliers
│ │ │ ├── new.html.erb
│ │ │ ├── edit.html.erb
│ │ │ ├── index.html.erb
│ │ │ └── _form.html.erb
│ │ ├── shared
│ │ │ └── _header.html.erb
│ │ ├── drop_ship_settings
│ │ │ └── edit.html.erb
│ │ └── shipments
│ │ │ ├── edit.html.erb
│ │ │ └── index.html.erb
│ │ ├── supplier_mailer
│ │ └── welcome.html.erb
│ │ └── drop_ship_order_mailer
│ │ └── supplier_order.html.erb
├── assets
│ ├── javascripts
│ │ └── spree
│ │ │ ├── frontend
│ │ │ └── spree_drop_ship.js
│ │ │ └── backend
│ │ │ └── spree_drop_ship.js
│ └── stylesheets
│ │ └── spree
│ │ ├── frontend
│ │ └── spree_drop_ship.css
│ │ └── backend
│ │ └── spree_drop_ship.scss
├── models
│ ├── spree.rb
│ ├── spree
│ │ ├── supplier_variant.rb
│ │ ├── payment_decorator.rb
│ │ ├── user_decorator.rb
│ │ ├── drop_ship_configuration.rb
│ │ ├── stock_location_decorator.rb
│ │ ├── variant_decorator.rb
│ │ ├── product_decorator.rb
│ │ ├── order_decorator.rb
│ │ ├── shipment_decorator.rb
│ │ ├── supplier_ability.rb
│ │ ├── stock
│ │ │ └── splitter
│ │ │ │ └── drop_ship.rb
│ │ └── supplier.rb
│ └── ckeditor
│ │ └── asset_decorator.rb
├── overrides
│ └── spree
│ │ ├── admin
│ │ ├── products
│ │ │ ├── _form
│ │ │ │ └── converted_admin_product_form_right.html.erb.deface
│ │ │ └── index
│ │ │ │ └── override_rows.html.erb.deface
│ │ ├── shared
│ │ │ └── sub_menu
│ │ │ │ └── _configuration
│ │ │ │ └── add_drop_ship_settings.html.erb.deface
│ │ ├── stock_locations
│ │ │ └── _form
│ │ │ │ └── add_supplier.html.erb.deface
│ │ └── stock_movements
│ │ │ └── _form
│ │ │ └── fix_bootstrap_form.html.erb.deface
│ │ └── layouts
│ │ └── admin
│ │ ├── add_profile_admin_tabs.html.erb.deface
│ │ └── converted_admin_tabs.html.erb.deface
├── controllers
│ ├── spree
│ │ ├── admin
│ │ │ ├── stock_locations_controller_decorator.rb
│ │ │ ├── drop_ship_settings_controller.rb
│ │ │ ├── products_controller_decorator.rb
│ │ │ ├── suppliers_controller.rb
│ │ │ └── shipments_controller.rb
│ │ ├── base_controller_decorator.rb
│ │ └── api
│ │ │ └── v1
│ │ │ └── stock_locations_controller_decorator.rb
│ └── ckeditor
│ │ ├── pictures_controller_decorator.rb
│ │ └── attachment_files_controller_decorator.rb
└── mailers
│ └── spree
│ ├── supplier_mailer.rb
│ └── drop_ship_order_mailer.rb
├── spec
├── models
│ └── spree
│ │ ├── payment_decorator_spec.rb
│ │ ├── supplier_variant_spec.rb
│ │ ├── variant_decorator_spec.rb
│ │ ├── product_decorator_spec.rb
│ │ ├── user_decorator_spec.rb
│ │ ├── stock_location_decorator_spec.rb
│ │ ├── shipment_decorator_spec.rb
│ │ ├── order_decorator_spec.rb
│ │ ├── stock
│ │ └── splitter
│ │ │ └── drop_ship_spec.rb
│ │ ├── supplier_spec.rb
│ │ └── supplier_ability_spec.rb
├── features
│ └── admin
│ │ ├── return_authorizations_spec.rb
│ │ ├── orders_spec.rb
│ │ ├── products_spec.rb
│ │ ├── settings_spec.rb
│ │ ├── shipments_spec.rb
│ │ ├── stock_spec.rb
│ │ ├── suppliers_spec.rb
│ │ └── stock_management_spec.rb
├── support
│ └── integration_helpers.rb
└── spec_helper.rb
├── config
├── routes.rb
└── locales
│ ├── en.yml
│ └── es.yml
├── lib
├── spree_drop_ship.rb
├── spree_drop_ship
│ ├── engine.rb
│ └── factories.rb
├── generators
│ └── spree_drop_ship
│ │ └── install
│ │ └── install_generator.rb
└── tasks
│ └── spree_sample.rake
├── Gemfile
├── gemfiles
├── spree_3_2.gemfile
├── spree_3_3.gemfile
├── spree_3_4.gemfile
├── spree_3_1.gemfile
└── spree_master.gemfile
├── db
└── migrate
│ ├── 20130216070944_product_belongs_to_supplier.rb
│ ├── 20130606220913_add_permalink_to_suppliers.rb
│ ├── 20130405011127_user_belongs_to_supplier.rb
│ ├── 20130405005502_stock_locations_belongs_to_supplier.rb
│ ├── 20140323170638_add_supplier_commission_to_shipments.rb
│ ├── 20130428063053_add_balanced_token_to_suppliers.rb
│ ├── 20130510181443_add_supplier_id_to_ckeditor_assets.rb
│ ├── 20140416184616_migrate_payment_and_commission.rb
│ ├── 20140529041325_create_spree_supplier_variants.rb
│ └── 20121006073854_create_suppliers.rb
├── .gitignore
├── script
└── rails
├── Rakefile
├── Versionfile
├── .rubocop.yml
├── Appraisals
├── .travis.yml
├── LICENSE
├── spree_drop_ship.gemspec
└── README.md
/.hound.yml:
--------------------------------------------------------------------------------
1 | ---
2 | rubocop:
3 | config_file: .rubocop.yml
4 |
--------------------------------------------------------------------------------
/app/views/spree/shared/unauthorized.erb:
--------------------------------------------------------------------------------
1 | <%= Spree.t(:authorization_failure) %>
2 |
--------------------------------------------------------------------------------
/app/assets/javascripts/spree/frontend/spree_drop_ship.js:
--------------------------------------------------------------------------------
1 | //= require spree/frontend
2 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/spree/frontend/spree_drop_ship.css:
--------------------------------------------------------------------------------
1 | /*
2 | *= require spree/frontend
3 | */
4 |
--------------------------------------------------------------------------------
/app/models/spree.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | def self.table_name_prefix
3 | 'spree_'
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/assets/javascripts/spree/backend/spree_drop_ship.js:
--------------------------------------------------------------------------------
1 | //= require spree/backend
2 | // Shipments AJAX API
3 |
--------------------------------------------------------------------------------
/spec/models/spree/payment_decorator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Spree::Payment do
4 |
5 | it { should belong_to(:payable) }
6 |
7 | end
8 |
--------------------------------------------------------------------------------
/app/models/spree/supplier_variant.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class SupplierVariant < Spree::Base
3 | belongs_to :supplier
4 | belongs_to :variant
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/app/models/ckeditor/asset_decorator.rb:
--------------------------------------------------------------------------------
1 | if defined?(Ckeditor)
2 | Ckeditor::Asset.class_eval do
3 | belongs_to :supplier, class_name: 'Spree::Supplier'
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/spec/models/spree/supplier_variant_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Spree::SupplierVariant do
4 | skip "add some examples to (or delete) #{__FILE__}"
5 | end
6 |
--------------------------------------------------------------------------------
/config/routes.rb:
--------------------------------------------------------------------------------
1 | Spree::Core::Engine.add_routes do
2 | namespace :admin do
3 | resource :drop_ship_settings
4 | resources :shipments
5 | resources :suppliers
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/lib/spree_drop_ship.rb:
--------------------------------------------------------------------------------
1 | require 'sass/rails'
2 | require 'spree_api'
3 | require 'spree_backend'
4 | require 'spree_core'
5 | require 'spree_extension'
6 | require 'spree_drop_ship/engine'
7 |
--------------------------------------------------------------------------------
/Gemfile:
--------------------------------------------------------------------------------
1 | source 'https://rubygems.org'
2 |
3 | gem 'spree', github: 'spree/spree'
4 | gem 'spree_auth_devise', github: 'spree/spree'
5 | gem 'rails-controller-testing'
6 |
7 | gemspec
8 |
--------------------------------------------------------------------------------
/spec/models/spree/variant_decorator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Spree::Variant do
4 | it 'should populate stock item for each of the master variant suppliers' do
5 | skip 'todo'
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/gemfiles/spree_3_2.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "spree", "~> 3.2.0"
6 | gem "spree_auth_devise", "~> 3.3.0"
7 | gem "rails-controller-testing"
8 |
9 | gemspec path: "../"
10 |
--------------------------------------------------------------------------------
/gemfiles/spree_3_3.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "spree", "~> 3.3.0"
6 | gem "spree_auth_devise", "~> 3.3.0"
7 | gem "rails-controller-testing"
8 |
9 | gemspec path: "../"
10 |
--------------------------------------------------------------------------------
/gemfiles/spree_3_4.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "spree", "~> 3.4.0"
6 | gem "spree_auth_devise", "~> 3.3.0"
7 | gem "rails-controller-testing"
8 |
9 | gemspec path: "../"
10 |
--------------------------------------------------------------------------------
/spec/features/admin/return_authorizations_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe "Admin - Return Authorizations" do
4 |
5 | skip 'TODO: need to write, but should likely wait until after https://github.com/spree/spree/issues/4026'
6 |
7 | end
8 |
--------------------------------------------------------------------------------
/db/migrate/20130216070944_product_belongs_to_supplier.rb:
--------------------------------------------------------------------------------
1 | class ProductBelongsToSupplier < SpreeExtension::Migration[4.2]
2 | def change
3 | add_column :spree_products, :supplier_id, :integer
4 | add_index :spree_products, :supplier_id
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20130606220913_add_permalink_to_suppliers.rb:
--------------------------------------------------------------------------------
1 | class AddPermalinkToSuppliers < SpreeExtension::Migration[4.2]
2 | def change
3 | add_column :spree_suppliers, :slug, :string
4 | add_index :spree_suppliers, :slug, unique: true
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | \#*
2 | *~
3 | .#*
4 | .bundle
5 | .DS_Store
6 | .idea
7 | .project
8 | .ruby-gemset
9 | .ruby-version
10 | .sass-cache
11 | coverage
12 | Gemfile.lock
13 | gemfiles/*.lock
14 | tmp
15 | nbproject
16 | pkg
17 | *.swp
18 | spec/dummy
19 | spec/vcr_cassettes
20 |
--------------------------------------------------------------------------------
/db/migrate/20130405011127_user_belongs_to_supplier.rb:
--------------------------------------------------------------------------------
1 | class UserBelongsToSupplier < SpreeExtension::Migration[4.2]
2 | def change
3 | add_column Spree.user_class.table_name, :supplier_id, :integer
4 | add_index Spree.user_class.table_name, :supplier_id
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/gemfiles/spree_3_1.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "spree", "~> 3.1.0"
6 | gem "spree_auth_devise", "~> 3.3.0"
7 | gem "rails-controller-testing"
8 | gem "rails", "~> 4.2.10"
9 |
10 | gemspec path: "../"
11 |
--------------------------------------------------------------------------------
/db/migrate/20130405005502_stock_locations_belongs_to_supplier.rb:
--------------------------------------------------------------------------------
1 | class StockLocationsBelongsToSupplier < SpreeExtension::Migration[4.2]
2 | def change
3 | add_column :spree_stock_locations, :supplier_id, :integer
4 | add_index :spree_stock_locations, :supplier_id
5 | end
6 | end
7 |
--------------------------------------------------------------------------------
/db/migrate/20140323170638_add_supplier_commission_to_shipments.rb:
--------------------------------------------------------------------------------
1 | class AddSupplierCommissionToShipments < SpreeExtension::Migration[4.2]
2 | def change
3 | add_column :spree_shipments, :supplier_commission, :decimal, precision: 8, scale: 2, default: 0.0, null: false
4 | end
5 | end
6 |
--------------------------------------------------------------------------------
/app/overrides/spree/admin/products/_form/converted_admin_product_form_right.html.erb.deface:
--------------------------------------------------------------------------------
1 |
2 |
3 | <% if can? :manage, Spree::Supplier %>
4 |
5 | <% end %>
6 |
--------------------------------------------------------------------------------
/db/migrate/20130428063053_add_balanced_token_to_suppliers.rb:
--------------------------------------------------------------------------------
1 | class AddBalancedTokenToSuppliers < SpreeExtension::Migration[4.2]
2 | def change
3 | add_column :spree_suppliers, :tax_id, :string
4 | add_column :spree_suppliers, :token, :string
5 | add_index :spree_suppliers, :token
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/app/models/spree/payment_decorator.rb:
--------------------------------------------------------------------------------
1 | module Dropship
2 | module Spree
3 | module PaymentDecorator
4 | def self.prepended(base)
5 | base.belongs_to :payable, polymorphic: true
6 | end
7 | end
8 | end
9 | end
10 |
11 | Spree::Payment.prepend Dropship::Spree::PaymentDecorator
12 |
--------------------------------------------------------------------------------
/app/overrides/spree/admin/shared/sub_menu/_configuration/add_drop_ship_settings.html.erb.deface:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%= configurations_sidebar_menu_item Spree.t(:drop_ship_settings), spree.edit_admin_drop_ship_settings_path %>
4 |
--------------------------------------------------------------------------------
/db/migrate/20130510181443_add_supplier_id_to_ckeditor_assets.rb:
--------------------------------------------------------------------------------
1 | class AddSupplierIdToCkeditorAssets < SpreeExtension::Migration[4.2]
2 | if table_exists?(:ckeditor_assets)
3 | def change
4 | add_column :ckeditor_assets, :supplier_id, :integer
5 | add_index :ckeditor_assets, :supplier_id
6 | end
7 | end
8 | end
9 |
--------------------------------------------------------------------------------
/db/migrate/20140416184616_migrate_payment_and_commission.rb:
--------------------------------------------------------------------------------
1 | class MigratePaymentAndCommission < SpreeExtension::Migration[4.2]
2 | def change
3 | add_column :spree_payments, :payable_id, :integer
4 | add_column :spree_payments, :payable_type, :string
5 | add_index :spree_payments, [:payable_id, :payable_type]
6 | end
7 | end
8 |
--------------------------------------------------------------------------------
/script/rails:
--------------------------------------------------------------------------------
1 | # This command will automatically be run when you run "rails" with Rails 3 gems installed from the root of your application.
2 |
3 | ENGINE_ROOT = File.expand_path('../..', __FILE__)
4 | ENGINE_PATH = File.expand_path('../../lib/spree_drop_ship/engine', __FILE__)
5 |
6 | require 'rails/all'
7 | require 'rails/engine/commands'
8 |
--------------------------------------------------------------------------------
/gemfiles/spree_master.gemfile:
--------------------------------------------------------------------------------
1 | # This file was generated by Appraisal
2 |
3 | source "https://rubygems.org"
4 |
5 | gem "spree", github: "spree/spree", branch: "master"
6 | gem "spree_auth_devise", github: "spree/spree_auth_devise", branch: "master"
7 | gem "mysql2", "~> 0.5.1"
8 | gem "rails-controller-testing"
9 |
10 | gemspec path: "../"
11 |
--------------------------------------------------------------------------------
/spec/models/spree/product_decorator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Spree::Product do
4 |
5 | let(:product) { create :product }
6 |
7 | it '#supplier?' do
8 | product.supplier?.should eq false
9 | product.add_supplier! create(:supplier)
10 | product.reload.supplier?.should eq true
11 | end
12 |
13 | end
14 |
--------------------------------------------------------------------------------
/app/controllers/spree/admin/stock_locations_controller_decorator.rb:
--------------------------------------------------------------------------------
1 | Spree::Admin::StockLocationsController.class_eval do
2 | create.after :set_supplier
3 |
4 | private
5 |
6 | def set_supplier
7 | if try_spree_current_user.supplier?
8 | @object.supplier = try_spree_current_user.supplier
9 | @object.save
10 | end
11 | end
12 | end
13 |
--------------------------------------------------------------------------------
/app/mailers/spree/supplier_mailer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class SupplierMailer < Spree::BaseMailer
3 | default from: Spree::Store.current.mail_from_address
4 |
5 | def welcome(supplier_id)
6 | @supplier = Supplier.find supplier_id
7 | mail to: @supplier.email, subject: Spree.t('supplier_mailer.welcome.subject')
8 | end
9 | end
10 | end
11 |
--------------------------------------------------------------------------------
/app/controllers/spree/base_controller_decorator.rb:
--------------------------------------------------------------------------------
1 | Spree::BaseController.class_eval do
2 | prepend_before_action :redirect_supplier
3 |
4 | private
5 |
6 | def redirect_supplier
7 | if ['/admin', '/admin/authorization_failure'].include?(request.path) && try_spree_current_user.try(:supplier)
8 | redirect_to '/admin/shipments' and return false
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/Rakefile:
--------------------------------------------------------------------------------
1 | require 'bundler'
2 | Bundler::GemHelper.install_tasks
3 |
4 | require 'rspec/core/rake_task'
5 | require 'spree/testing_support/common_rake'
6 |
7 | RSpec::Core::RakeTask.new
8 |
9 | task default: [:spec]
10 |
11 | desc 'Generates a dummy app for testing'
12 | task :test_app do
13 | ENV['LIB_NAME'] = 'spree_drop_ship'
14 | Rake::Task['common:test_app'].invoke 'Spree::User'
15 | end
16 |
--------------------------------------------------------------------------------
/spec/features/admin/orders_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'Admin - Orders', js: true do
4 |
5 | it 'Supplier should not be authorized' do
6 | create(:user) # create extra user so admin role isnt assigned to the user we login as
7 | login_user create(:supplier_user)
8 | visit spree.admin_orders_path
9 | page.should have_content('Authorization Failure')
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/Versionfile:
--------------------------------------------------------------------------------
1 | # This file is used to designate compatibilty with different versions of Spree
2 | # Please see http://spreecommerce.com/documentation/extensions.html#versionfile for details
3 | # '1.3.x' => { :ref => 'e5624f7' } # Not going to be maintained but most of the works there if your looking for a starting point to branch off of.
4 |
5 | '2.4.x' => { :branch => 'master' }
6 | '2.3.x' => { :branch => '2-3-stable' }
7 |
--------------------------------------------------------------------------------
/app/overrides/spree/layouts/admin/add_profile_admin_tabs.html.erb.deface:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%# Add tab "profile to admin tabs" %>
4 | <% if try_spree_current_user && try_spree_current_user.supplier? %>
5 |
8 | <% end %>
9 |
--------------------------------------------------------------------------------
/spec/models/spree/user_decorator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Spree.user_class do
4 |
5 | it { should belong_to(:supplier) }
6 |
7 | it { should have_many(:variants).through(:supplier) }
8 |
9 | let(:user) { build :user }
10 |
11 | it '#supplier?' do
12 | user.supplier?.should be false
13 | user.supplier = build :supplier
14 | user.supplier?.should be true
15 | end
16 |
17 | end
18 |
--------------------------------------------------------------------------------
/app/assets/stylesheets/spree/backend/spree_drop_ship.scss:
--------------------------------------------------------------------------------
1 | /*
2 | *= require spree/backend
3 | */
4 |
5 | .center {
6 | text-align: center;
7 | }
8 |
9 | .state.delivered:before,
10 | .state.confirmed:before {
11 | background-color: #ff9300;
12 | }
13 |
14 | .supplier-banks ol li {
15 | margin-left: 20px;
16 | margin-bottom: 5px;
17 | text-align: left;
18 | }
19 |
20 | a .without-textdecor{
21 | text-decoration: none;
22 | }
--------------------------------------------------------------------------------
/app/models/spree/user_decorator.rb:
--------------------------------------------------------------------------------
1 | module Dropship
2 | module Spree
3 | module UserDecorator
4 | def self.prepended(base)
5 | base.belongs_to :supplier, class_name: 'Spree::Supplier'
6 |
7 | base.has_many :variants, through: :supplier
8 | end
9 |
10 | def supplier?
11 | supplier.present?
12 | end
13 | end
14 | end
15 | end
16 |
17 | Spree.user_class.prepend Dropship::Spree::UserDecorator
18 |
--------------------------------------------------------------------------------
/spec/support/integration_helpers.rb:
--------------------------------------------------------------------------------
1 | module IntegrationHelpers
2 |
3 | def login_user(user = nil, options = {})
4 | options[:password] ||= 'secret'
5 | user ||= create(:user, password: options[:password])
6 |
7 | visit spree.login_path
8 | fill_in 'spree_user[email]', with: user.email
9 | fill_in 'spree_user[password]', with: options[:password]
10 | click_button 'Login'
11 | page.should_not have_content 'Login'
12 | end
13 |
14 | end
15 |
--------------------------------------------------------------------------------
/app/mailers/spree/drop_ship_order_mailer.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class DropShipOrderMailer < Spree::BaseMailer
3 | default from: Spree::Store.current.mail_from_address
4 |
5 | def supplier_order(shipment_id)
6 | @shipment = Shipment.find shipment_id
7 | @supplier = @shipment.supplier
8 | mail to: @supplier.email, subject: Spree.t('drop_ship_order_mailer.supplier_order.subject', name: Spree::Store.current.name, number: @shipment.number)
9 | end
10 | end
11 | end
12 |
--------------------------------------------------------------------------------
/.rubocop.yml:
--------------------------------------------------------------------------------
1 | ---
2 | AllCops:
3 | Exclude:
4 | - 'spec/**/*'
5 | - 'spec/dummy/**/*'
6 | - 'db/**/*'
7 |
8 | # Too picky.
9 | Metrics/LineLength:
10 | Enabled: false
11 |
12 | # This should truly be on for well documented gems.
13 | Style/Documentation:
14 | Enabled: false
15 |
16 | # Neatly aligned code is to swell.
17 | Layout/SpaceBeforeFirstArg:
18 | Enabled: false
19 |
20 | # Avoid contradictory style rules by enforce single quotes.
21 | Style/StringLiterals:
22 | EnforcedStyle: single_quotes
23 |
--------------------------------------------------------------------------------
/app/controllers/ckeditor/pictures_controller_decorator.rb:
--------------------------------------------------------------------------------
1 | if defined?(Ckeditor::PicturesController)
2 | Ckeditor::PicturesController.class_eval do
3 | load_and_authorize_resource class: 'Ckeditor::Picture'
4 | after_action :set_supplier, only: [:create]
5 |
6 | def index; end
7 |
8 | private
9 |
10 | def set_supplier
11 | if spree_current_user.supplier? && @picture
12 | @picture.supplier = spree_current_user.supplier
13 | @picture.save
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/app/controllers/spree/api/v1/stock_locations_controller_decorator.rb:
--------------------------------------------------------------------------------
1 | Spree::Api::V1::StockLocationsController.class_eval do
2 | before_action :supplier_locations, only: [:index]
3 | before_action :supplier_transfers, only: [:index]
4 |
5 | private
6 |
7 | def supplier_locations
8 | params[:q] ||= {}
9 | params[:q][:supplier_id_eq] = spree_current_user.supplier_id
10 | end
11 |
12 | def supplier_transfers
13 | params[:q] ||= {}
14 | params[:q][:supplier_id_eq] = spree_current_user.supplier_id
15 | end
16 | end
17 |
--------------------------------------------------------------------------------
/db/migrate/20140529041325_create_spree_supplier_variants.rb:
--------------------------------------------------------------------------------
1 | class CreateSpreeSupplierVariants < SpreeExtension::Migration[4.2]
2 | def change
3 | create_table :spree_supplier_variants do |t|
4 | t.belongs_to :supplier, index: true
5 | t.belongs_to :variant, index: true
6 | t.decimal :cost
7 |
8 | t.timestamps
9 | end
10 | Spree::Product.where.not(supplier_id: nil).each do |product|
11 | product.add_supplier! product.supplier_id
12 | end
13 | remove_column :spree_products, :supplier_id
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/app/controllers/ckeditor/attachment_files_controller_decorator.rb:
--------------------------------------------------------------------------------
1 | if defined?(Ckeditor::AttachmentFilesController)
2 | Ckeditor::AttachmentFilesController.class_eval do
3 | load_and_authorize_resource class: 'Ckeditor::AttachmentFile'
4 | after_action :set_supplier, only: [:create]
5 |
6 | def index; end
7 |
8 | private
9 |
10 | def set_supplier
11 | if try_spree_current_user.supplier? && @attachment
12 | @attachment.supplier = try_spree_current_user.supplier
13 | @attachment.save
14 | end
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/app/controllers/spree/admin/drop_ship_settings_controller.rb:
--------------------------------------------------------------------------------
1 | class Spree::Admin::DropShipSettingsController < Spree::Admin::BaseController
2 | def edit
3 | @config = Spree::DropShipConfiguration.new
4 | end
5 |
6 | def update
7 | config = Spree::DropShipConfiguration.new
8 |
9 | params.each do |name, value|
10 | next unless config.has_preference? name
11 | config[name] = value
12 | end
13 |
14 | flash[:success] = Spree.t('admin.drop_ship_settings.update.success')
15 | redirect_to spree.edit_admin_drop_ship_settings_path
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/app/overrides/spree/admin/stock_locations/_form/add_supplier.html.erb.deface:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%# Remove extra div and change classes %>
4 | <% if spree_current_user.admin? %>
5 |
6 | <%= f.field_container :supplier do %>
7 | <%= f.label :supplier_id, Spree.t(:supplier) %>
8 | <%= f.collection_select(:supplier_id, Spree::Supplier.all, :id, :name, { :include_blank => 'None' }, { :class => 'select2' }) %>
9 | <%= f.error_message_on :supplier %>
10 | <% end %>
11 |
12 | <% end %>
13 |
--------------------------------------------------------------------------------
/app/views/spree/admin/suppliers/new.html.erb:
--------------------------------------------------------------------------------
1 | <% content_for :page_actions do %>
2 | <%= button_link_to Spree.t('back_to_suppliers'), spree.admin_suppliers_path, icon: 'arrow-left' %>
3 | <% end %>
4 |
5 | <% content_for :page_title do %>
6 | <%= Spree.t('new_supplier') %>
7 | <% end %>
8 |
9 | <%= render 'spree/shared/error_messages', target: @object%>
10 |
11 | <%= form_for [:admin, @object] do |f| %>
12 | <%= render "form", form: f %>
13 | <%# render 'create' and 'cancel' buttons %>
14 |
15 | <%= render :partial => 'spree/admin/shared/new_resource_links' %>
16 |
17 | <% end %>
18 |
--------------------------------------------------------------------------------
/app/overrides/spree/admin/stock_movements/_form/fix_bootstrap_form.html.erb.deface:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%# Fix markup form bug %>
4 | <%= f.field_container :quantity, class: ['form-group'] do %>
5 | <%= f.label :quantity, Spree.t(:quantity) %>
6 | <%= f.text_field :quantity , class: 'form-control'%>
7 | <% end %>
8 | <%= f.field_container :stock_item_id, class: ['form-group'] do %>
9 | <%= f.label :stock_item_id, Spree.t(:stock_item_id) %>
10 | <%= f.text_field 'stock_item_id', :class => 'select2', :'data-stock-location-id' => params[:stock_location_id] %>
11 | <% end %>
--------------------------------------------------------------------------------
/app/controllers/spree/admin/products_controller_decorator.rb:
--------------------------------------------------------------------------------
1 | Spree::Admin::ProductsController.class_eval do
2 | before_action :get_suppliers, only: %i[edit update]
3 | before_action :supplier_collection, only: [:index]
4 |
5 | private
6 |
7 | def get_suppliers
8 | @suppliers = Spree::Supplier.order(:name)
9 | end
10 |
11 | # Scopes the collection to the Supplier.
12 | def supplier_collection
13 | if try_spree_current_user && !try_spree_current_user.admin? && try_spree_current_user.supplier?
14 | @collection = @collection.joins(:suppliers).where('spree_suppliers.id = ?', try_spree_current_user.supplier_id)
15 | end
16 | end
17 | end
18 |
--------------------------------------------------------------------------------
/spec/features/admin/products_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'Admin - Products', js: true do
4 |
5 | context 'as Admin' do
6 |
7 | xit 'should be able to change supplier' do
8 | s1 = create(:supplier)
9 | s2 = create(:supplier)
10 | product = create :product
11 | product.add_supplier! s1
12 |
13 | login_user create(:admin_user)
14 | visit spree.admin_product_path(product)
15 |
16 | select2 s2.name, from: 'Supplier'
17 | click_button 'Update'
18 |
19 | expect(page).to have_content("Product \"#{product.name}\" has been successfully updated!")
20 | expect(product.reload.suppliers.first.id).to eql(s2.id)
21 | end
22 |
23 | end
24 |
25 | end
26 |
--------------------------------------------------------------------------------
/app/models/spree/drop_ship_configuration.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | class DropShipConfiguration < Preferences::Configuration
3 | # Automatically deliver drop ship orders by default.
4 | preference :automatically_deliver_orders_to_supplier, :boolean, default: true
5 |
6 | # Default flat rate to charge suppliers per order for commission.
7 | preference :default_commission_flat_rate, :float, default: 0.0
8 |
9 | # Default percentage to charge suppliers per order for commission.
10 | preference :default_commission_percentage, :float, default: 0.0
11 |
12 | # Determines whether or not to email a new supplier their welcome email.
13 | preference :send_supplier_email, :boolean, default: true
14 | end
15 | end
16 |
--------------------------------------------------------------------------------
/db/migrate/20121006073854_create_suppliers.rb:
--------------------------------------------------------------------------------
1 | class CreateSuppliers < SpreeExtension::Migration[4.2]
2 |
3 | def change
4 | create_table :spree_suppliers do |t|
5 | t.boolean :active, default: false, null: false
6 | t.references :address
7 | t.decimal :commission_flat_rate, :precision => 8, :scale => 2, :default => 0.0, :null => false
8 | t.float :commission_percentage, default: 0.0, null: false
9 | t.string :email
10 | t.string :name
11 | t.string :url
12 | t.datetime :deleted_at
13 | t.timestamps
14 | end
15 | add_index :spree_suppliers, :address_id
16 | add_index :spree_suppliers, :deleted_at
17 | add_index :spree_suppliers, :active
18 | end
19 |
20 | end
21 |
--------------------------------------------------------------------------------
/Appraisals:
--------------------------------------------------------------------------------
1 | appraise 'spree-3-1' do
2 | gem 'spree', '~> 3.1.0'
3 | gem 'spree_auth_devise', '~> 3.1.0'
4 | end
5 |
6 | appraise 'spree-3-2' do
7 | gem 'spree', '~> 3.2.0'
8 | gem 'spree_auth_devise', '~> 3.2.0'
9 | gem 'rails-controller-testing'
10 | end
11 |
12 | appraise 'spree-3-3' do
13 | gem 'spree', '~> 3.3.0'
14 | gem 'spree_auth_devise', '~> 3.3.0'
15 | gem 'rails-controller-testing'
16 | end
17 |
18 | appraise 'spree-3-4' do
19 | gem 'spree', '~> 3.4.0'
20 | gem 'spree_auth_devise', '~> 3.4.0'
21 | gem 'rails-controller-testing'
22 | end
23 |
24 | appraise 'spree-master' do
25 | gem 'spree', github: 'spree/spree', branch: 'master'
26 | gem 'spree_auth_devise', github: 'spree/spree_auth_devise', branch: 'master'
27 | gem 'rails-controller-testing'
28 | end
29 |
--------------------------------------------------------------------------------
/app/overrides/spree/layouts/admin/converted_admin_tabs.html.erb.deface:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%# Add sidebar's items for supplier %>
4 | <% if can? :index, Spree::Supplier %>
5 |
8 | <% end %>
9 | <% if can? :index, Spree::Shipment %>
10 |
13 | <% end %>
14 | <% if try_spree_current_user.try(:supplier?) and can? :index, Spree::StockLocation %>
15 |
18 | <% end %>
19 |
--------------------------------------------------------------------------------
/app/views/spree/admin/suppliers/edit.html.erb:
--------------------------------------------------------------------------------
1 | <% if can? :index, Spree::Supplier %>
2 | <% content_for :page_actions do %>
3 | <%= button_link_to Spree.t('back_to_suppliers'), spree.admin_suppliers_path, icon: 'arrow-left' %>
4 | <% end %>
5 | <% end %>
6 |
7 | <% content_for :page_title do %>
8 | <%== Spree.t(:editing_supplier) + " “#{@object.name}”".html_safe %>
9 | <% end %>
10 |
11 | <%= render 'spree/shared/error_messages', target: @object %>
12 |
13 | <%= form_for [:admin, @object] do |f| %>
14 | <%= render "form", form: f %>
15 |
16 | <%# render 'update' and 'cancel' buttons %>
17 |
18 | <%= render :partial => 'spree/admin/shared/edit_resource_links' %>
19 |
20 | <% end %>
21 |
22 |
23 |
--------------------------------------------------------------------------------
/app/models/spree/stock_location_decorator.rb:
--------------------------------------------------------------------------------
1 | module Dropship
2 | module Spree
3 | module StockLocationDecorator
4 | def self.prepended(base)
5 | base.belongs_to :supplier, class_name: 'Spree::Supplier'
6 |
7 | base.scope :by_supplier, ->(supplier_id) { where(supplier_id: supplier_id) }
8 | end
9 |
10 | # Wrapper for creating a new stock item respecting the backorderable config and supplier
11 | def propagate_variant(variant)
12 | if supplier_id.blank? || variant.suppliers.pluck(:id).include?(supplier_id)
13 | stock_items.create!(variant: variant, backorderable: backorderable_default)
14 | end
15 | end
16 |
17 | def available?(variant)
18 | stock_item(variant).try(:available?)
19 | end
20 | end
21 | end
22 | end
23 |
24 | Spree::StockLocation.prepend Dropship::Spree::StockLocationDecorator
25 |
--------------------------------------------------------------------------------
/app/models/spree/variant_decorator.rb:
--------------------------------------------------------------------------------
1 | module Dropship
2 | module Spree
3 | module VariantDecorator
4 | def self.prepended(base)
5 | base.has_many :supplier_variants
6 | base.has_many :suppliers, through: :supplier_variants
7 |
8 | base.before_create :populate_for_suppliers
9 | end
10 |
11 | private
12 |
13 | def create_stock_items
14 | ::Spree::StockLocation.all.find_each do |stock_location|
15 | if stock_location.supplier_id.blank? || suppliers.pluck(:id).include?(stock_location.supplier_id)
16 | stock_location.propagate_variant(self) if stock_location.propagate_all_variants?
17 | end
18 | end
19 | end
20 |
21 | def populate_for_suppliers
22 | self.suppliers = product.suppliers
23 | end
24 | end
25 | end
26 | end
27 |
28 | Spree::Variant.prepend Dropship::Spree::VariantDecorator
29 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | sudo: required
2 | dist: trusty
3 |
4 | before_script:
5 | - export DISPLAY=:99.0 && sh -e /etc/init.d/xvfb start && sleep 3
6 |
7 | script:
8 | - bundle exec rake test_app
9 | - bundle exec rake spec
10 |
11 | addons:
12 | chrome: stable
13 |
14 | env:
15 | - DB=mysql
16 | - DB=postgres
17 |
18 | language: ruby
19 |
20 | rvm:
21 | - 2.5.1
22 | - 2.2.7
23 |
24 | matrix:
25 | allow_failures:
26 | - gemfile: gemfiles/spree_master.gemfile
27 |
28 | gemfile:
29 | - gemfiles/spree_3_1.gemfile
30 | - gemfiles/spree_3_2.gemfile
31 | - gemfiles/spree_3_3.gemfile
32 | - gemfiles/spree_3_4.gemfile
33 | - gemfiles/spree_master.gemfile
34 |
35 | before_install:
36 | - mysql -u root -e "GRANT ALL ON *.* TO 'travis'@'%';"
37 | - wget http://chromedriver.storage.googleapis.com/2.38/chromedriver_linux64.zip
38 | - unzip chromedriver_linux64.zip
39 | - sudo apt-get install libnss3
40 | - sudo cp chromedriver /usr/local/bin/
41 | - sudo chmod +x /usr/local/bin/chromedriver
42 |
--------------------------------------------------------------------------------
/spec/models/spree/stock_location_decorator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Spree::StockLocation do
4 |
5 | it { should belong_to(:supplier) }
6 |
7 | subject { create(:stock_location, backorderable_default: true) }
8 |
9 | context "propagate variants" do
10 |
11 | let(:variant) { build(:variant) }
12 | let(:stock_item) { subject.propagate_variant(variant) }
13 |
14 | context "passes backorderable default config" do
15 | context "true" do
16 | before { subject.backorderable_default = true }
17 | it { stock_item.backorderable.should be true }
18 | end
19 |
20 | context "false" do
21 | before { subject.backorderable_default = false }
22 | it { stock_item.backorderable.should be false }
23 | end
24 | end
25 |
26 | context 'does not propagate for non supplier variants' do
27 | before { subject.supplier_id = create(:supplier).id }
28 | it { stock_item.should be_nil }
29 | end
30 |
31 | end
32 |
33 | end
34 |
--------------------------------------------------------------------------------
/app/views/spree/admin/shared/_header.html.erb:
--------------------------------------------------------------------------------
1 | <%# Override header for supplier %>
2 | <% admin = try_spree_current_user.try(:supplier?) || try_spree_current_user.try(:has_spree_role?, "admin") %>
3 |
4 |
--------------------------------------------------------------------------------
/lib/spree_drop_ship/engine.rb:
--------------------------------------------------------------------------------
1 | module SpreeDropShip
2 | class Engine < Rails::Engine
3 | require 'spree/core'
4 | isolate_namespace Spree
5 | engine_name 'spree_drop_ship'
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_drop_ship.custom_splitters', after: 'spree.register.stock_splitters' do |app|
15 | app.config.spree.stock_splitters << Spree::Stock::Splitter::DropShip
16 | end
17 |
18 | initializer 'spree_drop_ship.preferences', before: :load_config_initializers do |_app|
19 | SpreeDropShip::Config = Spree::DropShipConfiguration.new
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::SupplierAbility)
27 | end
28 |
29 | config.to_prepare &method(:activate).to_proc
30 | end
31 | end
32 |
--------------------------------------------------------------------------------
/app/views/spree/admin/drop_ship_settings/edit.html.erb:
--------------------------------------------------------------------------------
1 | <% content_for :page_title do %>
2 | <%= Spree.t("drop_ship_settings") %>
3 | <% end %>
4 |
5 | <%= form_tag(spree.admin_drop_ship_settings_path, :method => :put) do %>
6 |
7 | <% %w( default_commission_flat_rate default_commission_percentage automatically_deliver_orders_to_supplier send_supplier_email).each do |key|
8 | type = SpreeDropShip::Config.preference_type(key) %>
9 |
10 | <%= label_tag(key, Spree.t(key) + ': ') + tag(:br) if type != :boolean %>
11 | <%= preference_field_tag(key, SpreeDropShip::Config[key], :type => type ,:class => 'form-control')%>
12 | <%= label_tag(key, Spree.t(key)) + tag(:br) if type == :boolean %>
13 |
14 | <% end %>
15 |
16 | <%= button Spree.t(:update), 'update' %>
17 | <%= Spree.t(:or) %>
18 | <%= button_link_to Spree.t(:cancel), spree.edit_admin_drop_ship_settings_url, :icon => 'cancel' %>
19 |
20 |
21 | <% end %>
--------------------------------------------------------------------------------
/spec/features/admin/settings_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'Admin - DropShip Settings', js: true do
4 |
5 | before do
6 | login_user create(:admin_user)
7 |
8 | visit spree.admin_path
9 | within '[data-hook=admin_tabs]' do
10 | click_link 'Configuration'
11 | end
12 | within 'ul[data-hook=admin_configurations_sidebar_menu]' do
13 | click_link 'Drop Ship Settings'
14 | end
15 | end
16 |
17 | it 'should be able to be updated' do
18 | # Change settings
19 | uncheck 'send_supplier_email'
20 | fill_in 'default_commission_flat_rate', with: 0.30
21 | fill_in 'default_commission_percentage', with: 10
22 | click_button 'Update'
23 | expect(page).to have_content('Drop ship settings successfully updated.')
24 |
25 | # Verify update saved properly by reversing checkboxes or checking field values.
26 | check 'send_supplier_email'
27 | click_button 'Update'
28 | find_field('default_commission_flat_rate').value.to_f.should eql(0.3)
29 | find_field('default_commission_percentage').value.to_f.should eql(10.0)
30 | expect(page).to have_content('Drop ship settings successfully updated.')
31 | end
32 |
33 | end
34 |
--------------------------------------------------------------------------------
/app/models/spree/product_decorator.rb:
--------------------------------------------------------------------------------
1 | module Dropship
2 | module Spree
3 | module ProductDecorator
4 | def self.prepended(base)
5 | base.has_many :suppliers, through: :master
6 | end
7 |
8 | def add_supplier!(supplier_or_id)
9 | supplier = supplier_or_id.is_a?(::Spree::Supplier) ? supplier_or_id : ::Spree::Supplier.find(supplier_or_id)
10 | populate_for_supplier! supplier if supplier
11 | end
12 |
13 | def add_suppliers!(supplier_ids)
14 | ::Spree::Supplier.where(id: supplier_ids).find_each do |supplier|
15 | populate_for_supplier! supplier
16 | end
17 | end
18 |
19 | # Returns true if the product has a drop shipping supplier.
20 | def supplier?
21 | suppliers.present?
22 | end
23 |
24 | private
25 |
26 | def populate_for_supplier!(supplier)
27 | variants_including_master.each do |variant|
28 | unless variant.suppliers.pluck(:id).include?(supplier.id)
29 | variant.suppliers << supplier
30 | supplier.stock_locations.each { |location| location.propagate_variant(variant) }
31 | end
32 | end
33 | end
34 | end
35 | end
36 | end
37 |
38 | Spree::Product.prepend Dropship::Spree::ProductDecorator
39 |
--------------------------------------------------------------------------------
/app/views/spree/admin/shipments/edit.html.erb:
--------------------------------------------------------------------------------
1 | <% content_for :page_actions do %>
2 | <%= button_link_to Spree.t(:back_to_shipments_list), spree.admin_shipments_path, icon: 'arrow-left' %>
3 | <% end %>
4 |
5 | <% if spree_current_user.admin? && @shipment.supplier.present? %>
6 |
7 |
8 | <%= Spree.t(:supplier_information) %>
9 |
10 | <%= Spree::Supplier.human_attribute_name(:name) %>: <%= @shipment.supplier.name %>
11 | <%= Spree::Supplier.human_attribute_name(:email) %>: <%= @shipment.supplier.email %>
12 | <%= Spree::Supplier.human_attribute_name(:url) %>: <%= link_to @shipment.supplier.url, @shipment.supplier.url if @shipment.supplier.url.present? %>
13 |
14 |
15 | <%= Spree.t('contact_information') %>:
16 |
17 | <%= render partial: 'spree/shared/address', locals: { address: @shipment.supplier.address } %>
18 |
19 |
20 | <% end %>
21 |
22 |
23 | <%= render partial: "spree/admin/orders/shipment", locals: { order: @shipment.order, shipment: @shipment } %>
24 |
25 |
--------------------------------------------------------------------------------
/app/models/spree/order_decorator.rb:
--------------------------------------------------------------------------------
1 | module Dropship
2 | module Spree
3 | module OrderDecorator
4 | def self.prepended(base)
5 | base.has_many :stock_locations, through: :shipments
6 | base.has_many :suppliers, through: :stock_locations
7 | end
8 |
9 | # Once order is finalized we want to notify the suppliers of their drop ship orders.
10 | # Here we are handling notification by emailing the suppliers.
11 | # If you want to customize this you could override it as a hook for notifying a supplier with a API request instead.
12 | def finalize!
13 | super
14 |
15 | shipments.each do |shipment|
16 | next unless ::SpreeDropShip::Config[:send_supplier_email] && shipment.supplier.present?
17 | begin
18 | ::Spree::DropShipOrderMailer.supplier_order(shipment.id).deliver!
19 | rescue => ex # Errno::ECONNREFUSED => ex
20 | puts ex.message
21 | puts ex.backtrace.join("\n")
22 | Rails.logger.error ex.message
23 | Rails.logger.error ex.backtrace.join("\n")
24 | return true # always return true so that failed email doesn't crash app.
25 | end
26 | end
27 | end
28 | end
29 | end
30 | end
31 |
32 | Spree::Order.prepend Dropship::Spree::OrderDecorator
33 |
--------------------------------------------------------------------------------
/app/controllers/spree/admin/suppliers_controller.rb:
--------------------------------------------------------------------------------
1 | class Spree::Admin::SuppliersController < Spree::Admin::ResourceController
2 | def edit
3 | @object.address = Spree::Address.default if @object.address.blank?
4 | respond_with(@object) do |format|
5 | format.html { render layout: !request.xhr? }
6 | format.js { render layout: false }
7 | end
8 | end
9 |
10 | def new
11 | @object = Spree::Supplier.new(address_attributes: {country_id: Spree::Address.default.country_id})
12 | end
13 |
14 | private
15 |
16 | def collection
17 | params[:q] ||= {}
18 | params[:q][:meta_sort] ||= 'name.asc'
19 | @search = Spree::Supplier.search(params[:q])
20 | @collection = @search.result.page(params[:page]).per(per_page)
21 | end
22 |
23 | def per_page
24 | params[:per_page] || admin_orders_per_page || orders_per_page
25 | end
26 |
27 | def admin_orders_per_page
28 | Spree::Config[:admin_orders_per_page] if Spree::Config.has_preference?(:admin_orders_per_page)
29 | end
30 |
31 | def orders_per_page
32 | Spree::Config[:orders_per_page] if Spree::Config.has_preference?(:orders_per_page)
33 | end
34 |
35 | def find_resource
36 | Spree::Supplier.friendly.find(params[:id])
37 | end
38 |
39 | def location_after_save
40 | spree.edit_admin_supplier_path(@object)
41 | end
42 | end
43 |
--------------------------------------------------------------------------------
/app/models/spree/shipment_decorator.rb:
--------------------------------------------------------------------------------
1 | module Dropship
2 | module Spree
3 | module ShipmentDecorator
4 | def self.prepended(base)
5 | # TODO: here to fix cancan issue thinking its just Order
6 | base.belongs_to :order, class_name: 'Spree::Order', touch: true, inverse_of: :shipments
7 |
8 | base.has_many :payments, as: :payable
9 |
10 | base.scope :by_supplier, ->(supplier_id) { base.joins(:stock_location).where(spree_stock_locations: { supplier_id: supplier_id }) }
11 |
12 | base.delegate :supplier, to: :stock_location
13 | end
14 |
15 | def display_final_price_with_items
16 | ::Spree::Money.new final_price_with_items
17 | end
18 |
19 | def final_price_with_items
20 | item_cost + final_price
21 | end
22 |
23 | # TODO: move commission to spree_marketplace?
24 | def supplier_commission_total
25 | ((final_price_with_items * supplier.commission_percentage / 100) + supplier.commission_flat_rate)
26 | end
27 |
28 | private
29 |
30 | def after_ship
31 | super
32 |
33 | update_commission if supplier.present?
34 | end
35 |
36 | def update_commission
37 | update_column :supplier_commission, supplier_commission_total
38 | end
39 | end
40 | end
41 | end
42 |
43 | Spree::Shipment.prepend Dropship::Spree::ShipmentDecorator
44 |
--------------------------------------------------------------------------------
/app/models/spree/supplier_ability.rb:
--------------------------------------------------------------------------------
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 | # TODO: Want this to be inline like:
10 | # can [:admin, :read, :stock], Spree::Product, suppliers: { id: user.supplier_id }
11 | # can [:admin, :read, :stock], Spree::Product, supplier_ids: user.supplier_id
12 | can [:admin, :read, :stock], Spree::Product do |product|
13 | product.supplier_ids.include?(user.supplier_id)
14 | end
15 | can %i[admin index], Spree::Product
16 | can %i[admin index], Spree::Stock
17 | can %i[admin manage read ready ship], Spree::Shipment, order: { state: 'complete' }, stock_location: { supplier_id: user.supplier_id }
18 | can %i[admin create update], :stock_items
19 | can %i[admin manage], Spree::StockItem, stock_location_id: user.supplier.stock_locations.pluck(:id)
20 | can %i[admin manage], Spree::StockLocation, supplier_id: user.supplier_id
21 | can :create, Spree::StockLocation
22 | can %i[admin manage], Spree::StockMovement, stock_item: { stock_location_id: user.supplier.stock_locations.pluck(:id) }
23 | can :create, Spree::StockMovement
24 | can %i[admin update], Spree::Supplier, id: user.supplier_id
25 | end
26 | end
27 | end
28 | end
29 |
--------------------------------------------------------------------------------
/lib/generators/spree_drop_ship/install/install_generator.rb:
--------------------------------------------------------------------------------
1 | module SpreeDropShip
2 | module Generators
3 | class InstallGenerator < Rails::Generators::Base
4 | class_option :auto_run_migrations, type: :boolean, default: false
5 |
6 | def add_javascripts
7 | append_file 'vendor/assets/javascripts/spree/frontend/all.js', "//= require spree/frontend/spree_drop_ship\n"
8 | append_file 'vendor/assets/javascripts/spree/backend/all.js', "//= require spree/backend/spree_drop_ship\n"
9 | end
10 |
11 | def add_stylesheets
12 | inject_into_file 'vendor/assets/stylesheets/spree/frontend/all.css', " *= require spree/frontend/spree_drop_ship\n", before: /\*\//, verbose: true
13 | inject_into_file 'vendor/assets/stylesheets/spree/backend/all.css', " *= require spree/backend/spree_drop_ship\n", before: /\*\//, verbose: true
14 | end
15 |
16 | def add_migrations
17 | run 'bundle exec rake railties:install:migrations FROM=spree_drop_ship'
18 | end
19 |
20 | def run_migrations
21 | run_migrations = options[:auto_run_migrations] || ['', 'y', 'Y'].include?(ask('Would you like to run the migrations now? [Y/n]'))
22 | if run_migrations
23 | run 'bundle exec rake db:migrate'
24 | else
25 | puts 'Skipping rake db:migrate, don\'t forget to run it!'
26 | end
27 | end
28 | end
29 | end
30 | end
31 |
--------------------------------------------------------------------------------
/app/overrides/spree/admin/products/index/override_rows.html.erb.deface:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%# Changed classes to new like https://github.com/spree/spree/blob/master/backend/app/views/spree/admin/stock_locations/_form.html.erb %>
4 | <%= product.sku rescue '' %>
5 | <%= mini_image product, class: "thumbnail" %>
6 |
7 | <% if try_spree_current_user && try_spree_current_user.supplier? %>
8 | <%= link_to product.try(:name), stock_admin_product_path(product)%>
9 | <% else %>
10 | <%= link_to product.try(:name), edit_admin_product_path(product) %>
11 | <% end %>
12 |
13 | <%= product.display_price.to_html rescue '' %>
14 |
15 | <% if try_spree_current_user && try_spree_current_user.supplier? %>
16 | <%= link_to 'SM', stock_admin_product_path(product), title:'Stock Management', class: 'btn btn-default btn-sm with-tip without-textdecor' if can?(:stock, product) && !product.deleted? %>
17 | <% else %>
18 | <%= link_to_edit product, :no_text => true, :class => 'edit' if can?(:edit, product) && !product.deleted? %>
19 | <% end %>
20 | <%= link_to_clone product, :no_text => true, :class => 'clone' if can?(:clone, product) %>
21 | <%= link_to_delete product, :no_text => true if can?(:delete, product) && !product.deleted? %>
22 |
23 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2012-2013 Jeff Dutil
2 | All rights reserved.
3 |
4 | Redistribution and use in source and binary forms, with or without modification,
5 | are permitted provided that the following conditions are met:
6 |
7 | * Redistributions of source code must retain the above copyright notice,
8 | this list of conditions and the following disclaimer.
9 | * Redistributions in binary form must reproduce the above copyright notice,
10 | this list of conditions and the following disclaimer in the documentation
11 | and/or other materials provided with the distribution.
12 | * Neither the name Spree nor the names of its contributors may be used to
13 | endorse or promote products derived from this software without specific
14 | prior written permission.
15 |
16 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
17 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
18 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
19 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
20 | CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
21 | EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
22 | PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
23 | PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
24 | LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
25 | NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
26 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27 |
--------------------------------------------------------------------------------
/spec/models/spree/shipment_decorator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Spree::Shipment do
4 |
5 | describe 'Scopes' do
6 |
7 | it '#by_supplier' do
8 | supplier = create(:supplier)
9 | stock_location_1 = supplier.stock_locations.first
10 | stock_location_2 = create(:stock_location, supplier: supplier)
11 | shipment_1 = create(:shipment)
12 | shipment_2 = create(:shipment, stock_location: stock_location_1)
13 | shipment_3 = create(:shipment)
14 | shipment_4 = create(:shipment, stock_location: stock_location_2)
15 | shipment_5 = create(:shipment)
16 | shipment_6 = create(:shipment, stock_location: stock_location_1)
17 |
18 | expect(subject.class.by_supplier(supplier.id)).to match_array([shipment_2, shipment_4, shipment_6])
19 | end
20 |
21 | end
22 |
23 | describe '#after_ship' do
24 | before do
25 | allow_any_instance_of(Spree::ShipmentHandler).to receive(:send_shipped_email)
26 | end
27 |
28 | it 'should capture payment if balance due' do
29 | skip 'TODO make it so!'
30 | end
31 |
32 | it 'should track commission for shipment' do
33 | supplier = create(:supplier_with_commission)
34 | shipment = create(:shipment, stock_location: supplier.stock_locations.first)
35 |
36 | expect(shipment.supplier_commission.to_f).to eql(0.0)
37 | shipment.stub final_price_with_items: 10.0
38 | shipment.send(:after_ship)
39 | expect(shipment.reload.supplier_commission.to_f).to eql(1.5)
40 | end
41 |
42 | end
43 |
44 | it '#final_price_with_items' do
45 | shipment = build :shipment
46 | shipment.stub item_cost: 50.0, final_price: 5.5
47 | expect(shipment.final_price_with_items.to_f).to eql(55.5)
48 | end
49 |
50 | end
51 |
--------------------------------------------------------------------------------
/spree_drop_ship.gemspec:
--------------------------------------------------------------------------------
1 | Gem::Specification.new do |s|
2 | s.platform = Gem::Platform::RUBY
3 | s.name = 'spree_drop_ship'
4 | s.version = '3.0.1.beta'
5 | s.summary = 'Spree Drop Shipping Extension'
6 | s.description = 'Adds drop shipping functionality to Spree stores.'
7 | s.required_ruby_version = '>= 2.0.0'
8 |
9 | s.author = 'Jeff Dutil'
10 | s.email = 'JDutil@BurlingtonWebApps.com'
11 | s.homepage = 'http://github.com/JDutil/spree_drop_ship'
12 |
13 | s.files = `git ls-files`.split("\n")
14 | s.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
15 | s.require_path = 'lib'
16 | s.requirements << 'none'
17 |
18 |
19 | s.add_dependency 'spree_api', '>= 3.1.0', '< 4.0'
20 | s.add_dependency 'spree_backend', '>= 3.1.0', '< 4.0'
21 | s.add_dependency 'spree_core', '>= 3.1.0', '< 4.0'
22 | s.add_dependency 'spree_extension'
23 |
24 | s.add_development_dependency 'appraisal'
25 | s.add_development_dependency 'capybara'
26 | s.add_development_dependency 'coffee-rails'
27 | s.add_development_dependency 'coveralls'
28 | s.add_development_dependency 'database_cleaner'
29 | s.add_development_dependency 'factory_bot_rails'
30 | s.add_development_dependency 'ffaker'
31 | s.add_development_dependency 'launchy'
32 | s.add_development_dependency 'mysql2', '~> 0.3.18'
33 | s.add_development_dependency 'pg' , '~> 0.18'
34 | s.add_development_dependency 'puma'
35 | s.add_development_dependency 'rspec-rails'
36 | s.add_development_dependency 'sass-rails'
37 | s.add_development_dependency 'selenium-webdriver'
38 | s.add_development_dependency 'shoulda-matchers'
39 | s.add_development_dependency 'spree_auth_devise'
40 | s.add_development_dependency 'spree_sample'
41 | s.add_development_dependency 'sqlite3'
42 | end
43 |
--------------------------------------------------------------------------------
/app/models/spree/stock/splitter/drop_ship.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Stock
3 | module Splitter
4 | class DropShip < Spree::Stock::Splitter::Base
5 | def split(packages)
6 | split_packages = []
7 | packages.each do |package|
8 | # Package fulfilled items together.
9 | fulfilled = package.contents.select { |content| content.variant.suppliers.count == 0 }
10 | split_packages << build_package(fulfilled)
11 | # Determine which supplier to package drop shipped items.
12 | drop_ship = package.contents.select { |content| content.variant.suppliers.count > 0 }
13 | drop_ship.each do |content|
14 | # Select the related variant
15 | variant = content.variant
16 | # Select suppliers ordering ascending according to cost.
17 | suppliers = variant.supplier_variants.order('spree_supplier_variants.cost ASC').map(&:supplier)
18 | # Select first supplier that has stock location with avialable stock item.
19 | available_supplier = suppliers.find do |supplier|
20 | supplier.stock_locations_with_available_stock_items(variant).any?
21 | end
22 | # Select the first available stock location with in the available_supplier stock locations.
23 | stock_location = available_supplier.stock_locations_with_available_stock_items(variant).first
24 | # Add to any existing packages or create a new one.
25 | if existing_package = split_packages.find { |p| p.stock_location == stock_location }
26 | existing_package.contents << content
27 | else
28 | split_packages << Spree::Stock::Package.new(stock_location, [content])
29 | end
30 | end
31 | end
32 | return_next split_packages
33 | end
34 | end
35 | end
36 | end
37 | end
38 |
--------------------------------------------------------------------------------
/spec/models/spree/order_decorator_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Spree::Order do
4 |
5 | context '#finalize_with_drop_ship!' do
6 |
7 | after do
8 | SpreeDropShip::Config[:send_supplier_email] = true
9 | end
10 |
11 | it 'should deliver drop ship orders when Spree::DropShipConfig[:send_supplier_email] == true' do
12 | order = create(:order_with_totals, ship_address: create(:address))
13 | order.line_items = [create(:line_item, variant: create(:variant_with_supplier)), create(:line_item, variant: create(:variant_with_supplier))]
14 | order.create_proposed_shipments
15 |
16 | order.shipments.each do |shipment|
17 | Spree::DropShipOrderMailer.should_receive(:supplier_order).with(shipment.id).and_return(double(Mail, :deliver! => true))
18 | end
19 |
20 | order.finalize!
21 | order.reload
22 |
23 | # Check orders are properly split.
24 | order.shipments.size.should eql(2)
25 | order.shipments.each do |shipment|
26 | shipment.line_items.size.should eql(1)
27 | shipment.line_items.first.variant.suppliers.first.should eql(shipment.supplier)
28 | end
29 | end
30 |
31 | it 'should NOT deliver drop ship orders when Spree::DropShipConfig[:send_supplier_email] == false' do
32 | SpreeDropShip::Config[:send_supplier_email] = false
33 | order = create(:order_with_totals, ship_address: create(:address))
34 | order.line_items = [create(:line_item, variant: create(:variant_with_supplier)), create(:line_item, variant: create(:variant_with_supplier))]
35 | order.create_proposed_shipments
36 |
37 | order.shipments.each do |shipment|
38 | Spree::DropShipOrderMailer.should_not_receive(:supplier_order).with(shipment.id)
39 | end
40 |
41 | order.finalize!
42 | order.reload
43 |
44 | # Check orders are properly split.
45 | order.shipments.size.should eql(2)
46 | order.shipments.each do |shipment|
47 | shipment.line_items.size.should eql(1)
48 | shipment.line_items.first.variant.suppliers.first.should eql(shipment.supplier)
49 | end
50 | end
51 |
52 | end
53 |
54 | end
55 |
--------------------------------------------------------------------------------
/spec/models/spree/stock/splitter/drop_ship_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | module Spree
4 | module Stock
5 | module Splitter
6 | describe DropShip do
7 |
8 | let(:supplier_1) { create(:supplier) }
9 | let(:supplier_2) { create(:supplier) }
10 |
11 | let(:variant_1) {
12 | v = create(:variant)
13 | v.product.add_supplier! supplier_1
14 | v.reload.supplier_variants.find_by_supplier_id(supplier_1.id).update_column(:cost, 5)
15 | v.product.add_supplier! supplier_2
16 | v.reload.supplier_variants.find_by_supplier_id(supplier_2.id).update_column(:cost, 6)
17 | v
18 | }
19 | let(:variant_2) {
20 | v = create(:variant)
21 | v.product.add_supplier! supplier_1
22 | v.reload.supplier_variants.find_by_supplier_id(supplier_1.id).update_column(:cost, 5)
23 | v.product.add_supplier! supplier_2
24 | v.reload.supplier_variants.find_by_supplier_id(supplier_2.id).update_column(:cost, 4)
25 | v
26 | }
27 | let(:variant_3) {
28 | v = create(:variant)
29 | v.product.add_supplier! supplier_1
30 | v.product.add_supplier! supplier_2
31 | v.reload
32 | }
33 | let(:variant_4) { create(:variant) }
34 |
35 | let(:variants){
36 | [variant_1, variant_2, variant_3, variant_4]
37 | }
38 |
39 | let(:packer) { build(:stock_packer) }
40 |
41 | subject { DropShip.new(packer) }
42 |
43 | it 'splits packages for drop ship' do
44 | package = Package.new(packer.stock_location)
45 | package = Package.new(packer.stock_location)
46 | 4.times { |i| package.add build(:inventory_unit, variant: variants[i]) }
47 |
48 | packages = subject.split([package])
49 | packages.count.should eq 3
50 |
51 | expect(packages[0].stock_location).to eq(packer.stock_location)
52 | expect(packages[0].contents.count).to eq(1)
53 | expect(packages[1].stock_location).to eq(supplier_1.stock_locations.first)
54 | expect(packages[1].contents.count).to eq(2)
55 | expect(packages[2].stock_location).to eq(supplier_2.stock_locations.first)
56 | expect(packages[2].contents.count).to eq(1)
57 | end
58 |
59 | end
60 | end
61 | end
62 | end
63 |
--------------------------------------------------------------------------------
/app/controllers/spree/admin/shipments_controller.rb:
--------------------------------------------------------------------------------
1 | module Spree
2 | module Admin
3 | class ShipmentsController < Spree::Admin::ResourceController
4 | def index
5 | params[:q] ||= {}
6 | # params[:q][:completed_at_null] ||= '1'
7 | # @show_only_incomplete = params[:q][:completed_at_null].present?
8 | # params[:q][:s] ||= @show_only_incomplete ? 'created_at desc' : 'completed_at desc'
9 |
10 | # As date params are deleted if @show_only_incomplete, store
11 | # the original date so we can restore them into the params
12 | # after the search
13 | created_at_gt = params[:q][:created_at_gt]
14 | created_at_lt = params[:q][:created_at_lt]
15 |
16 | if params[:q][:created_at_gt].present?
17 | params[:q][:created_at_gt] = begin
18 | Time.zone.parse(params[:q][:created_at_gt]).beginning_of_day
19 | rescue
20 | ''
21 | end
22 | end
23 |
24 | if params[:q][:created_at_lt].present?
25 | params[:q][:created_at_lt] = begin
26 | Time.zone.parse(params[:q][:created_at_lt]).end_of_day
27 | rescue
28 | ''
29 | end
30 | end
31 |
32 | @search = Spree::Shipment.accessible_by(current_ability, :index).ransack(params[:q])
33 | @shipments = @search.result
34 | .page(params[:page])
35 | .per(per_page)
36 |
37 | # Restore dates
38 | params[:q][:created_at_gt] = created_at_gt
39 | params[:q][:created_at_lt] = created_at_lt
40 | end
41 |
42 | private
43 |
44 | def per_page
45 | params[:per_page] || admin_orders_per_page || orders_per_page
46 | end
47 |
48 | def admin_orders_per_page
49 | Spree::Config[:admin_orders_per_page] if Spree::Config.has_preference?(:admin_orders_per_page)
50 | end
51 |
52 | def orders_per_page
53 | Spree::Config[:orders_per_page] if Spree::Config.has_preference?(:orders_per_page)
54 | end
55 |
56 | def find_resource
57 | if parent_data.present?
58 | parent.send(controller_name).find_by!(number: params[:id])
59 | else
60 | model_class.find_by!(number: params[:id])
61 | end
62 | end
63 | end
64 | end
65 | end
66 |
--------------------------------------------------------------------------------
/lib/tasks/spree_sample.rake:
--------------------------------------------------------------------------------
1 | namespace :spree_sample do
2 | desc 'Create sample drop ship orders'
3 | task drop_ship_orders: :environment do
4 | if Spree::Order.count == 0
5 | puts 'Please run `rake spree_sample:load` first to create products and orders'
6 | exit
7 | end
8 |
9 | if Spree::Supplier.count == 0
10 | puts 'Please run `rake spree_sample:suppliers` first to create suppliers'
11 | exit
12 | end
13 |
14 | count = 0
15 | @orders = Spree::Order.complete.includes(:line_items).all
16 | @suppliers = Spree::Supplier.all
17 |
18 | puts 'Linking existing line items to suppliers'
19 | Spree::LineItem.all.each do |li|
20 | print '*' if li.product.add_supplier!(@suppliers.sample.id) && li.save
21 | end
22 | puts
23 |
24 | puts 'Creating drop ship orders for existing orders'
25 | Spree::Order.all.each do |order|
26 | print '*' if order.finalize!
27 | end
28 | puts
29 | end
30 |
31 | desc 'Create sample suppliers and randomly link to products'
32 | task suppliers: :environment do
33 | old_send_value = SpreeDropShip::Config[:send_supplier_email]
34 | SpreeDropShip::Config[:send_supplier_email] = false
35 |
36 | @usa = Spree::Country.find_by(iso: 'US')
37 | @ca = @usa.states.find_by(abbr: 'CA')
38 |
39 | count = Spree::Supplier.count
40 | puts 'Creating Suppliers...'
41 | 5.times do |i|
42 | name = "Supplier #{count + i + 1}"
43 | supplier = Spree::Supplier.new(name: name,
44 | email: "#{name.parameterize}@example.com",
45 | url: 'http://example.com')
46 | supplier.build_address(firstname: name, lastname: name,
47 | address1: '100 State St',
48 | city: 'Santa Barbara', zipcode: '93101',
49 | state_id: @ca.id, country_id: @usa.id,
50 | phone: '1234567890')
51 | print '*' if supplier.save
52 | end
53 | puts
54 | puts "#{Spree::Supplier.count - count} suppliers created"
55 |
56 | puts 'Randomly linking Products & Suppliers...'
57 |
58 | @supplier_ids = Spree::Supplier.pluck(:id).shuffle
59 | @products = Spree::Product.all
60 | count = 0
61 |
62 | @products.each do |product|
63 | product.add_supplier! Spree::Supplier.find(@supplier_ids[rand(@supplier_ids.length)])
64 | product.save
65 | count += 1
66 | print '*'
67 | end
68 | puts
69 | puts "#{count} products linked."
70 | SpreeDropShip::Config[:send_supplier_email] = old_send_value
71 | end
72 | end
73 |
--------------------------------------------------------------------------------
/spec/features/admin/shipments_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe 'Admin - Shipments', js: true do
4 |
5 | context 'as Supplier' do
6 |
7 | let!(:order) { build(:order_ready_for_drop_ship, state: 'complete', completed_at: "2011-02-01 12:36:15", number: "R100") }
8 | let!(:supplier) { create(:supplier) }
9 |
10 | let!(:product) {
11 | p = create(:product, name: 'spree t-shirt', price: 20.00)
12 | p.add_supplier! supplier.id
13 | p
14 | }
15 |
16 | let!(:shipment) { create(:shipment, order: order, stock_location: supplier.stock_locations.first) }
17 | let!(:shipping_method) { create(:shipping_method, name: "Default") }
18 |
19 | before do
20 | # Adjust qoh so shipment will be ready
21 | shipment.stock_location.stock_items.where(variant_id: product.master.id).first.adjust_count_on_hand(10)
22 | # Add product and update shipment
23 | order.contents.add(product.master, 2)
24 | shipment.refresh_rates
25 | shipment.update!(order)
26 | shipment.update_amounts
27 |
28 | # TODO this is a hack until capture_on_dispatch finished https://github.com/spree/spree/issues/4727
29 | shipment.update_attribute :state, 'ready'
30 |
31 | user = create(:user, supplier: supplier)
32 | user.generate_spree_api_key!
33 | login_user user
34 |
35 | visit spree.edit_admin_shipment_path(shipment)
36 | end
37 |
38 | context 'edit page' do
39 |
40 | it "can add tracking information" do
41 | within '.table tr.show-tracking' do
42 | click_icon :edit
43 | end
44 | within '.table tr.edit-tracking' do
45 | fill_in "tracking", with: "FOOBAR"
46 | click_icon :save
47 | end
48 | wait_for_ajax
49 | within '.table tr.show-tracking' do
50 | page.should have_content("Tracking: FOOBAR")
51 | end
52 | end
53 |
54 | it "can change the shipping method" do
55 | within(".table tr.show-method") do
56 | click_icon :edit
57 | end
58 | select2 "Default", from: "Shipping Method"
59 | click_icon :save
60 | wait_for_ajax
61 |
62 | page.should have_content("Default $0.00")
63 | end
64 |
65 | it "can ship a completed order" do
66 | click_on "Ship"
67 | wait_for_ajax
68 |
69 | page.should have_content("shipped package")
70 | order.reload.shipment_state.should == "shipped"
71 | end
72 | end
73 |
74 | it 'should render unauthorized visiting another suppliers shipment' do
75 | visit spree.edit_admin_shipment_path(create(:shipment))
76 | page.should have_content('Authorization Failure')
77 | end
78 | end
79 |
80 | end
81 |
--------------------------------------------------------------------------------
/app/views/spree/supplier_mailer/welcome.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | <%= link_to Spree::Store.current.name, spree.root_url %>
10 |
11 |
12 |
13 |
14 | <%= Spree.t('supplier_mailer.welcome.hello', name: @supplier.name ) %>
15 |
16 | <%= Spree.t('supplier_mailer.welcome.manage_your_account' ) %>
17 | <%= link_to Spree.t('supplier_mailer.welcome.logging_into_your_account'), spree.login_url, target: '_blank' %>
18 |
19 |
20 |
21 |
22 |
23 | <%= Spree.t('supplier_mailer.welcome.please_verify_your_information') %>
24 |
25 |
26 |
27 | <%= Spree.t('supplier_mailer.welcome.supplier_information') %>
28 |
29 |
30 |
31 |
32 |
33 | <%= Spree.t(:name) %>: <%= @supplier.name %>
34 | <%= Spree.t(:email) %>: <%= mail_to @supplier.email %>
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
44 | <%= raw Spree.t('supplier_mailer.welcome.thank_you_again', name: Spree::Store.current.name) %>
45 |
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/lib/spree_drop_ship/factories.rb:
--------------------------------------------------------------------------------
1 | FactoryBot.define do
2 | factory :order_for_drop_ship, parent: :order do
3 | bill_address
4 | ship_address
5 |
6 | transient do
7 | line_items_count 5
8 | end
9 |
10 | after(:create) do |order, evaluator|
11 | supplier = create(:supplier)
12 | product = create(:product)
13 | product.add_supplier! supplier
14 | # product.stock_items.where(variant_id: product.master.id).first.adjust_count_on_hand(10)
15 |
16 | product_2 = create(:product)
17 | product_2.add_supplier! create(:supplier)
18 |
19 | create_list(:line_item, evaluator.line_items_count,
20 | order: order,
21 | variant: product_2.master)
22 | order.line_items.reload
23 |
24 | create(:shipment, order: order, stock_location: supplier.stock_locations.first)
25 | order.shipments.reload
26 |
27 | order.update_with_updater!
28 | end
29 |
30 | factory :completed_order_for_drop_ship_with_totals do
31 | state 'complete'
32 |
33 | after(:create) do |order|
34 | order.refresh_shipment_rates
35 | order.update_column(:completed_at, Time.now)
36 | end
37 |
38 | factory :order_ready_for_drop_ship do
39 | payment_state 'paid'
40 | shipment_state 'ready'
41 |
42 | after(:create) do |order|
43 | create(:payment, amount: order.total, order: order, state: 'completed')
44 | order.shipments.each do |shipment|
45 | shipment.inventory_units.each { |u| u.update_column('state', 'on_hand') }
46 | shipment.update_column('state', 'ready')
47 | end
48 | order.reload
49 | end
50 |
51 | factory :shipped_order_for_drop_ship do
52 | after(:create) do |order|
53 | order.shipments.each do |shipment|
54 | shipment.inventory_units.each { |u| u.update_column('state', 'shipped') }
55 | shipment.update_column('state', 'shipped')
56 | end
57 | order.reload
58 | end
59 | end
60 | end
61 | end
62 | end
63 |
64 | factory :supplier, class: Spree::Supplier do
65 | sequence(:name) { |i| "Big Store #{i}" }
66 | email { FFaker::Internet.email }
67 | url 'http://example.com'
68 | address
69 | # Creating a stock location with a factory instead of letting the model handle it
70 | # so that we can run tests with backorderable defaulting to true.
71 | before :create do |supplier|
72 | supplier.stock_locations << build(:stock_location, name: supplier.name, supplier: supplier)
73 | end
74 |
75 | factory :supplier_with_commission do
76 | commission_flat_rate 0.5
77 | commission_percentage 10
78 | end
79 | end
80 |
81 | factory :supplier_user, parent: :user do
82 | supplier
83 | end
84 |
85 | factory :variant_with_supplier, parent: :variant do
86 | after :create do |variant|
87 | variant.product.add_supplier! create(:supplier)
88 | end
89 | end
90 | end
91 |
--------------------------------------------------------------------------------
/app/views/spree/admin/suppliers/index.html.erb:
--------------------------------------------------------------------------------
1 | <%# Updated icon %>
2 | <% content_for :page_actions do %>
3 | <%= button_link_to Spree.t('new_supplier'), new_object_url, icon: 'add', class: 'btn-success' %>
4 | <% end %>
5 |
6 | <% content_for :page_title do %>
7 | <%= Spree.t(:listing_suppliers) %>
8 | <% end %>
9 |
10 | <% content_for :table_filter_title do %>
11 | <%= Spree.t(:search) %>
12 | <% end %>
13 |
14 | <% content_for :table_filter do %>
15 |
16 | <%= search_form_for [:admin, @search] do |f| %>
17 |
18 | <%= f.label :name_cont, Spree.t(:name) %>
19 | <%= f.text_field :name_cont, :size => 15, class: "form-control" %>
20 |
21 |
22 |
23 | <%= button Spree.t(:search), 'search' %>
24 |
25 | <% end %>
26 |
27 | <% end %>
28 |
29 | <%# This is a new paginate! %>
30 | <%= render :partial => 'spree/admin/shared/index_table_options', :locals => { :collection => @collection } %>
31 |
32 | <% if @collection.any? %>
33 |
34 |
35 |
36 | <%= sort_link @search, :name, Spree::Supplier.human_attribute_name(:name) %>
37 | <%= sort_link @search, :email, Spree::Supplier.human_attribute_name(:email) %>
38 | <%= Spree.t(:users) %>
39 | <%= sort_link @search, :active, Spree::Supplier.human_attribute_name(:active) %>
40 |
41 |
42 |
43 |
44 | <%# Updated classes! %>
45 | <%- @collection.each do |supplier| %>
46 | id="<%= spree_dom_id supplier %>" data-hook="admin_suppliers_index_rows" class="<%= cycle('odd', 'even') %>">
47 |
48 | <%= link_to supplier.name, edit_object_url(supplier) %>
49 | <%= mail_to supplier.email %>
50 | <%= supplier.users.pluck(:email).join(', ') %>
51 | <%= supplier.active %>
52 |
53 | <% unless supplier.deleted? %>
54 | <%= link_to_edit supplier, :no_text => true, :class => 'edit' %>
55 | <%= link_to_delete supplier, :no_text => true %>
56 | <% end %>
57 |
58 |
59 | <% end %>
60 |
61 |
62 | <% else %>
63 | <%# New alert with link to add new supplier %>
64 |
65 | <%= Spree.t(:no_resource_found, resource: Spree::Supplier.model_name.human(count: :many)) %>,
66 | <%= link_to Spree.t(:add_one), spree.new_admin_supplier_path %>!
67 |
68 | <% end %>
69 |
70 | <%# This is a new paginate! %>
71 | <%= render :partial => 'spree/admin/shared/index_table_options', :locals => { :collection => @collection } %>
72 |
--------------------------------------------------------------------------------
/spec/features/admin/stock_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | feature 'Admin - Product Stock Management', js: true do
4 |
5 | before do
6 | @user = create(:supplier_user)
7 | @supplier1 = @user.supplier
8 | @supplier2 = create(:supplier)
9 | @product = create :product
10 | @product.add_supplier! @supplier1
11 | end
12 |
13 | context 'as Admin' do
14 |
15 | scenario 'should display all existing stock item locations' do
16 | login_user create(:admin_user)
17 | visit spree.stock_admin_product_path(@product)
18 |
19 | within '.stock_location_info' do
20 | page.should have_content(@supplier1.name)
21 | # Stock item doesn't exist
22 | page.should_not have_content(@supplier2.name)
23 | end
24 | end
25 |
26 | end
27 |
28 | context 'as Supplier' do
29 |
30 | before(:each) do
31 | login_user @user
32 | visit '/admin/products'
33 | click_link "Stock Locations"
34 | end
35 |
36 | scenario 'should only display suppliers stock locations' do
37 | visit spree.stock_admin_product_path(@product)
38 |
39 | within '.stock_location_info' do
40 | page.should have_content(@supplier1.name)
41 | page.should_not have_content(@supplier2.name)
42 | end
43 | end
44 |
45 | scenario "can create a new stock location" do
46 | visit spree.new_admin_stock_location_path
47 | fill_in "Name", with: "London"
48 | check "Active"
49 | click_button "Create"
50 |
51 | page.should have_content("successfully created")
52 | page.should have_content("London")
53 | end
54 |
55 | scenario "can delete an existing stock location", js: true do
56 | stock_location = create(:stock_location, supplier: @user.supplier)
57 | visit current_path
58 |
59 | find('#listing_stock_locations').should have_content("NY Warehouse")
60 | within("#spree_stock_location_#{stock_location.id}") { click_icon :delete }
61 | page.driver.browser.switch_to.alert.accept
62 | # Wait for API request to complete.
63 | sleep(1)
64 |
65 | visit current_path
66 |
67 | find('#listing_stock_locations').should_not have_content("NY Warehouse")
68 | end
69 |
70 | scenario "can update an existing stock location" do
71 | create(:stock_location, supplier: @user.supplier)
72 | visit current_path
73 |
74 | page.should have_content("Big Store")
75 |
76 | within_row(1) { click_icon :edit }
77 | fill_in "Name", with: "London"
78 | click_button "Update"
79 |
80 | page.should have_content("successfully updated")
81 | page.should have_content("London")
82 | end
83 |
84 | scenario "can deactivate an existing stock location" do
85 | create(:stock_location, supplier: @user.supplier)
86 | visit current_path
87 |
88 | page.should have_content("Big Store")
89 |
90 | within_row(1) { click_icon :edit }
91 | uncheck "Active"
92 | click_button "Update"
93 |
94 | find('#listing_stock_locations').should have_content("Inactive")
95 | end
96 |
97 | end
98 |
99 | end
100 |
--------------------------------------------------------------------------------
/app/views/spree/admin/suppliers/_form.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 | <%= Spree.t('contact_address') %>
4 | <%= form.fields_for :address do |contact_form| %>
5 | <%= render :partial => 'spree/admin/shared/address_form', :locals => { :f => contact_form, :name => Spree.t(:contact_address), :type => "shipping" } %>
6 | <% end %>
7 |
8 |
9 |
10 | <%# added bootstrap classes for 'field_container' and '' %>
11 |
12 |
13 | <%= Spree.t('supplier_details') %>
14 | <%= form.field_container :name, class: ['form-group'] do %>
15 | <%= form.label :name, Spree::Supplier.human_attribute_name(:name) %>:
16 | <%= form.text_field :name, :class => 'form-control' %>
17 | <% end %>
18 | <%= form.field_container :email, class: ['form-group'] do %>
19 | <%= form.label :email, Spree::Supplier.human_attribute_name(:email) %>:
20 | <%= form.email_field :email, :class => 'form-control' %>
21 | <%# email validation %>
22 | <%= error_message_on :user, :email %>
23 | <% end %>
24 | <%= form.field_container :url, class: ['form-group'] do %>
25 | <%= form.label :url, Spree::Supplier.human_attribute_name(:url) %>:
26 | <%= form.text_field :url, :class => 'form-control' %>
27 | <% end %>
28 | <%= form.field_container :tax_id, class: ['form-group'] do %>
29 | <%= form.label :tax_id, Spree::Supplier.human_attribute_name(:tax_id) %>:
30 | <%= form.text_field :tax_id, :class => 'form-control' %>
31 | <% end %>
32 | <% if spree_current_user.admin? %>
33 | <%= form.field_container :commission_flat_rate, class: ['form-group'] do %>
34 | <%= form.label :commission_flat_rate, Spree::Supplier.human_attribute_name(:commission_flat_rate) %>:
35 | <%= form.text_field :commission_flat_rate, :class => 'form-control' %>
36 | <% end %>
37 | <%= form.field_container :commission_percentage , class: ['form-group'] do %>
38 | <%= form.label :commission_percentage, Spree::Supplier.human_attribute_name(:commission_percentage) %>:
39 | <%= form.text_field :commission_percentage, :class => 'form-control' %>
40 | <% end %>
41 | <%= form.field_container :users, class: ['form-group'] do %>
42 | <%= form.label :users %>
43 | <%= hidden_field_tag 'supplier[user_ids_string]', form.object.user_ids.join(','), class: 'user_picker' %>
44 | <% end %>
45 | <%= form.field_container :active, class: ['form-group'] do %>
46 |
47 | <%= label_tag :active do %>
48 | <%= form.check_box :active %>
49 | <%= Spree::Supplier.human_attribute_name(:active) %>
50 | <% end %>
51 |
52 | <% end %>
53 | <% end %>
54 | <%# This moved to _edit(new)_resource_links.html.erb %>
55 |
56 | <%# if form.object.new_record? %>
57 | <%#= button Spree.t('create') %>
58 | <%# else %>
59 | <%#= button Spree.t('update') %>
60 | <%# end %>
61 | <%#= Spree.t('or') %>
62 | <%# if can? :index, Spree::Supplier %>
63 | <%#= link_to Spree.t('cancel'), collection_url %>
64 | <%# else %>
65 | <%#= link_to Spree.t('cancel'), spree.account_path %>
66 | <%# end %>
67 |
68 |
69 |
70 |
--------------------------------------------------------------------------------
/config/locales/en.yml:
--------------------------------------------------------------------------------
1 | en:
2 | activerecord:
3 | attributes:
4 | spree/supplier:
5 | commission_flat_rate: Commission Flat Rate
6 | commission_percentage: Commission Percentage
7 | name: Name
8 | tax_id: TAX ID
9 | url: URL
10 | errors:
11 | models:
12 | spree/supplier:
13 | attributes:
14 | url:
15 | invalid: is invalid. Please be sure to include include 'http://'
16 | not_responding: 'is invalid, not responding or redirecting to another location'
17 | spree:
18 | admin:
19 | drop_ship_settings:
20 | update:
21 | success: Drop ship settings successfully updated.
22 | orders:
23 | edit:
24 | approve_drop_ship_orders: Approve Drop Ship Orders
25 | resend_drop_ship_orders: Resend Drop Ship Orders
26 | drop_ship_order_approval_confirmation: This will send an email notification to each supplier. Continue?
27 | show:
28 | approve_drop_ship_orders: Approve Drop Ship Orders
29 | resend_drop_ship_orders: Resend Drop Ship Orders
30 | drop_ship_order_approval_confirmation: This will send an email notification to each supplier. Continue?
31 | back_to_orders_list: Back To Orders List
32 | back_to_suppliers: Back To Suppliers
33 | business: Business
34 | cancel: Cancel
35 | company_info: Company Info
36 | confirm_order: Confirm Order
37 | contact_address: Contact Address
38 | drop_ship: Drop Ship
39 | drop_ship_order_mailer:
40 | supplier_order:
41 | hello: "Hello %{name},"
42 | subject: "%{name} Drop Shipment #%{number}"
43 | thank_you_again: "Thank you again for your business, %{name} "
44 | drop_ship_order_number: "Drop Ship Order #%{number}"
45 | drop_ship_orders: Drop Ship Orders
46 | drop_ship_settings: Drop Ship Settings
47 | editing_supplier: Editing Supplier
48 | individual: Individual
49 | listing_suppliers: Listing Suppliers
50 | manage: Manage
51 | must_be_logged_in: 'Must Be Logged In'
52 | name: Name
53 | new_supplier: New Supplier
54 | or: or
55 | order_state:
56 | active: Active
57 | completed: Completed
58 | confirmed: Confirmed
59 | delivered: Delivered
60 | resend_order_to_supplier: Resend Order To Supplier
61 | save: Save
62 | search: Search
63 | send_order_to_supplier: Send Order To Supplier
64 | shared:
65 | unauthorized:
66 | explained: You are not authorized to access this page.
67 | unauthorized: Unauthorized
68 | show_only_incomplete_orders: Show Only Incomplete Orders
69 | signup: Sign Up
70 | signup_to_become_a_supplier: 'Sign Up To Become A Supplier'
71 | supplier: Supplier
72 | supplier_details: Supplier Details
73 | supplier_info: Supplier Info
74 | supplier_information: Supplier Information
75 | supplier_mailer:
76 | welcome:
77 | hello: "Hello %{name},"
78 | logging_into_your_account: logging into your account.
79 | manage_your_account: You may now manage your profile and inventory by
80 | subject: 'Thank you for signing up. Please verify your information.'
81 | thank_you_again: "Thank you again for your business, %{name} "
82 | thank_you_for_signing_up: Thank you for signing up to be a drop ship supplier.
83 | supplier_registration:
84 | already_signed_up: "You've already signed up to become a supplier."
85 | create:
86 | invalid_password: Invalid password please sign in or sign up.
87 | success: Thank you for signing up!
88 | supplier_signup: Supplier Sign Up
89 | suppliers: Suppliers
90 |
--------------------------------------------------------------------------------
/spec/spec_helper.rb:
--------------------------------------------------------------------------------
1 | require 'coveralls'
2 | Coveralls.wear!
3 |
4 | # Configure Rails Environment
5 | ENV['RAILS_ENV'] = 'test'
6 |
7 | require File.expand_path('../dummy/config/environment.rb', __FILE__)
8 |
9 | require 'rspec/rails'
10 | require 'database_cleaner'
11 | require 'factory_bot'
12 | require 'selenium/webdriver'
13 | FactoryBot.find_definitions
14 | require 'ffaker'
15 | require 'shoulda-matchers'
16 |
17 | # Requires supporting ruby files with custom matchers and macros, etc,
18 | # in spec/support/ and its subdirectories.
19 | Dir[File.join(File.dirname(__FILE__), 'support/**/*.rb')].each { |f| require f }
20 |
21 | require 'spree/testing_support/authorization_helpers'
22 | require 'spree/testing_support/capybara_ext'
23 | require 'spree/testing_support/controller_requests'
24 | require 'spree/testing_support/factories'
25 | require 'spree/testing_support/preferences'
26 | require 'spree/testing_support/url_helpers'
27 |
28 | require 'spree_drop_ship/factories'
29 |
30 | capabilities = Selenium::WebDriver::Remote::Capabilities.chrome(
31 | 'chromeOptions' => {
32 | 'args' => ['--headless', '--disable-gpu']
33 | }
34 | )
35 |
36 | Capybara.register_driver :chrome do |app|
37 | Capybara::Selenium::Driver.new(app, browser: :chrome, desired_capabilities: capabilities)
38 | end
39 |
40 | Capybara.javascript_driver = :chrome
41 |
42 | Shoulda::Matchers.configure do |config|
43 | config.integrate do |with|
44 | with.test_framework :rspec
45 | with.library :active_model
46 | with.library :active_record
47 | end
48 | end
49 |
50 | RSpec.configure do |config|
51 | config.include FactoryBot::Syntax::Methods
52 | config.include IntegrationHelpers
53 | config.include Spree::TestingSupport::Preferences
54 |
55 | # == URL Helpers
56 | #
57 | # Allows access to Spree's routes in specs:
58 | #
59 | # visit spree.admin_path
60 | # current_path.should eql(spree.products_path)
61 | config.include Spree::TestingSupport::UrlHelpers
62 | config.include Spree::TestingSupport::ControllerRequests, type: :controller
63 |
64 | # Capybara javascript drivers require transactional fixtures set to false, and we use DatabaseCleaner
65 | # to cleanup after each test instead. Without transactional fixtures set to false the records created
66 | # to setup a test will be unavailable to the browser, which runs under a seperate server instance.
67 | config.use_transactional_fixtures = false
68 |
69 | # Ensure Suite is set to use transactions for speed.
70 | config.before :suite do
71 | DatabaseCleaner.strategy = :transaction
72 | DatabaseCleaner.clean_with :truncation
73 | end
74 |
75 | # Before each spec check if it is a Javascript test and switch between using database transactions or not where necessary.
76 | config.before :each do
77 | DatabaseCleaner.strategy = RSpec.current_example.metadata[:js] ? :truncation : :transaction
78 | DatabaseCleaner.start
79 | reset_spree_preferences
80 | end
81 |
82 | # After each spec clean the database.
83 | config.after :each do
84 | DatabaseCleaner.clean
85 | end
86 |
87 | # If true, the base class of anonymous controllers will be inferred
88 | # automatically. This will be the default behavior in future versions of
89 | # rspec-rails.
90 | config.infer_base_class_for_anonymous_controllers = false
91 |
92 | config.infer_spec_type_from_file_location!
93 | # Run specs in random order to surface order dependencies. If you find an
94 | # order dependency and want to debug it, you can fix the order by providing
95 | # the seed, which is printed after each run.
96 | # --seed 1234
97 | # config.order = "random"
98 | config.color = true
99 | config.infer_spec_type_from_file_location!
100 | end
101 |
--------------------------------------------------------------------------------
/app/models/spree/supplier.rb:
--------------------------------------------------------------------------------
1 | class Spree::Supplier < Spree::Base
2 | extend FriendlyId
3 | friendly_id :name, use: :slugged
4 |
5 | attr_accessor :password, :password_confirmation
6 |
7 | #==========================================
8 | # Associations
9 |
10 | belongs_to :address, class_name: 'Spree::Address'
11 | accepts_nested_attributes_for :address
12 |
13 | if defined?(Ckeditor::Asset)
14 | has_many :ckeditor_pictures
15 | has_many :ckeditor_attachment_files
16 | end
17 | has_many :products, through: :variants
18 | has_many :stock_locations
19 | has_many :shipments, through: :stock_locations
20 | has_many :supplier_variants
21 | has_many :users, class_name: Spree.user_class.to_s
22 | has_many :variants, through: :supplier_variants
23 |
24 | #==========================================
25 | # Validations
26 |
27 | validates :commission_flat_rate, presence: true
28 | validates :commission_percentage, presence: true
29 | validates :email, presence: true, email: true, uniqueness: true
30 | validates :name, presence: true, uniqueness: true
31 | validates :url, format: { with: URI.regexp(%w[http https]), allow_blank: true }
32 |
33 | #==========================================
34 | # Callbacks
35 |
36 | after_create :assign_user
37 | after_create :create_stock_location
38 | after_create :send_welcome, if: -> { SpreeDropShip::Config[:send_supplier_email] }
39 | before_create :set_commission
40 | before_validation :check_url
41 |
42 | #==========================================
43 | # Instance Methods
44 | scope :active, -> { where(active: true) }
45 |
46 | def deleted?
47 | deleted_at.present?
48 | end
49 |
50 | def user_ids_string
51 | user_ids.join(',')
52 | end
53 |
54 | def user_ids_string=(s)
55 | self.user_ids = s.to_s.split(',').map(&:strip)
56 | end
57 |
58 | # Retreive the stock locations that has available
59 | # stock items of the given variant
60 | def stock_locations_with_available_stock_items(variant)
61 | stock_locations.select { |sl| sl.available?(variant) }
62 | end
63 |
64 | #==========================================
65 | # Protected Methods
66 |
67 | protected
68 |
69 | def assign_user
70 | if users.empty?
71 | if user = Spree.user_class.find_by(email: email)
72 | users << user
73 | save
74 | end
75 | end
76 | end
77 |
78 | def check_url
79 | unless url.blank? || url =~URI.regexp(%w[http https])
80 | self.url = "http://#{url}"
81 | end
82 | end
83 |
84 | def create_stock_location
85 | if stock_locations.empty?
86 | location = stock_locations.build(
87 | active: true,
88 | country_id: address.try(:country_id),
89 | name: name,
90 | state_id: address.try(:state_id)
91 | )
92 | # It's important location is always created. Some apps add validations that shouldn't break this.
93 | location.save validate: false
94 | end
95 | end
96 |
97 | def send_welcome
98 | Spree::SupplierMailer.welcome(id).deliver_later!
99 | # Specs raise error for not being able to set default_url_options[:host]
100 | rescue => ex # Errno::ECONNREFUSED => ex
101 | Rails.logger.error ex.message
102 | Rails.logger.error ex.backtrace.join("\n")
103 | return true # always return true so that failed email doesn't crash app.
104 | end
105 |
106 | def set_commission
107 | unless changes.key?(:commission_flat_rate)
108 | self.commission_flat_rate = SpreeDropShip::Config[:default_commission_flat_rate]
109 | end
110 | unless changes.key?(:commission_percentage)
111 | self.commission_percentage = SpreeDropShip::Config[:default_commission_percentage]
112 | end
113 | end
114 | end
115 |
--------------------------------------------------------------------------------
/spec/models/spree/supplier_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe Spree::Supplier do
4 | it { should belong_to(:address) }
5 |
6 | it { should have_many(:products).through(:variants) }
7 | it { should have_many(:stock_locations) }
8 | it { should have_many(:users) }
9 | it { should have_many(:variants).through(:supplier_variants) }
10 |
11 | it { should validate_presence_of(:email) }
12 | it { should validate_presence_of(:name) }
13 |
14 | it '#deleted?' do
15 | subject.deleted_at = nil
16 | subject.deleted_at?.should eql(false)
17 | subject.deleted_at = Time.now
18 | subject.deleted_at?.should eql(true)
19 | end
20 |
21 | context '#assign_user' do
22 | let(:supplier) { build(:supplier) }
23 |
24 | it 'with user' do
25 | Spree.user_class.should_not_receive :find_by_email
26 | supplier.email = 'test@test.com'
27 | supplier.users << create(:user)
28 | supplier.save
29 | end
30 |
31 | it 'with existing user email' do
32 | user = create(:user, email: 'test@test.com')
33 | Spree.user_class.should_receive(:find_by).with(email: user.email).and_return(user)
34 | supplier.email = user.email
35 | supplier.save
36 | supplier.reload.users.first.should eql(user)
37 | end
38 |
39 | end
40 |
41 | it '#create_stock_location' do
42 | Spree::StockLocation.count.should eql(0)
43 | supplier = create :supplier
44 | Spree::StockLocation.first.active.should be true
45 | Spree::StockLocation.first.country.should eql(supplier.address.country)
46 | Spree::StockLocation.first.supplier.should eql(supplier)
47 | end
48 |
49 | context '#send_welcome' do
50 |
51 | after do
52 | SpreeDropShip::Config[:send_supplier_email] = true
53 | end
54 |
55 | before do
56 | @instance = build(:supplier)
57 | @mail_message = double('Mail::Message')
58 | end
59 |
60 | context 'with Spree::DropShipConfig[:send_supplier_email] == false' do
61 |
62 | it 'should not send' do
63 | SpreeDropShip::Config[:send_supplier_email] = false
64 | expect {
65 | Spree::SupplierMailer.should_not_receive(:welcome).with(an_instance_of(Integer))
66 | }
67 | @instance.save
68 | end
69 |
70 | end
71 |
72 | context 'with Spree::DropShipConfig[:send_supplier_email] == true' do
73 |
74 | it 'should send welcome email' do
75 | expect {
76 | Spree::SupplierMailer.should_receive(:welcome).with(an_instance_of(Integer))
77 | }
78 | @instance.save
79 | end
80 |
81 | end
82 |
83 | end
84 |
85 | it '#set_commission' do
86 | SpreeDropShip::Config.set default_commission_flat_rate: 1
87 | SpreeDropShip::Config.set default_commission_percentage: 1
88 | supplier = create :supplier
89 | SpreeDropShip::Config.set default_commission_flat_rate: 0
90 | SpreeDropShip::Config.set default_commission_percentage: 0
91 | # Default configuration is 0.0 for each.
92 | supplier.commission_flat_rate.to_f.should eql(1.0)
93 | supplier.commission_percentage.to_f.should eql(1.0)
94 | # With custom commission applied.
95 | supplier = create :supplier, commission_flat_rate: 123, commission_percentage: 25
96 | supplier.commission_flat_rate.should eql(123.0)
97 | supplier.commission_percentage.should eql(25.0)
98 | end
99 |
100 | describe '#shipments' do
101 | let!(:supplier) { create(:supplier) }
102 |
103 | it 'should return shipments for suppliers stock locations' do
104 | stock_location_1 = supplier.stock_locations.first
105 | stock_location_2 = create(:stock_location, supplier: supplier)
106 | shipment_1 = create(:shipment)
107 | shipment_2 = create(:shipment, stock_location: stock_location_1)
108 | shipment_3 = create(:shipment)
109 | shipment_4 = create(:shipment, stock_location: stock_location_2)
110 | shipment_5 = create(:shipment)
111 | shipment_6 = create(:shipment, stock_location: stock_location_1)
112 |
113 | expect(supplier.shipments).to match_array([shipment_2, shipment_4, shipment_6])
114 | end
115 | end
116 | end
117 |
--------------------------------------------------------------------------------
/app/views/spree/admin/shipments/index.html.erb:
--------------------------------------------------------------------------------
1 | <% content_for :page_title do %>
2 | <%= Spree.t(:listing_shipments) %>
3 | <% end %>
4 |
5 | <% content_for :table_filter_title do %>
6 | <%= Spree.t(:search) %>
7 | <% end %>
8 |
9 | <%# Update search form with new markup %>
10 | <% content_for :table_filter do %>
11 |
12 | <%= search_form_for [:admin, @search] do |f| %>
13 |
28 |
29 |
30 |
31 | <%= label_tag nil, Spree.t(:shipment_number) %>
32 | <%= f.text_field :number_cont , class: 'form-control js-quick-search-target'%>
33 |
34 |
35 |
36 |
37 | <%= button Spree.t(:filter_results), 'search' %>
38 |
39 | <% end %>
40 |
41 | <% end %>
42 |
43 | <%# This is a new paginate! %>
44 | <%= render :partial => 'spree/admin/shared/index_table_options', :locals => { :collection => @shipments } %>
45 |
46 | <% unless @shipments.empty? %>
47 |
48 |
49 |
50 | <%= sort_link @search, :created_at, Spree::Shipment.human_attribute_name(:created_at) %>
51 | <%= sort_link @search, :number, Spree::Shipment.human_attribute_name(:number) %>
52 | <%= sort_link @search, :state, Spree::Shipment.human_attribute_name(:state) %>
53 | <%= sort_link @search, :item_cost, Spree::Shipment.human_attribute_name(:item_cost) %>
54 | <%= sort_link @search, :shipping_total, Spree::Shipment.human_attribute_name(:cost) %>
55 | <%= sort_link @search, :tax_total, Spree::Shipment.human_attribute_name(:tax_total) %>
56 | <%= sort_link @search, :total, Spree::Shipment.human_attribute_name(:final_price) %>
57 | <%= sort_link @search, :supplier_commission, Spree::Shipment.human_attribute_name(:supplier_commission) %>
58 |
59 |
60 |
61 |
62 | <% @shipments.each do |shipment| %>
63 |
64 | <%= l shipment.created_at.to_date %>
65 | <%= link_to shipment.number, spree.edit_admin_shipment_path(shipment) %>
66 | <%= Spree.t("shipment_state.#{shipment.state.downcase}") %>
67 | <%= shipment.display_item_cost.to_html %>
68 | <%= shipment.display_cost.to_html %>
69 | <%= Spree::Money.new(shipment.tax_total, currency: shipment.currency).to_html %>
70 | <%= Spree::Money.new(shipment.final_price_with_items, currency: shipment.currency).to_html %>
71 | <%= Spree::Money.new(shipment.supplier_commission, currency: shipment.currency).to_html %>
72 |
73 | <%= link_to_edit_url spree.edit_admin_shipment_path(shipment), :title => "admin_edit_#{dom_id(shipment)}", :no_text => true %>
74 |
75 |
76 | <% end %>
77 |
78 |
79 | <% else %>
80 | <%# New alert 'Not Found' with i18n %>
81 |
82 | <%= Spree.t(:no_resource_found, resource: Spree::Shipment.model_name.human(count: :many)) %>,
83 |
84 | <% end %>
85 |
86 | <%# This is a new paginate! %>
87 | <%= render :partial => 'spree/admin/shared/index_table_options', :locals => { :collection => @shipments } %>
88 |
--------------------------------------------------------------------------------
/spec/features/admin/suppliers_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | feature 'Admin - Suppliers', js: true do
4 |
5 | before do
6 | country = create(:country, name: "United States")
7 | create(:state, name: "Vermont", country: country)
8 | @supplier = create :supplier
9 | end
10 |
11 | context 'as an Admin' do
12 |
13 | before do
14 | login_user create(:admin_user)
15 | visit spree.admin_path
16 | within '[data-hook=admin_tabs]' do
17 | click_link 'Suppliers'
18 | end
19 | page.should have_content('Listing Suppliers')
20 | end
21 |
22 | scenario 'should be able to create new supplier' do
23 | click_link 'New Supplier'
24 | check 'supplier_active'
25 | fill_in 'supplier[name]', with: 'Test Supplier'
26 | fill_in 'supplier[email]', with: 'spree@example.com'
27 | fill_in 'supplier[url]', with: 'http://www.test.com'
28 | fill_in 'supplier[commission_flat_rate]', with: '0'
29 | fill_in 'supplier[commission_percentage]', with: '0'
30 | fill_in 'supplier[address_attributes][firstname]', with: 'First'
31 | fill_in 'supplier[address_attributes][lastname]', with: 'Last'
32 | fill_in 'supplier[address_attributes][address1]', with: '1 Test Drive'
33 | fill_in 'supplier[address_attributes][city]', with: 'Test City'
34 | fill_in 'supplier[address_attributes][zipcode]', with: '55555'
35 | select2 'United States', from: 'Country'
36 | select2 'Vermont', from: 'State'
37 | fill_in 'supplier[address_attributes][phone]', with: '555-555-5555'
38 | click_button 'Create'
39 | page.should have_content('Supplier "Test Supplier" has been successfully created!')
40 | end
41 |
42 | scenario 'should be able to delete supplier' do
43 | click_icon 'delete'
44 | page.driver.browser.switch_to.alert.accept
45 | within 'table' do
46 | page.should_not have_content(@supplier.name)
47 | end
48 | end
49 |
50 | scenario 'should be able to edit supplier' do
51 | click_icon 'edit'
52 | check 'supplier_active'
53 | fill_in 'supplier[name]', with: 'Test Supplier'
54 | fill_in 'supplier[email]', with: 'spree@example.com'
55 | fill_in 'supplier[url]', with: 'http://www.test.com'
56 | fill_in 'supplier[commission_flat_rate]', with: '0'
57 | fill_in 'supplier[commission_percentage]', with: '0'
58 | fill_in 'supplier[address_attributes][firstname]', with: 'First'
59 | fill_in 'supplier[address_attributes][lastname]', with: 'Last'
60 | fill_in 'supplier[address_attributes][address1]', with: '1 Test Drive'
61 | fill_in 'supplier[address_attributes][city]', with: 'Test City'
62 | fill_in 'supplier[address_attributes][zipcode]', with: '55555'
63 | select2 'United States', from: 'Country'
64 | select2 'Vermont', from: 'State'
65 | fill_in 'supplier[address_attributes][phone]', with: '555-555-5555'
66 | click_button 'Update'
67 | page.should have_content('Supplier "Test Supplier" has been successfully updated!')
68 | end
69 |
70 | end
71 |
72 | context 'as a Supplier' do
73 | before do
74 | @user = create(:supplier_user)
75 | login_user @user
76 | visit spree.edit_admin_supplier_path(@user.supplier)
77 | end
78 |
79 | scenario 'should only see tabs they have access to' do
80 | within '[data-hook=admin_tabs]' do
81 | page.should_not have_link('Overview')
82 | page.should have_link('Products')
83 | page.should_not have_link('Reports')
84 | page.should_not have_link('Configuration')
85 | page.should_not have_link('Promotions')
86 | page.should_not have_link('Suppliers')
87 | page.should have_link('Shipments')
88 | end
89 | end
90 |
91 | scenario 'should be able to update supplier' do
92 | fill_in 'supplier[name]', with: 'Test Supplier'
93 | fill_in 'supplier[email]', with: @user.email
94 | fill_in 'supplier[url]', with: 'http://www.test.com'
95 | fill_in 'supplier[address_attributes][firstname]', with: 'First'
96 | fill_in 'supplier[address_attributes][lastname]', with: 'Last'
97 | fill_in 'supplier[address_attributes][address1]', with: '1 Test Drive'
98 | fill_in 'supplier[address_attributes][city]', with: 'Test City'
99 | fill_in 'supplier[address_attributes][zipcode]', with: '55555'
100 | select2 'United States', from: 'Country'
101 | select2 'Vermont', from: 'State'
102 | fill_in 'supplier[address_attributes][phone]', with: '555-555-5555'
103 | page.should_not have_css('#supplier_active') # cannot edit active
104 | page.should_not have_css('#supplier_featured') # cannot edit featured
105 | page.should_not have_css('#s2id_supplier_user_ids') # cannot edit assigned users
106 | page.should_not have_css('#supplier_commission_flat_rate') # cannot edit flat rate commission
107 | page.should_not have_css('#supplier_commission_percentage') # cannot edit comission percentage
108 | click_button 'Update'
109 | page.should have_content('Supplier "Test Supplier" has been successfully updated!')
110 | page.current_path.should eql(spree.edit_admin_supplier_path(@user.reload.supplier))
111 | end
112 |
113 | end
114 |
115 | context 'as a User other than the suppliers' do
116 |
117 | scenario 'should be unauthorized' do
118 | supplier = create(:supplier)
119 | login_user create(:user)
120 | visit spree.edit_admin_supplier_path(supplier)
121 | page.should have_content('Authorization Failure')
122 | end
123 |
124 | end
125 |
126 | end
127 |
--------------------------------------------------------------------------------
/config/locales/es.yml:
--------------------------------------------------------------------------------
1 | es:
2 | activerecord:
3 | attributes:
4 | spree/supplier:
5 | commission_flat_rate: Tarifa fija de comisión
6 | commission_percentage: Porcentaje de comisión
7 | name: Nombre
8 | tax_id: TAX ID
9 | url: URL
10 | address1: Dirección
11 | address2: Dirección (continuación)
12 | city: Ciudad
13 | commission: Comisión
14 | country: País
15 | email: Email
16 | state: Estado
17 | active: Activo
18 | url: URL
19 | zipcode: Código Postal(ZipCode)
20 | supplier_info: Información de proveedor
21 | supplier_signup: Registro de proveedor
22 | suppliers: Proveedores
23 | spree/shipment:
24 | created_at: Creado
25 | number: Número
26 | state: Estatus
27 | item_cost: Costo ítem
28 | cost: Costo
29 | total_tax: Total impuesto
30 | final_price: Precio final
31 | supplier_commission: Comisión Proveedor
32 | errors:
33 | models:
34 | spree/supplier:
35 | attributes:
36 | url:
37 | invalid: es inválido. Por favor asegúrece que 'http://' esté incluido
38 | not_responding: 'es inválido, no responde o ha redireccionado a otra url'
39 | drop_ship_order_number: "Drop Ship Orden #%{number}"
40 | drop_ship_orders: Drop Ship Ordenes
41 | drop_ship_settings: Drop Ship Configuración
42 | must_be_logged_in: 'Debe Estar Autenticado'
43 | signup: Registro
44 | signup_to_become_a_supplier: 'Autentíquese para ser un Proveedor'
45 | spree:
46 | admin:
47 | drop_ship_settings:
48 | update:
49 | success: Configuración Drop ship actualizada correctamente
50 | orders:
51 | drop_ship_order_order_header:
52 | drop_ship_order_ids: DSO IDs
53 | edit:
54 | approve_drop_ship_orders: Ordenes aprobadas por el proveedor
55 | resend_drop_ship_orders: Reenviar ordenes al proveedor
56 | drop_ship_order_approval_confirmation: Se le enviará una notificación por correo electrónico a cada proveedor. ¿Desea continuar?
57 | show:
58 | approve_drop_ship_orders: Ordene aprobadas por el proveedor
59 | resend_drop_ship_orders: Reenviar ordenes al proveedor
60 | drop_ship_order_approval_confirmation: Se le enviará una notificación por correo electrónico a cada proveedor. ¿Desea continuar?
61 | suppliers:
62 | edit:
63 | back_to_suppliers: Regresar a los proveedores
64 | new:
65 | new_supplier: Nuevo Proveedor
66 | form:
67 | supplier_details: Detalles del Proveedor
68 | supplier_address: Dirección del Proveedor
69 | drop_ship_orders:
70 | deliver:
71 | error: "La orden %{number} no pudo ser enviada al proveedor."
72 | success: "La orden %{number} fue enviada al proveedor."
73 | orders_sent: Todas las ordenes fueron enviadas a los proveedores
74 | orders_not_sent: Algunas o todas las ordenes no pudieron ser enviadas. Por favor contacte directamente al proveedor.
75 | back_to_orders_list: Regresar a las ordenes
76 | back_to_suppliers: Regresar a los proveedores
77 | business: Negocio
78 | cancel: Cancelar
79 | company_info: Informacion de la compañia
80 | confirm_order: Confirmar Orden
81 | contact_address: Dirección de contacto
82 | drop_ship: Drop Ship
83 | editing_supplier: Editar proveedor
84 | default_commission_flat_rate: Comision por defecto
85 | default_commission_percentage: Porcentaje de comision por defecto
86 | automatically_deliver_orders_to_supplier: Enviar orden automaticamente a los proveedores
87 | send_supplier_email: Enviar correo al proveedor
88 | individual: Individual
89 | listing_suppliers: Lista de proveedores
90 | manage: Gestionar
91 | name: Nombre
92 | or: ó
93 | suppliers: Proveedores
94 | listing_shipments: 'Lista de envíos'
95 | order_state:
96 | active: Activo
97 | completed: Completado
98 | confirmed: Confirmado
99 | delivered: Entregado
100 | resend_order_to_supplier: Reenviar orden al proveedor.
101 | save: Guardar
102 | search: Buscar
103 | send_order_to_supplier: Enviar orden al proveedor.
104 | show_only_incomplete_orders: Solo mostrar ordenes incompletas
105 | supplier: Proveedor
106 | supplier_information: Información del proveedor
107 | drop_ship_order_mailer:
108 | supplier_order:
109 | hello: "Hola %{name},"
110 | subject: "%{name} Orden al proveedor #%{number}"
111 | thank_you_again: "Gracias nuevamente por su negocio,
%{name} "
112 | shared:
113 | unauthorized:
114 | explained: Usted no está autorizado a acceder a esta página.
115 | unauthorized: No autorizado
116 | supplier_mailer:
117 | welcome:
118 | hello: "Hola %{name},"
119 | check_your_status: Puede revisar el estado de su pedido por
120 | logging_into_your_account: Autentíquese.
121 | once_your_request_is_approved: Una vez que su pedido sea aprobado podrá gestionar su inventario.
122 | subject: 'Gracias por autenticarse. Por favor verifique la información.'
123 | thank_you_again: "Gracias nuevamente por su negocio,
%{name} "
124 | thank_you_for_signing_up: Gracias por autenticarse para ser un proveedor.
125 | suppliers:
126 | already_signed_up: "Ya está autenticado para convertirse en un proveedor."
127 | create:
128 | success: Gracias por autenticarse!
129 | supplier_registration:
130 | already_signed_up: "Ya inicio sesión como proveedor."
131 | create:
132 | invalid_password: Clave incorrecta por favor inicie sesion.
133 | success: Gracias por registrarse!
--------------------------------------------------------------------------------
/spec/features/admin/stock_management_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 |
3 | describe "Stock Management", js: true do
4 |
5 | before do
6 | @user = create(:supplier_user)
7 | login_user @user
8 | visit spree.admin_shipments_path
9 | end
10 |
11 | context "as supplier user" do
12 |
13 | context "given a product with a variant and a stock location" do
14 | before do
15 | @secondary = create(:stock_location, name: 'Secondary', supplier: @user.supplier)
16 | @product = create(:product, name: 'apache baseball cap', price: 10)
17 | @v = @product.variants.create!(attributes_for(:variant))
18 | @user.supplier.reload.stock_locations.update_all(backorderable_default: false) # True database default is false.
19 | end
20 |
21 | context 'with single variant' do
22 | before do
23 | @product.add_supplier! @user.supplier
24 | @v.stock_items.first.update_column(:count_on_hand, 10)
25 | @secondary.stock_item(@v).destroy
26 | click_link "Products"
27 | within '#sidebar-product' do
28 | click_link 'Products'
29 | end
30 | click_link @product.name
31 | within '[data-hook=admin_product_tabs]' do
32 | click_link "Stock"
33 | end
34 | end
35 |
36 | it "should not show deleted stock_items" do
37 | within(:css, '.stock_location_info') do
38 | page.should have_content(@user.supplier.name)
39 | page.should_not have_content('Secondary')
40 | end
41 | end
42 |
43 | it "can toggle backorderable for a variant's stock item", js: true do
44 | backorderable = find(".stock_item_backorderable")
45 | backorderable.should_not be_checked
46 |
47 | backorderable.set(true)
48 |
49 | sleep(1)
50 |
51 | visit current_path
52 |
53 | backorderable = find(".stock_item_backorderable")
54 | backorderable.should be_checked
55 | end
56 |
57 | # Regression test for #2896
58 | # The regression was that unchecking the last checkbox caused a redirect
59 | # to happen. By ensuring that we're still on an /admin/products URL, we
60 | # assert that the redirect is *not* happening.
61 | it "can toggle backorderable for the second variant stock item", js: true do
62 | new_location = create(:stock_location, name: "Another Location", supplier: @user.supplier)
63 | visit page.current_path
64 |
65 | new_location_backorderable = find "#stock_item_backorderable_#{new_location.id}"
66 | new_location_backorderable.set(false)
67 | # Wait for API request to complete.
68 | sleep(1)
69 |
70 | page.current_url.should include("/admin/products")
71 | end
72 |
73 | it "can create a new stock movement", js: true do
74 | fill_in "stock_movement_quantity", with: 5
75 | select2 @user.supplier.name, from: "Stock Location"
76 | click_button "Add Stock"
77 |
78 | page.should have_content('successfully created')
79 | within(:css, '.stock_location_info table') do
80 | column_text(2).should eq '15'
81 | end
82 | end
83 |
84 | it "can create a new negative stock movement", js: true do
85 | fill_in "stock_movement_quantity", with: -5
86 | select2 @user.supplier.name, from: "Stock Location"
87 | click_button "Add Stock"
88 |
89 | page.should have_content('successfully created')
90 |
91 | within(:css, '.stock_location_info table') do
92 | column_text(2).should eq '5'
93 | end
94 | end
95 |
96 | it "can create a new negative stock movement", js: true do
97 | fill_in "stock_movement_quantity", with: -5
98 | select2 @user.supplier.name, from: "Stock Location"
99 | click_button "Add Stock"
100 |
101 | page.should have_content('successfully created')
102 |
103 | within(:css, '.stock_location_info table') do
104 | column_text(2).should eq '5'
105 | end
106 | end
107 | end
108 |
109 | context "with multiple variants" do
110 | before do
111 | v = @product.variants.create!(attributes_for(:variant))
112 | @product.add_supplier! @user.supplier
113 | v.stock_items.first.update_column(:count_on_hand, 30)
114 |
115 | click_link "Products"
116 | within '#sidebar-product' do
117 | click_link 'Products'
118 | end
119 | click_link @product.name
120 | within '[data-hook=admin_product_tabs]' do
121 | click_link "Stock"
122 | end
123 | end
124 |
125 | it "can create a new stock movement for the specified variant", js: true do
126 | fill_in "stock_movement_quantity", with: 10
127 | select2 @v.sku, from: "Variant"
128 | click_button "Add Stock"
129 |
130 | page.should have_content('successfully created')
131 | end
132 | end
133 | end
134 |
135 | # Regression test for #3304
136 | context "with no stock location" do
137 | before do
138 | @product = create(:product, name: 'apache baseball cap', price: 10)
139 | @product.add_supplier! @user.supplier
140 | @product.variants.create!(attributes_for(:variant))
141 | Spree::StockLocation.delete_all
142 | click_link "Products"
143 | within '#sidebar-product' do
144 | click_link 'Products'
145 | end
146 | click_link @product.name
147 | end
148 |
149 | it "redirects to stock locations page" do
150 | page.should have_content(Spree.t(:stock_management_requires_a_stock_location))
151 | page.current_url.should include("admin/stock_locations")
152 | end
153 | end
154 | end
155 | end
156 |
--------------------------------------------------------------------------------
/spec/models/spree/supplier_ability_spec.rb:
--------------------------------------------------------------------------------
1 | require 'spec_helper'
2 | require 'cancan/matchers'
3 | require 'spree/testing_support/ability_helpers'
4 |
5 | describe Spree::SupplierAbility do
6 |
7 | let(:user) { create(:user, supplier: create(:supplier)) }
8 | let(:ability) { Spree::SupplierAbility.new(user) }
9 | let(:token) { nil }
10 |
11 | context 'for Dash' do
12 | let(:resource) { Spree::Admin::BaseController }
13 |
14 | context 'requested by supplier' do
15 | it_should_behave_like 'access denied'
16 | it_should_behave_like 'no index allowed'
17 | it_should_behave_like 'admin denied'
18 | end
19 | end
20 |
21 | context 'for Product' do
22 | let(:resource) { create(:product) }
23 |
24 | it_should_behave_like 'index allowed'
25 | it_should_behave_like 'admin granted'
26 |
27 | context 'requested by another suppliers user' do
28 | let(:resource) {
29 | product = create(:product)
30 | product.add_supplier!(create(:supplier))
31 | product
32 | }
33 | it_should_behave_like 'access denied'
34 | end
35 |
36 | context 'requested by suppliers user' do
37 | let(:resource) {
38 | product = create(:product)
39 | product.add_supplier!(user.supplier)
40 | product.reload
41 | }
42 | # it_should_behave_like 'access granted'
43 | it { ability.should be_able_to :read, resource }
44 | it { ability.should be_able_to :stock, resource }
45 | end
46 | end
47 |
48 | context 'for Shipment' do
49 | context 'requested by another suppliers user' do
50 | let(:resource) { Spree::Shipment.new(stock_location: create(:stock_location, supplier: create(:supplier))) }
51 | it_should_behave_like 'access denied'
52 | it_should_behave_like 'no index allowed'
53 | it_should_behave_like 'admin denied'
54 | it { ability.should_not be_able_to :ready, resource }
55 | it { ability.should_not be_able_to :ship, resource }
56 | end
57 |
58 | context 'requested by suppliers user' do
59 | context 'when order is complete' do
60 | let(:resource) {
61 | order = create(:completed_order_for_drop_ship_with_totals)
62 | order.stock_locations.first.update_attribute :supplier, user.supplier
63 | Spree::Shipment.new(order: order, stock_location: order.stock_locations.first)
64 | }
65 | it_should_behave_like 'access granted'
66 | it_should_behave_like 'index allowed'
67 | it_should_behave_like 'admin granted'
68 | it { ability.should be_able_to :ready, resource }
69 | it { ability.should be_able_to :ship, resource }
70 | end
71 |
72 | context 'when order is incomplete' do
73 | let(:resource) { Spree::Shipment.new(stock_location: create(:stock_location, supplier: user.supplier)) }
74 | it_should_behave_like 'access denied'
75 | it { ability.should_not be_able_to :ready, resource }
76 | it { ability.should_not be_able_to :ship, resource }
77 | end
78 | end
79 | end
80 |
81 | context 'for StockItem' do
82 | let(:resource) { Spree::StockItem }
83 |
84 | it_should_behave_like 'index allowed'
85 | it_should_behave_like 'admin granted'
86 |
87 | context 'requested by another suppliers user' do
88 | let(:resource) {
89 | supplier = create(:supplier)
90 | variant = create(:product).master
91 | variant.product.add_supplier! supplier
92 | supplier.stock_locations.first.stock_items.first
93 | }
94 | it_should_behave_like 'access denied'
95 | end
96 |
97 | context 'requested by suppliers user' do
98 | let(:resource) {
99 | variant = create(:product).master
100 | variant.product.add_supplier! user.supplier
101 | user.supplier.stock_locations.first.stock_items.first
102 | }
103 | it_should_behave_like 'access granted'
104 | end
105 | end
106 |
107 | context 'for StockLocation' do
108 | context 'requsted by another suppliers user' do
109 | let(:resource) {
110 | supplier = create(:supplier)
111 | variant = create(:product).master
112 | variant.product.add_supplier! supplier
113 | supplier.stock_locations.first
114 | }
115 | it_should_behave_like 'create only'
116 | end
117 |
118 | context 'requested by suppliers user' do
119 | let(:resource) {
120 | variant = create(:product).master
121 | variant.product.add_supplier! user.supplier
122 | user.supplier.stock_locations.first
123 | }
124 | it_should_behave_like 'access granted'
125 | it_should_behave_like 'admin granted'
126 | it_should_behave_like 'index allowed'
127 | end
128 | end
129 |
130 | context 'for StockMovement' do
131 | let(:resource) { Spree::StockMovement }
132 |
133 | it_should_behave_like 'index allowed'
134 | it_should_behave_like 'admin granted'
135 |
136 | context 'requested by another suppliers user' do
137 | let(:resource) {
138 | supplier = create(:supplier)
139 | variant = create(:product).master
140 | variant.product.add_supplier! supplier
141 | Spree::StockMovement.new(stock_item: supplier.stock_locations.first.stock_items.first)
142 | }
143 | it_should_behave_like 'create only'
144 | end
145 |
146 | context 'requested by suppliers user' do
147 | let(:resource) {
148 | variant = create(:product).master
149 | variant.product.add_supplier! user.supplier
150 | Spree::StockMovement.new(stock_item: user.supplier.stock_locations.first.stock_items.first)
151 | }
152 | it_should_behave_like 'access granted'
153 | end
154 | end
155 |
156 | context 'for Supplier' do
157 | context 'requested by any user' do
158 | let(:ability) { Spree::SupplierAbility.new(create(:user)) }
159 | let(:resource) { Spree::Supplier }
160 |
161 | it_should_behave_like 'admin denied'
162 | it_should_behave_like 'access denied'
163 | end
164 |
165 | context 'requested by suppliers user' do
166 | let(:resource) { user.supplier }
167 | it_should_behave_like 'admin granted'
168 | it_should_behave_like 'update only'
169 | end
170 | end
171 |
172 | end
173 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 | ### Check out the latest, officially maintained [Spree Multi Vendor marketplace](https://github.com/spree-contrib/spree_multi_vendor) extension
3 |
4 | # Spree Drop Ship
5 |
6 | [](https://travis-ci.org/spree-contrib/spree_drop_ship)
7 | [](https://codeclimate.com/github/spree-contrib/spree_drop_ship)
8 | [](https://coveralls.io/r/spree-contrib/spree_drop_ship)
9 | [](https://gemnasium.com/spree-contrib/spree_drop_ship)
10 |
11 | What is drop shipping?
12 |
13 | "Drop shipping is a supply chain management technique in which the retailer does not keep goods in stock, but instead transfers customer orders
14 | and shipment details to either the manufacturer or a wholesaler, who then ships the goods directly to the customer." - [wikipedia](http://en.wikipedia.org/wiki/Drop_shipping)
15 |
16 | So the main goal with spree_drop_ship is to link products to suppliers and forward orders to the appropriate suppliers.
17 |
18 | Once an order is placed for a product that belongs to a supplier a shipment is created for the product's supplier.
19 | This shipment is then sent to the supplier (via Email by default). The supplier then follows a link to the shipment
20 | within the email where they are prompted to confirm the shipment.
21 |
22 | Spree Drop Ship used with [Spree Marketplace](https://github.com/jdutil/spree_marketplace) allows handling payments to your suppliers via ACH direct deposits.
23 | This is still currently a work in progress, and any input is welcome.
24 | .
25 |
26 | Upgrading
27 | ---------
28 |
29 | **Warning: Upgrading to Spree 2.2.x & 2.3.x when using this extension is not backwards compatible.
30 | I have removed the notion of drop ship orders which payments & commission were previously tracked to.
31 | Now suppliers simply manage their shipments, and payments & commission are now linked to a payable object i.e. shipment in this case.
32 | This means the previous method of determining a suppliers commission is no longer valid, and you will need to migrate your data accordingly.**
33 |
34 | I'm sorry for the inconvenience this may cause, but I've determined for this extension to meet it's most potential I needed to drastically alter the approach
35 | it was taking. I'm still undergoing several more radical changes for Spree 2.3.x that involve moving product management from this extension into the spree_marketplace
36 | extension. The goal from the beginning of this extension has been for it to be a very light weight and extensible drop shipping solution. Much of this extension
37 | has been made obsolete by split shipping, and line item adjustments within Spree Core itself. Now I feel I can really streamline this extension to take advantage
38 | of the recent Spree Core changes, and also move the product management into the marketplace extension as that is really more of what product management is inteded for.
39 | The typical drop shipping scenario would simply be a supplier being able to update their shipments they need to fulfill and nothing more.
40 |
41 | Installation
42 | ------------
43 |
44 | Here's how to install spree_drop_ship into your existing spree site AFTER you've installed Spree:
45 |
46 | Add the following to your Gemfile:
47 |
48 | gem 'spree_drop_ship', github: 'spree-contrib/spree_drop_ship'
49 |
50 | Make your bundle happy:
51 |
52 | bundle install
53 |
54 | Now run the generator:
55 |
56 | rails g spree_drop_ship:install
57 |
58 | Then migrate your database if you did not run during installation generator:
59 |
60 | bundle exec rake db:migrate
61 |
62 | And reboot your server:
63 |
64 | rails s
65 |
66 | You should be up and running now!
67 |
68 | Sample Data
69 | -----------
70 |
71 | If you'd like to generate sample data, use the included rake tasks:
72 |
73 | ```shell
74 | rake spree_sample:load # Loads sample data into the store
75 | rake spree_sample:suppliers # Create sample suppliers and randomly link to products
76 | rake spree_sample:drop_ship_orders # Create sample drop ship orders
77 | ```
78 |
79 | Demo
80 | ----
81 |
82 | You can easily use the spec/dummy app as a demo of spree_drop_ship. Just `cd` to where you develop and run:
83 |
84 | ```shell
85 | git clone git://github.com/spree-contrib/spree_drop_ship.git
86 | cd spree_drop_ship
87 | bundle install
88 | bundle exec rake test_app
89 | cd spec/dummy
90 | rake db:migrate db:seed spree_sample:load spree_sample:suppliers spree_sample:drop_ship_orders
91 | rails s
92 | ```
93 |
94 | Testing
95 | -------
96 |
97 | Be sure to bundle your dependencies and then create a dummy test app for the specs to run against.
98 |
99 | ```shell
100 | bundle
101 | bundle exec rake test_app
102 | bundle exec rspec spec
103 | ```
104 |
105 | Todo
106 | ----
107 |
108 | - Stock Items should automatically be set to backorderable false if the variant doesnt belong to the stock locations supplier.
109 | - Must allow suppliers to edit their stock location addresses & require it.
110 | - Return Authorization UI
111 | - Better documentation
112 | - related products should only allow suppliers own products to be related
113 |
114 | Contributing
115 | ------------
116 |
117 | In the spirit of [free software](http://www.fsf.org/licensing/essays/free-sw.html), **everyone** is encouraged to help improve this project.
118 |
119 | Here are some ways *you* can contribute:
120 |
121 | * by using prerelease versions
122 | * by reporting [bugs](https://github.com/spree-contrib/spree_drop_ship/issues)
123 | * by suggesting new features
124 | * by [translating to a new language](https://github.com/spree-contrib/spree_drop_ship/tree/master/config/locales)
125 | * by writing or editing documentation
126 | * by writing specifications
127 | * by writing code (*no patch is too small*: fix typos, add comments, clean up inconsistent whitespace)
128 | * by refactoring code
129 | * by resolving [issues](https://github.com/spree-contrib/spree_drop_ship/issues)
130 | * by reviewing patches
131 |
132 | Donating
133 | --------
134 |
135 | Bitcoin donations may be sent to: 1L6akT6Aus9r6Ashw1wDtLg7D8zJCVVZac
136 |
137 | Copyright (c) 2012-2014 Jeff Dutil, released under the [New BSD License](https://github.com/spree-contrib/spree_drop_ship/tree/master/LICENSE).
138 |
--------------------------------------------------------------------------------
/app/views/spree/drop_ship_order_mailer/supplier_order.html.erb:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | <%= link_to image_tag(Spree::Config[:logo]), spree.root_url %>
10 |
11 |
12 |
13 |
14 | <%= Spree.t('drop_ship_order_mailer.
15 | supplier_order.hello', name: @supplier.name ) %>
16 |
17 | An order has been placed. Please confirm the shipment details, and <%= link_to 'update the shipment', spree.edit_admin_shipment_url(@shipment) %> once it has shipped.
18 |
19 |
20 |
21 |
22 |
23 | Reference Number: <%= @shipment.number %>
24 | Placed on: <%= @shipment.created_at.strftime('%A %b %e, %Y at %l:%M%p %Z') %>
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | Shipping Information:
33 |
34 | Shipping Method:
35 |
36 |
37 |
38 |
39 |
40 | <%= @shipment.order.ship_address.firstname %> <%= @shipment.order.ship_address.lastname %>
41 | <%= @shipment.order.ship_address.address1 %>
42 | <% unless @shipment.order.ship_address.address2.blank? %><%= @shipment.order.ship_address.address2 %> <% end %>
43 | <%= @shipment.order.ship_address.city %>, <%= @shipment.order.ship_address.state.name %> <%= @shipment.order.ship_address.zipcode %>
44 | <%= @shipment.order.ship_address.country.name %>
45 | T: <%= @shipment.order.ship_address.phone %>
46 |
47 |
48 |
49 | <%= @shipment.shipping_method.name %>
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 | Item
59 | Sku
60 | Qty
61 | Subtotal
62 |
63 |
64 |
65 | <% @shipment.line_items.each do |line_item| %>
66 |
67 |
68 | <%= line_item.variant.name %>
69 |
70 | <%= line_item.variant.sku %>
71 | <%= line_item.quantity %>
72 |
73 | <%= line_item.display_amount %>
74 |
75 |
76 | <% end %>
77 |
78 |
79 |
80 |
81 | Subtotal
82 |
83 |
84 | <%= @shipment.display_item_cost %>
85 |
86 |
87 |
88 |
89 | Shipping & Handling
90 |
91 |
92 | <%= @shipment.display_final_price %>
93 |
94 |
95 |
96 |
97 | Grand Total
98 |
99 |
100 | <%= @shipment.display_final_price_with_items %>
101 |
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 | <%= raw Spree.t('drop_ship_order_mailer.supplier_order.thank_you_again', name: Spree::Store.current.name) %>
111 |
112 |
113 |
114 |
115 |
116 |
118 |
119 |
120 |
--------------------------------------------------------------------------------