Merge branch 'master' into landing_page

Conflicts:
	db/schema.rb
	db/seeds.rb
This commit is contained in:
alexs
2013-08-05 17:08:39 +10:00
25 changed files with 461 additions and 106 deletions

View File

@@ -44,6 +44,10 @@ Create the development and test databases, using the settings specified in `conf
rake db:schema:load db:seed
Load some default data for your environment
rake openfoodweb:dev:load_sample_data
At long last, your dreams of spinning up a development server can be realised:
rails server

View File

@@ -6,7 +6,7 @@ Spree::Admin::ProductsController.class_eval do
respond_to :json, :only => :clone
#respond_override :clone => { :json => {:success => lambda { redirect_to bulk_index_admin_products_url+"?q[id_eq]=#{@new.id}" } } }
def bulk_update
collection_hash = Hash[params[:_json].each_with_index.map { |p,i| [i,p] }]
product_set = Spree::ProductSet.new({:collection_attributes => collection_hash})
@@ -17,16 +17,41 @@ Spree::Admin::ProductsController.class_eval do
render :nothing => true
end
end
protected
def location_after_save
if URI(request.referer).path == '/admin/products/bulk_edit'
if URI(request.referer).path == '/admin/products/bulk_edit'
bulk_edit_admin_products_url
else
else
location_after_save_original
end
end
def collection
# This method is copied directly from the spree product controller, except where we narrow the search below with the managed_by search to support
# enterprise users.
# TODO: There has to be a better way!!!
return @collection if @collection.present?
params[:q] ||= {}
params[:q][:deleted_at_null] ||= "1"
params[:q][:s] ||= "name asc"
@search = super.ransack(params[:q])
@collection = @search.result.
managed_by(spree_current_user). # this line is added to the original spree code!!!!!
group_by_products_id.
includes(product_includes).
page(params[:page]).
per(Spree::Config[:admin_products_per_page])
if params[:q][:s].include?("master_default_price_amount")
# PostgreSQL compatibility
@collection = @collection.group("spree_prices.amount")
end
@collection
end
private
def load_spree_api_key

View File

@@ -4,6 +4,8 @@ class Enterprise < ActiveRecord::Base
belongs_to :address, :class_name => 'Spree::Address'
has_many :product_distributions, :foreign_key => 'distributor_id', :dependent => :destroy
has_many :distributed_products, :through => :product_distributions, :source => :product
has_many :enterprise_roles
has_many :users, through: :enterprise_roles
accepts_nested_attributes_for :address
@@ -48,6 +50,13 @@ class Enterprise < ActiveRecord::Base
with_distributed_products_outer.with_order_cycles_and_exchange_variants_outer.
where('product_distributions.product_id = ? OR spree_variants.product_id = ?', product, product).
select('DISTINCT enterprises.*')
}
scope :managed_by, lambda { |user|
if user.has_spree_role?('admin')
scoped
else
joins(:enterprise_roles).where('enterprise_roles.user_id = ?', user.id)
end
}

View File

@@ -0,0 +1,4 @@
class EnterpriseRole < ActiveRecord::Base
belongs_to :user, :class_name => Spree.user_class
belongs_to :enterprise
end

View File

@@ -0,0 +1,23 @@
class AbilityDecorator
include CanCan::Ability
def initialize(user)
if user.enterprises.count > 0
can [:admin, :read, :update, :bulk_edit], Spree::Product do |product|
user.enterprises.include? product.supplier
end
can [:create], Spree::Product
can [:admin, :index, :read, :create, :edit], Spree::Variant
can [:admin, :index, :read, :create, :edit], Spree::ProductProperty
can [:admin, :index, :read, :create, :edit], Spree::Image
can [:admin, :index, :read, :search], Spree::Taxon
can [:admin, :index, :read, :create, :edit], Spree::Classification
can [:admin, :index, :read], Spree::Order
end
end
end
Spree::Ability.register_ability(AbilityDecorator)

View File

@@ -15,7 +15,11 @@ Spree::Order.class_eval do
def products_available_from_new_distribution
# Check that the line_items in the current order are available from a newly selected distribution
errors.add(:base, "Distributor or order cycle cannot supply the products in your cart") unless DistributionChangeValidator.new(self).can_change_to_distribution?(distributor, order_cycle)
if OpenFoodWeb::FeatureToggle.enabled? :order_cycles
errors.add(:base, "Distributor or order cycle cannot supply the products in your cart") unless DistributionChangeValidator.new(self).can_change_to_distribution?(distributor, order_cycle)
else
errors.add(:distributor_id, "cannot supply the products in your cart") unless DistributionChangeValidator.new(self).can_change_to_distributor?(distributor)
end
end
def set_order_cycle!(order_cycle)

View File

@@ -57,6 +57,13 @@ Spree::Product.class_eval do
scope :in_order_cycle, lambda { |order_cycle| with_order_cycles_inner.
where('exchanges.sender_id = order_cycles.coordinator_id').
where('order_cycles.id = ?', order_cycle) }
scope :managed_by, lambda { |user|
if user.has_spree_role?('admin')
scoped
else
where('supplier_id IN (?)', user.enterprises.map {|enterprise| enterprise.id })
end
}
# -- Methods
@@ -87,8 +94,8 @@ Spree::Product.class_eval do
# Build a product distribution for each distributor
def build_product_distributions
Enterprise.is_distributor.each do |distributor|
def build_product_distributions_for_user user
Enterprise.is_distributor.managed_by(user).each do |distributor|
unless self.product_distributions.find_by_distributor_id distributor.id
self.product_distributions.build(:distributor => distributor)
end

View File

@@ -0,0 +1,16 @@
Spree.user_class.class_eval do
has_many :enterprise_roles, :dependent => :destroy
has_many :enterprises, through: :enterprise_roles
accepts_nested_attributes_for :enterprise_roles, :allow_destroy => true
attr_accessible :enterprise_ids, :enterprise_roles_attributes
def build_enterprise_roles
Enterprise.all.each do |enterprise|
unless self.enterprise_roles.find_by_enterprise_id enterprise.id
self.enterprise_roles.build(:enterprise => enterprise)
end
end
end
end

View File

@@ -0,0 +1,6 @@
Deface::Override.new(:virtual_path => "spree/admin/users/_form",
:insert_after => "[data-hook='admin_user_form_fields']",
:partial => "spree/admin/users/enterprises_form",
:name => "add_enterprises_to_user"
)

View File

@@ -1,6 +1,6 @@
%h2 Distributors
= f.field_container :product_distributions do
- f.object.build_product_distributions
- f.object.build_product_distributions_for_user spree_current_user
%table
= f.fields_for :product_distributions do |pd_form|
%tr

View File

@@ -2,7 +2,7 @@
.alpha.six.columns
= f.field_container :supplier do
= f.label :supplier
= f.collection_select(:supplier_id, Enterprise.is_primary_producer, :id, :name, {:include_blank => true}, {:class => "select2 fullwidth"})
= f.collection_select(:supplier_id, Enterprise.is_primary_producer.managed_by(spree_current_user), :id, :name, {:include_blank => true}, {:class => "select2 fullwidth"})
= f.error_message_on :supplier
.four.columns
= f.field_container :group_buy do

View File

@@ -1,5 +1,6 @@
= f.field_container :supplier do
= f.label :supplier
%br
= f.collection_select(:supplier_id, Enterprise.is_primary_producer, :id, :name, {:include_blank => true}, {:class => "select2"})
= f.error_message_on :supplier
- if spree_current_user.has_spree_role?('admin')
= f.field_container :supplier do
= f.label :supplier
%br
= f.collection_select(:supplier_id, Enterprise.is_primary_producer.managed_by(spree_current_user), :id, :name, {:include_blank => true}, {:class => "select2"})
= f.error_message_on :supplier

View File

@@ -0,0 +1,13 @@
%fieldset
%legend 'Manage Enterprises'
= f.field_container :enterprise_roles do
- f.object.build_enterprise_roles
%table
= f.fields_for :enterprise_roles do |enterprise_form|
%tr
%td
= hidden_field_tag "#{enterprise_form.object_name}[_destroy]", 1, :id => nil
= check_box_tag "#{enterprise_form.object_name}[_destroy]", 0, !enterprise_form.object.new_record?
%td
= label_tag "#{enterprise_form.object_name}[_destroy]", enterprise_form.object.enterprise.name
= enterprise_form.hidden_field :enterprise_id

View File

@@ -2,10 +2,8 @@
# See https://github.com/svenfuchs/rails-i18n/tree/master/rails%2Flocale for starting points.
en:
spree:
devise:
failure:
invalid: |
Invalid email or password.
invalid: |
Invalid email or password.
Were you a guest last time? Perhaps you need to create an account or reset your password.

View File

@@ -0,0 +1,8 @@
class AddEnterpriseRole < ActiveRecord::Migration
def change
create_table :enterprise_roles do |t|
t.references :user, index: true
t.references :enterprise, index: true
end
end
end

View File

@@ -143,6 +143,11 @@ ActiveRecord::Schema.define(:version => 20130801012854) do
t.datetime "updated_at", :null => false
end
create_table "enterprise_roles", :force => true do |t|
t.integer "user_id"
t.integer "enterprise_id"
end
create_table "enterprises", :force => true do |t|
t.string "name"
t.string "description"
@@ -435,9 +440,9 @@ ActiveRecord::Schema.define(:version => 20130801012854) do
t.string "email"
t.text "special_instructions"
t.integer "distributor_id"
t.integer "order_cycle_id"
t.string "currency"
t.string "last_ip_address"
t.integer "order_cycle_id"
end
add_index "spree_orders", ["number"], :name => "index_orders_on_number"

View File

@@ -1,10 +1,6 @@
# This file should contain all the record creation needed to seed the database with its default values.
# The data can then be loaded with the rake db:seed (or created alongside the db with db:setup).
require File.expand_path('../../spec/factories', __FILE__)
require File.expand_path('../../spec/support/spree/init', __FILE__)
# -- Spree
unless Spree::Country.find_by_name 'Australia'
puts "[db:seed] Seeding Spree"
@@ -12,12 +8,9 @@ unless Spree::Country.find_by_name 'Australia'
Spree::Auth::Engine.load_seed if defined?(Spree::Auth)
end
# -- States
# States are created from factories instead of fixtures because models loaded from fixtures
# are not available to the app in the remainder of the seeding process (probably because of
# activerecord caching).
unless Spree::State.find_by_name 'Victoria'
country = Spree::Country.find_by_name('Australia')
puts "[db:seed] Seeding states"
[
@@ -30,79 +23,11 @@ unless Spree::State.find_by_name 'Victoria'
['Victoria', 'Vic'],
['Western Australia', 'WA']
].each do |state|
# country_id 12 == Australia. See db/default/spree/countries.yaml
FactoryGirl.create(:state, :name => state[0], :abbr => state[1], :country_id => 12)
Spree::State.create!({"name"=>state[0], "abbr"=>state[1], :country=>country}, :without_protection => true)
end
end
# -- Shipping / payment information
unless Spree::Zone.find_by_name 'Australia'
puts "[db:seed] Seeding shipping / payment information"
zone = FactoryGirl.create(:zone, :name => 'Australia', :zone_members => [])
country = Spree::Country.find_by_name('Australia')
Spree::ZoneMember.create(:zone => zone, :zoneable => country)
FactoryGirl.create(:shipping_method, :zone => zone)
FactoryGirl.create(:payment_method, :environment => 'development')
end
# -- Taxonomies
unless Spree::Taxonomy.find_by_name 'Products'
puts "[db:seed] Seeding taxonomies"
taxonomy = Spree::Taxonomy.find_by_name('Products') || FactoryGirl.create(:taxonomy, :name => 'Products')
taxonomy_root = taxonomy.root
['Vegetables', 'Fruit', 'Oils', 'Preserves and Sauces', 'Dairy', 'Meat and Fish'].each do |taxon_name|
FactoryGirl.create(:taxon, :name => taxon_name, :parent_id => taxonomy_root.id)
end
end
# -- Enterprises
unless Enterprise.count > 0
puts "[db:seed] Seeding enterprises"
3.times { FactoryGirl.create(:supplier_enterprise) }
3.times { FactoryGirl.create(:distributor_enterprise) }
end
# -- Products
unless Spree::Product.count > 0
puts "[db:seed] Seeding products"
prod1 = FactoryGirl.create(:product,
:name => 'Garlic', :price => 20.00,
:supplier => Enterprise.is_primary_producer[0],
:taxons => [Spree::Taxon.find_by_name('Vegetables')])
ProductDistribution.create(:product => prod1,
:distributor => Enterprise.is_distributor[0],
:shipping_method => Spree::ShippingMethod.first)
prod2 = FactoryGirl.create(:product,
:name => 'Fuji Apple', :price => 5.00,
:supplier => Enterprise.is_primary_producer[1],
:taxons => [Spree::Taxon.find_by_name('Fruit')])
ProductDistribution.create(:product => prod2,
:distributor => Enterprise.is_distributor[1],
:shipping_method => Spree::ShippingMethod.first)
prod3 = FactoryGirl.create(:product,
:name => 'Beef - 5kg Trays', :price => 50.00,
:supplier => Enterprise.is_primary_producer[2],
:taxons => [Spree::Taxon.find_by_name('Meat and Fish')])
ProductDistribution.create(:product => prod3,
:distributor => Enterprise.is_distributor[2],
:shipping_method => Spree::ShippingMethod.first)
end
# -- Landing page images
LandingPageImage.create photo: File.open(File.join(Rails.root, "lib", "seed_data", "carrots.jpg"))
LandingPageImage.create photo: File.open(File.join(Rails.root, "lib", "seed_data", "tomatoes.jpg"))
LandingPageImage.create photo: File.open(File.join(Rails.root, "lib", "seed_data", "potatoes.jpg"))
LandingPageImage.create photo: File.open(File.join(Rails.root, "lib", "seed_data", "potatoes.jpg"))

79
lib/tasks/dev.rake Normal file
View File

@@ -0,0 +1,79 @@
namespace :openfoodweb do
namespace :dev do
desc 'load sample data'
task :load_sample_data => :environment do
require File.expand_path('../../../spec/factories', __FILE__)
require File.expand_path('../../../spec/support/spree/init', __FILE__)
# -- Shipping / payment information
unless Spree::Zone.find_by_name 'Australia'
puts "[db:seed] Seeding shipping / payment information"
zone = FactoryGirl.create(:zone, :name => 'Australia', :zone_members => [])
country = Spree::Country.find_by_name('Australia')
Spree::ZoneMember.create(:zone => zone, :zoneable => country)
FactoryGirl.create(:shipping_method, :zone => zone)
FactoryGirl.create(:payment_method, :environment => 'development')
end
# -- Taxonomies
unless Spree::Taxonomy.find_by_name 'Products'
puts "[db:seed] Seeding taxonomies"
taxonomy = Spree::Taxonomy.find_by_name('Products') || FactoryGirl.create(:taxonomy, :name => 'Products')
taxonomy_root = taxonomy.root
['Vegetables', 'Fruit', 'Oils', 'Preserves and Sauces', 'Dairy', 'Meat and Fish'].each do |taxon_name|
FactoryGirl.create(:taxon, :name => taxon_name, :parent_id => taxonomy_root.id)
end
end
# -- Enterprises
unless Enterprise.count > 0
puts "[db:seed] Seeding enterprises"
3.times { FactoryGirl.create(:supplier_enterprise) }
3.times { FactoryGirl.create(:distributor_enterprise) }
end
unless Spree::ShippingMethod.all.any? { |sm| sm.calculator.is_a? OpenFoodWeb::Calculator::Itemwise }
FactoryGirl.create(:itemwise_shipping_method)
end
# -- Products
unless Spree::Product.count > 0
puts "[db:seed] Seeding products"
prod1 = FactoryGirl.create(:product,
:name => 'Garlic', :price => 20.00,
:supplier => Enterprise.is_primary_producer[0],
:taxons => [Spree::Taxon.find_by_name('Vegetables')])
ProductDistribution.create(:product => prod1,
:distributor => Enterprise.is_distributor[0],
:shipping_method => Spree::ShippingMethod.first)
prod2 = FactoryGirl.create(:product,
:name => 'Fuji Apple', :price => 5.00,
:supplier => Enterprise.is_primary_producer[1],
:taxons => [Spree::Taxon.find_by_name('Fruit')])
ProductDistribution.create(:product => prod2,
:distributor => Enterprise.is_distributor[1],
:shipping_method => Spree::ShippingMethod.first)
prod3 = FactoryGirl.create(:product,
:name => 'Beef - 5kg Trays', :price => 50.00,
:supplier => Enterprise.is_primary_producer[2],
:taxons => [Spree::Taxon.find_by_name('Meat and Fish')])
ProductDistribution.create(:product => prod3,
:distributor => Enterprise.is_distributor[2],
:shipping_method => Spree::ShippingMethod.first)
end
end
end
end

View File

@@ -0,0 +1,102 @@
require "spec_helper"
feature %q{
As a Super User
I want to setup users to manage an enterprise
} do
include AuthenticationWorkflow
include WebHelper
before(:each) do
@new_user = create_enterprise_user
@supplier1 = create(:supplier_enterprise, name: 'Supplier 1')
@supplier2 = create(:supplier_enterprise, name: 'Supplier 2')
@distributor1 = create(:distributor_enterprise, name: 'Distributor 3')
@distributor2 = create(:distributor_enterprise, name: 'Distributor 4')
end
context "creating an Enterprise User" do
context 'with no enterprises' do
scenario "assigning a user to an Enterprise" do
login_to_admin_section
click_link 'Users'
click_link @new_user.email
click_link 'Edit'
check @supplier2.name
click_button 'Update'
@new_user.enterprises.count.should == 1
@new_user.enterprises.first.name.should == @supplier2.name
end
end
context 'with existing enterprises' do
before(:each) do
@new_user.enterprise_roles.build(enterprise: @supplier1).save
@new_user.enterprise_roles.build(enterprise: @distributor1).save
end
scenario "removing and add enterprises for a user" do
login_to_admin_section
click_link 'Users'
click_link @new_user.email
click_link 'Edit'
uncheck @distributor1.name # remove
check @distributor2.name # add
click_button 'Update'
@new_user.enterprises.count.should == 2
@new_user.enterprises.should include(@supplier1)
@new_user.enterprises.should include(@distributor2)
end
end
end
context "Product management" do
context 'products I supply' do
before(:each) do
@new_user.enterprise_roles.build(enterprise: @supplier1).save
product1 = create(:product, name: 'Green eggs', supplier: @supplier1)
product2 = create(:product, name: 'Ham', supplier: @supplier2)
login_to_admin_as @new_user
end
scenario "manage products that I supply" do
visit 'admin/products'
within '#listing_products' do
page.should have_content 'Green eggs'
page.should_not have_content 'Ham'
end
end
end
end
context "System management lockdown" do
before(:each) do
@new_user.enterprise_roles.build(enterprise: @supplier1).save
login_to_admin_as @new_user
end
scenario "should not be able to see system configuration" do
visit 'admin/general_settings/edit'
page.should have_content 'Authorization Failure'
end
scenario "should not be able to see user management" do
visit 'admin/users'
page.should have_content 'Authorization Failure'
end
end
end

View File

@@ -59,5 +59,71 @@ feature %q{
product.group_buy_unit_size.should == 10.0
end
describe 'As an enterprise user' do
before(:each) do
@new_user = create_enterprise_user
@supplier2 = create(:supplier_enterprise, name: 'Another Supplier')
@new_user.enterprise_roles.build(enterprise: @supplier2).save
@new_user.enterprise_roles.build(enterprise: @distributors[0]).save
login_to_admin_as @new_user
end
scenario "create new product" do
click_link 'Products'
click_link 'New Product'
fill_in 'product_name', :with => 'A new product !!!'
fill_in 'product_price', :with => '19.99'
page.should have_selector('#product_supplier_id')
select 'Another Supplier', :from => 'product_supplier_id'
# Should only have suppliers listed which the user can manage
within "#product_supplier_id" do
page.should_not have_content @supplier.name
end
check @distributors[0].name
select 'My shipping method', :from => 'product_product_distributions_attributes_0_shipping_method_id'
# Should only have distributors listed which the user can manage
within "#product_product_distributions_field" do
page.should_not have_content @distributors[1].name
page.should_not have_content @distributors[2].name
end
click_button 'Create'
flash_message.should == 'Product "A new product !!!" has been successfully created!'
product = Spree::Product.find_by_name('A new product !!!')
product.supplier.should == @supplier2
product.distributors.should == [@distributors[0]]
end
describe 'with existing product' do
before(:each) do
@product = create(:product, supplier: @supplier2)
end
scenario "can edit" do
click_link 'Products'
click_link @product.name
page.should_not have_selector('#product_supplier_id')
click_link "Images"
page.should_not have_content 'Authorization Failure'
click_link "Variants"
page.should_not have_content 'Authorization Failure'
click_link "Product Properties"
page.should_not have_content 'Authorization Failure'
end
end
end
end
end

View File

@@ -58,6 +58,29 @@ describe Enterprise do
create(:simple_order_cycle, suppliers: [s], distributors: [d], variants: [p.master], orders_open_at: 1.week.from_now, orders_close_at: 2.weeks.from_now)
Enterprise.active_distributors.should be_empty
end
it "shows only enterprises for given user" do
user = create(:user)
user.spree_roles = []
e1 = create(:enterprise)
e2 = create(:enterprise)
e1.enterprise_roles.build(user: user).save
enterprises = Enterprise.managed_by user
enterprises.count.should == 1
enterprises.should include e1
end
it "shows all enterprises for admin user" do
user = create(:admin_user)
e1 = create(:enterprise)
e2 = create(:enterprise)
enterprises = Enterprise.managed_by user
enterprises.count.should == 2
enterprises.should include e1
enterprises.should include e2
end
end
describe "with_distributed_active_products_on_hand" do
@@ -102,9 +125,9 @@ describe Enterprise do
create(:product, :supplier => d1, :on_hand => 5)
create(:product, :supplier => d1, :on_hand => 5)
create(:product, :supplier => d2, :on_hand => 5)
create(:product, :supplier => d3, :on_hand => 5, :available_on => 1.week.from_now)
create(:product, :supplier => d4, :on_hand => 0)
create(:product, :supplier => d5).delete
create(:product, :supplier => d3, :on_hand => 5, :available_on => 1.week.from_now)
create(:product, :supplier => d4, :on_hand => 0)
create(:product, :supplier => d5).delete
Enterprise.with_supplied_active_products_on_hand.sort.should == [d1, d2]
Enterprise.with_supplied_active_products_on_hand.distinct_count.should == 2

View File

@@ -137,7 +137,7 @@ describe Spree::Order do
subject.distributor = test_enterprise
subject.should_not be_valid
subject.errors.messages.should == {base: ["Distributor or order cycle cannot supply the products in your cart"]}
subject.errors.messages.should == {distributor_id: ["cannot supply the products in your cart"]}
end
end
end

View File

@@ -154,6 +154,34 @@ module Spree
Product.in_order_cycle(oc1).should == [p1]
end
end
describe 'access roles' do
before(:each) do
@e1 = create(:enterprise)
@e2 = create(:enterprise)
@p1 = create(:product, supplier: @e1)
@p2 = create(:product, supplier: @e2)
end
it "shows only products for given user" do
user = create(:user)
user.spree_roles = []
@e1.enterprise_roles.build(user: user).save
product = Product.managed_by user
product.count.should == 1
product.should include @p1
end
it "shows all products for admin user" do
user = create(:admin_user)
product = Product.managed_by user
product.count.should == 2
product.should include @p1
product.should include @p2
end
end
end
describe "finders" do

View File

@@ -11,9 +11,20 @@ module AuthenticationWorkflow
admin_user.spree_roles << admin_role
login_to_admin_as admin_user
end
def create_enterprise_user
new_user = create(:user, email: 'enterprise@hub.com', password: 'blahblah', :password_confirmation => 'blahblah', )
new_user.spree_roles = [] # for some reason unbeknown to me, this new user gets admin permissions by default.
new_user.save
new_user
end
def login_to_admin_as user
visit spree.admin_path
fill_in 'spree_user_email', :with => 'admin@ofw.org'
fill_in 'spree_user_password', :with => 'passw0rd'
fill_in 'spree_user_email', :with => user.email
fill_in 'spree_user_password', :with => user.password
click_button 'Login'
end

View File

@@ -1,5 +1,3 @@
require 'factory_girl_rails'
# Initialise shipping method when created without one, like this:
# create(:product, :distributors => [...])
# In this case, we don't care what the shipping method is, but we need one for validations to pass.