Merge branch 'master' into new_shop

This commit is contained in:
Will Marshall
2014-05-22 10:43:02 +10:00
51 changed files with 426 additions and 87 deletions

View File

@@ -35,6 +35,7 @@ gem 'geocoder'
gem 'gmaps4rails'
gem 'spinjs-rails'
gem 'rack-ssl', :require => 'rack/ssl'
gem 'custom_error_message', :github => 'jeremydurham/custom-err-msg'
gem 'foreigner'
gem 'immigrant'

View File

@@ -6,6 +6,12 @@ GIT
actionpack (~> 3.0)
activemodel (~> 3.0)
GIT
remote: git://github.com/jeremydurham/custom-err-msg.git
revision: 3a8ec9dddc7a5b0aab7c69a6060596de300c68f4
specs:
custom_error_message (1.1.1)
GIT
remote: git://github.com/openfoodfoundation/spree.git
revision: da651b40f5c6cdd32e00b060729eb9aefd4f615f
@@ -492,6 +498,7 @@ DEPENDENCIES
coffee-rails (~> 3.2.1)
comfortable_mexican_sofa
compass-rails
custom_error_message!
database_cleaner (= 0.7.1)
db2fog
debugger-linecache

View File

@@ -1,3 +1,3 @@
window.Admin = angular.module("ofn.admin", ["ngResource","ofn.dropdown"]).config ($httpProvider) ->
window.Admin = angular.module("ofn.admin", ["ngResource", "ngAnimate", "ofn.dropdown"]).config ($httpProvider) ->
$httpProvider.defaults.headers.common["X-CSRF-Token"] = $("meta[name=csrf-token]").attr("content")
$httpProvider.defaults.headers.common["Accept"] = "application/json, text/javascript, */*"

View File

@@ -11,6 +11,7 @@
//= require shared/jquery-ui-timepicker-addon
//= require angular
//= require angular-resource
//= require angular-animate
//= require admin/spree_core
//= require admin/spree_auth
//= require admin/spree_promo

View File

@@ -0,0 +1,10 @@
Admin.controller "AdminEnterpriseRelationshipsCtrl", ($scope, EnterpriseRelationships, Enterprises) ->
$scope.EnterpriseRelationships = EnterpriseRelationships
$scope.Enterprises = Enterprises
$scope.create = ->
$scope.EnterpriseRelationships.create($scope.parent_id, $scope.child_id)
$scope.delete = (enterprise_relationship) ->
if confirm("Are you sure?")
$scope.EnterpriseRelationships.delete enterprise_relationship

View File

@@ -0,0 +1,18 @@
Admin.factory 'EnterpriseRelationships', ($http, enterprise_relationships) ->
new class EnterpriseRelationships
create_errors: ""
constructor: ->
@enterprise_relationships = enterprise_relationships
create: (parent_id, child_id) ->
$http.post('/admin/enterprise_relationships', {enterprise_relationship: {parent_id: parent_id, child_id: child_id}}).success (data, status) =>
@enterprise_relationships.unshift(data)
@create_errors = ""
.error (response, status) =>
@create_errors = response.errors
delete: (er) ->
$http.delete('/admin/enterprise_relationships/' + er.id).success (data) =>
@enterprise_relationships.splice @enterprise_relationships.indexOf(er), 1

View File

@@ -0,0 +1,4 @@
Admin.factory 'Enterprises', (enterprises) ->
new class Enterprises
constructor: ->
@enterprises = enterprises

View File

@@ -1,8 +1,11 @@
Darkswarm.controller "LoginCtrl", ($scope, $http, $location, AuthenticationService) ->
Darkswarm.controller "LoginCtrl", ($scope, $http, AuthenticationService, Redirections) ->
$scope.path = "/login"
$scope.submit = ->
$http.post("/user/spree_user/sign_in", {spree_user: $scope.spree_user}).success (data)->
location.href = location.origin + location.pathname # Strips out hash fragments
if Redirections.after_login
location.href = location.origin + Redirections.after_login
else
location.href = location.origin + location.pathname # Strips out hash fragments
.error (data) ->
$scope.errors = data.message

View File

@@ -1,4 +1,5 @@
Darkswarm.controller "ProductNodeCtrl", ($scope) ->
$scope.price = ->
if $scope.product.variants.length > 0
prices = (v.price for v in $scope.product.variants)
@@ -6,4 +7,6 @@ Darkswarm.controller "ProductNodeCtrl", ($scope) ->
else
$scope.product.price
$scope.producer = $scope.product.supplier
$scope.hasVariants = $scope.product.variants.length > 0

View File

@@ -12,5 +12,3 @@ Darkswarm.controller "ProductsCtrl", ($scope, $rootScope, Product, OrderCycle) -
code = e.keyCode || e.which
if code == 13
e.preventDefault()
$scope.productPrice = (product) ->

View File

@@ -8,7 +8,10 @@ Darkswarm.directive "ofnModal", ($modal)->
link: (scope, elem, attrs, ctrl, transclude)->
scope.title = attrs.title
contents = null
transclude scope, (clone)->
# We're using an isolate scope, which is a child of the original scope
# We have to compile the transclude against the original scope, not the isolate
transclude scope.$parent, (clone)->
contents = clone
elem.on "click", =>

View File

@@ -1,4 +1,4 @@
Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location)->
Darkswarm.factory "AuthenticationService", (Navigation, $modal, $location, Redirections)->
new class AuthenticationService
selectedPath: "/login"

View File

@@ -2,6 +2,9 @@ Darkswarm.factory 'Product', ($resource) ->
new class Product
constructor: ->
@update()
# TODO: don't need to scope this into object
# Already on object as far as controller scope is concerned
data:
products: null
loading: true

View File

@@ -0,0 +1,3 @@
Darkswarm.factory "Redirections", ($location)->
new class Redirections
after_login: $location.search().after_login

View File

@@ -0,0 +1,20 @@
// TODO: Provide -moz- and -o- directives
@-webkit-keyframes alert-flash
0%
background-color: #f9f1ae
100%
background-color: #fff
table#enterprise-relationships
th.actions, td.actions
width: 16%
.errors
color: #f00
tr.ng-enter
-webkit-animation-name: alert-flash
-webkit-animation-duration: 1200ms
-webkit-animation-iteration-count: 1
-webkit-animation-timing-function: ease-in-out

View File

@@ -19,9 +19,11 @@ table .blank-action {
}
input.search {
margin-bottom: 1em;
}
#new_enterprise_fee_set input.search {
float: right;
margin-bottom: 1em;
}
.ng .ng-invalid.ng-dirty {

View File

@@ -0,0 +1,24 @@
module Admin
class EnterpriseRelationshipsController < ResourceController
def index
@enterprises = Enterprise.managed_by(spree_current_user).by_name
@enterprise_relationships = EnterpriseRelationship.by_name
end
def create
@enterprise_relationship = EnterpriseRelationship.new params[:enterprise_relationship]
if @enterprise_relationship.save
render partial: "admin/json/enterprise_relationship", locals: {enterprise_relationship: @enterprise_relationship}
else
render status: 400, json: {errors: @enterprise_relationship.errors.full_messages.join(', ')}
end
end
def destroy
@enterprise_relationship = EnterpriseRelationship.find params[:id]
@enterprise_relationship.destroy
render nothing: true
end
end
end

View File

@@ -13,8 +13,7 @@ Spree::Admin::OverviewController.class_eval do
redirect_to '/unauthorized'
else
store_location
url = respond_to?(:spree_login_path) ? spree_login_path : root_path
redirect_to url
redirect_to root_path(anchor: "login?after_login=#{spree.admin_path}")
end
end
end

View File

@@ -28,6 +28,7 @@ Spree::OrdersController.class_eval do
end
populator = Spree::OrderPopulator.new(current_order(true), current_currency)
if populator.populate(params.slice(:products, :variants, :quantity))
fire_event('spree.cart.add')
fire_event('spree.order.contents_changed')
respond_with(@order) do |format|

View File

@@ -1,4 +1,9 @@
module SharedHelper
def inject_json(name, partial)
render "json/injection", name: name, partial: partial
end
def distributor_link_class(distributor)
cart = current_order(true)
@active_distributors ||= Enterprise.distributors_with_active_order_cycles

View File

@@ -3,5 +3,10 @@ class EnterpriseRelationship < ActiveRecord::Base
belongs_to :child, class_name: 'Enterprise'
validates_presence_of :parent_id, :child_id
validates_uniqueness_of :child_id, scope: :parent_id
validates_uniqueness_of :child_id, scope: :parent_id, message: "^That relationship is already established."
scope :with_enterprises,
joins('LEFT JOIN enterprises AS parent_enterprises ON parent_enterprises.id = enterprise_relationships.parent_id').
joins('LEFT JOIN enterprises AS child_enterprises ON child_enterprises.id = enterprise_relationships.child_id')
scope :by_name, with_enterprises.order('parent_enterprises.name, child_enterprises.name')
end

View File

@@ -0,0 +1,3 @@
:javascript
angular.module('ofn.admin').value('enterprise_relationships', #{render partial: "admin/json/enterprise_relationships", object: @enterprise_relationships});
angular.module('ofn.admin').value('enterprises', #{render partial: "admin/json/enterprises", object: @enterprises});

View File

@@ -0,0 +1,5 @@
%td {{ enterprise_relationship.parent_name }}
%td permits
%td {{ enterprise_relationship.child_name }}
%td.actions
%a.delete-enterprise-relationship.icon-trash.no-text{'ng-click' => 'delete(enterprise_relationship)'}

View File

@@ -0,0 +1,9 @@
%tr
%td
%select{name: "enterprise_relationship_parent_name", "ng-model" => "parent_id", "ng-options" => "e.id as e.name for e in Enterprises.enterprises"}
%td permits
%td
%select{name: "enterprise_relationship_child_name", "ng-model" => "child_id", "ng-options" => "e.id as e.name for e in Enterprises.enterprises"}
%td.actions
%input{type: "button", value: "Create", "ng-click" => "create()"}
.errors {{ EnterpriseRelationships.create_errors }}

View File

@@ -0,0 +1,15 @@
- content_for :page_title do
Enterprise Relationships
= render 'admin/shared/enterprises_sub_menu'
%div{"ng-app" => "ofn.admin", "ng-controller" => "AdminEnterpriseRelationshipsCtrl"}
= render 'data'
%input.search{"ng-model" => "query", "placeholder" => "Search"}
%table#enterprise-relationships
%tbody
= render 'form'
%tr{"ng-repeat" => "enterprise_relationship in EnterpriseRelationships.enterprise_relationships | filter:query"}
= render 'enterprise_relationship'

View File

@@ -8,6 +8,8 @@
</li>
<% end %>
<%= render 'admin/shared/enterprises_sub_menu' %>
<%= form_for @enterprise_set, :url => main_app.bulk_update_admin_enterprises_path do |f| %>
<table class="index" id="listing_enterprises">

View File

@@ -0,0 +1,11 @@
object @enterprise_relationship
attributes :id, :parent_id, :child_id
node :parent_name do |enterprise_relationship|
enterprise_relationship.parent.name
end
node :child_name do |enterprise_relationship|
enterprise_relationship.child.name
end

View File

@@ -0,0 +1,2 @@
collection @enterprise_relationships
extends "admin/json/enterprise_relationship"

View File

@@ -0,0 +1,3 @@
collection @enterprises
attributes :id, :name

View File

@@ -0,0 +1,4 @@
= content_for :sub_menu do
%ul#sub_nav.inline-menu{"data-hook" => "admin_order_sub_tabs"}
= tab :enterprises, url: main_app.admin_enterprises_path
= tab :relationships, url: main_app.admin_enterprise_relationships_path, match_path: '/enterprise_relationships'

View File

@@ -0,0 +1,2 @@
:javascript
angular.module('Darkswarm').value("#{name.to_s}", #{render "json/#{partial.to_s}"})

View File

@@ -15,16 +15,16 @@
= csrf_meta_tags
%body.off-canvas{"ng-app" => "Darkswarm"}
= inject_json "currentHub", "current_hub"
= inject_json "user", "current_user"
.off-canvas-wrap{offcanvas: true}
.inner-wrap
= render partial: "shared/current_hub"
= render partial: "shared/current_user"
= render partial: "shared/menu/menu"
= display_flash_messages
%ofn-flash
-#= render "shared/sidebar"
%section{ role: "main" }
= yield

View File

@@ -1,10 +1,9 @@
%ofn-modal{title: "{{ producer.name }}"}
#producer_modal{bindonce: true}
#producer_modal
.row
.small-12.columns
%img{"bo-src" => "producer.promo_image"}
%img{"ng-src" => "producer.promo_image"}
%h3 {{ producer.name }}
.row
.small-6.columns
%p

View File

@@ -1,2 +1,4 @@
%ofn-modal{title: "{{product.name}}"}
{{ product | json }}
{{ product.description }}

View File

@@ -1,2 +0,0 @@
:javascript
angular.module('Darkswarm').value('currentHub', #{render "json/current_hub"})

View File

@@ -1,2 +0,0 @@
:javascript
angular.module('Darkswarm').value('user', #{render "json/current_user"})

View File

@@ -6,11 +6,10 @@
%img{"bo-src" => "product.primary_taxon.icon",
"ng-click" => "ordering.order = 'primary_taxon.name'",
name: "{{product.primary_taxon.name}}"}
{{ product.name}}
-#= render partial: "shop/products/modal"
= render partial: "modals/product"
.small-5.columns.summary-header
{{ product.supplier.name }}
= render partial: "modals/producer"
.small-2.columns.summary-price.text-right.price
%span{"ng-if" => "hasVariants"}

View File

@@ -41,6 +41,8 @@ Openfoodnetwork::Application.routes.draw do
post :bulk_update, :on => :collection, :as => :bulk_update
end
resources :enterprise_relationships
resources :enterprise_fees do
post :bulk_update, :on => :collection, :as => :bulk_update
end

View File

@@ -15,6 +15,7 @@ describe Spree::OrdersController do
controller.stub(:current_order_cycle).and_return(order_cycle)
controller.stub(:current_order).and_return order
order.stub_chain(:line_items, :empty?).and_return true
session[:access_token] = order.token
spree_get :edit
response.should redirect_to shop_path
end

View File

@@ -96,6 +96,9 @@ FactoryGirl.define do
is_distributor true
end
factory :enterprise_relationship do
end
factory :enterprise_group, :class => EnterpriseGroup do
name 'Enterprise group'
description 'this is a group'
@@ -187,6 +190,10 @@ FactoryGirl.modify do
distributors { [Enterprise.is_distributor.first || FactoryGirl.create(:distributor_enterprise)] }
end
factory :option_type do
# Prevent inconsistent ordering in specs when all option types have the same (0) position
sequence(:position)
end
end

View File

@@ -0,0 +1,76 @@
require 'spec_helper'
feature %q{
As an Administrator
I want to manage relationships between enterprises
}, js: true do
include AuthenticationWorkflow
include WebHelper
before { login_to_admin_section }
scenario "listing relationships" do
# Given some enterprises with relationships
e1, e2, e3, e4 = create(:enterprise), create(:enterprise), create(:enterprise), create(:enterprise)
create(:enterprise_relationship, parent: e1, child: e2)
create(:enterprise_relationship, parent: e3, child: e4)
# When I go to the relationships page
click_link 'Enterprises'
click_link 'Relationships'
# Then I should see the relationships
within('table#enterprise-relationships') do
page.should have_table_row [e1.name, 'permits', e2.name, '']
page.should have_table_row [e3.name, 'permits', e4.name, '']
end
end
scenario "creating a relationship" do
e1 = create(:enterprise, name: 'One')
e2 = create(:enterprise, name: 'Two')
visit admin_enterprise_relationships_path
select 'One', from: 'enterprise_relationship_parent_name'
select 'Two', from: 'enterprise_relationship_child_name'
click_button 'Create'
page.should have_table_row [e1.name, 'permits', e2.name, '']
EnterpriseRelationship.where(parent_id: e1, child_id: e2).should be_present
end
scenario "attempting to create a relationship with invalid data" do
e1 = create(:enterprise, name: 'One')
e2 = create(:enterprise, name: 'Two')
create(:enterprise_relationship, parent: e1, child: e2)
expect do
# When I attempt to create a duplicate relationship
visit admin_enterprise_relationships_path
select 'One', from: 'enterprise_relationship_parent_name'
select 'Two', from: 'enterprise_relationship_child_name'
click_button 'Create'
# Then I should see an error message
page.should have_content "That relationship is already established."
end.to change(EnterpriseRelationship, :count).by(0)
end
scenario "deleting a relationship" do
e1 = create(:enterprise, name: 'One')
e2 = create(:enterprise, name: 'Two')
er = create(:enterprise_relationship, parent: e1, child: e2)
visit admin_enterprise_relationships_path
page.should have_table_row [e1.name, 'permits', e2.name, '']
first("a.delete-enterprise-relationship").click
page.should_not have_table_row [e1.name, 'permits', e2.name, '']
EnterpriseRelationship.where(id: er.id).should be_empty
end
end

View File

@@ -5,7 +5,27 @@ feature "Authentication", js: true do
describe "login" do
let(:user) { create(:user, password: "password", password_confirmation: "password") }
describe "newskool" do
describe "With redirects" do
scenario "logging in with a redirect set" do
visit groups_path(anchor: "login?after_login=#{producers_path}")
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_login_button
page.should have_content "Select a producer from the list below"
current_path.should == producers_path
end
scenario "logging into admin redirects home, then back to admin" do
visit spree.admin_path
fill_in "Email", with: user.email
fill_in "Password", with: user.password
click_login_button
page.should have_content "Dashboard"
current_path.should == spree.admin_path
end
end
describe "Loggin in from the home page" do
before do
visit root_path
end

View File

@@ -19,14 +19,3 @@ describe "HubNodeCtrl", ->
expect(scope.current()).toEqual false
scope.hub = {id: 99}
expect(scope.current()).toEqual true
it "knows whether selecting this hub will empty the cart", ->
CurrentHub.id = undefined
expect(scope.emptiesCart()).toEqual false
CurrentHub.id = 99
scope.hub.id = 99
expect(scope.emptiesCart()).toEqual false
scope.hub.id = 1
expect(scope.emptiesCart()).toEqual true

View File

@@ -0,0 +1,24 @@
describe "ProductNodeCtrl", ->
ctrl = null
scope = null
product =
id: 99
price: 10.00
variants: []
beforeEach ->
module('Darkswarm')
inject ($controller) ->
scope =
product: product
ctrl = $controller 'ProductNodeCtrl', {$scope: scope}
describe "determining the price to display for a product", ->
it "displays the product price when the product does not have variants", ->
expect(scope.price()).toEqual 10.00
it "displays the minimum variant price when the product has variants", ->
scope.product =
price: 11
variants: [{price: 22}, {price: 33}]
expect(scope.price()).toEqual 22

View File

@@ -1,47 +1,21 @@
describe 'All controllers', ->
describe 'ProductsCtrl', ->
ctrl = null
scope = null
event = null
Product = null
describe 'ProductsCtrl', ->
ctrl = null
scope = null
event = null
Product = null
beforeEach ->
module('Darkswarm')
Product =
all: ->
update: ->
data: "testy mctest"
OrderCycle =
order_cycle: {}
inject ($controller) ->
scope = {}
ctrl = $controller 'ProductsCtrl', {$scope: scope, Product: Product, OrderCycle: OrderCycle}
it 'fetches products from Product', ->
expect(scope.data).toEqual 'testy mctest'
describe "determining the price to display for a product", ->
it "displays the product price when the product does not have variants", ->
product = {variants: [], price: 12.34}
expect(scope.productPrice(product)).toEqual 12.34
it "displays the minimum variant price when the product has variants", ->
product =
price: 11
variants: [{price: 22}, {price: 33}]
expect(scope.productPrice(product)).toEqual 22
describe 'OrderCycleCtrl', ->
ctrl = null
scope = null
event = null
product_ctrl = null
OrderCycle = null
beforeEach ->
module 'Darkswarm'
beforeEach ->
module('Darkswarm')
Product =
all: ->
update: ->
data: "testy mctest"
OrderCycle =
order_cycle: {}
inject ($controller) ->
scope = {}
inject ($controller) ->
scope = {}
ctrl = $controller 'OrderCycleCtrl', {$scope: scope}
ctrl = $controller 'ProductsCtrl', {$scope: scope, Product: Product, OrderCycle: OrderCycle}
it 'fetches products from Product', ->
expect(scope.data).toEqual 'testy mctest'

View File

@@ -26,8 +26,10 @@ describe 'Order service', ->
}
angular.module('Darkswarm').value('order', orderData)
module 'Darkswarm'
inject ($injector, _$httpBackend_)->
$httpBackend = _$httpBackend_
$httpBackend.expectGET("/shop/products").respond 200, []
Order = $injector.get("Order")
Navigation = $injector.get("Navigation")
flash = $injector.get("flash")

View File

@@ -10,5 +10,5 @@ describe 'Product service', ->
it "Fetches products from the backend on init", ->
$httpBackend.expectGET("/shop/products").respond([{test : "cats"}])
products = Product.all()
$httpBackend.flush()
expect(Product.data.products[0].test).toEqual "cats"

View File

@@ -0,0 +1,16 @@
require 'spec_helper'
describe EnterpriseRelationship do
describe "scopes" do
it "sorts by parent, child enterprise name" do
e1 = create(:enterprise, name: 'A')
e2 = create(:enterprise, name: 'B')
e3 = create(:enterprise, name: 'C')
er1 = create(:enterprise_relationship, parent: e1, child: e3)
er2 = create(:enterprise_relationship, parent: e2, child: e1)
er3 = create(:enterprise_relationship, parent: e1, child: e2)
EnterpriseRelationship.by_name.should == [er3, er1, er2]
end
end
end

View File

@@ -31,10 +31,10 @@ describe Enterprise do
let(:e) { create(:distributor_enterprise) }
let(:p) { create(:supplier_enterprise) }
let(:c) { create(:distributor_enterprise) }
before do
EnterpriseRelationship.create! parent_id: p.id, child_id: e.id
EnterpriseRelationship.create! parent_id: e.id, child_id: c.id
end
let!(:er1) { create(:enterprise_relationship, parent_id: p.id, child_id: e.id) }
let!(:er2) { create(:enterprise_relationship, parent_id: e.id, child_id: c.id) }
it "finds relatives" do
e.relatives.sort.should == [p, c].sort
end

View File

@@ -0,0 +1,43 @@
RSpec::Matchers.define :have_table_row do |row|
match_for_should do |node|
@row = row
false_on_timeout_error do
wait_until { rows_under(node).include? row }
end
end
match_for_should_not do |node|
@row = row
false_on_timeout_error do
# Without this sleep, we trigger capybara's wait when looking up the table, for the full
# period of default_wait_time.
sleep 0.1
wait_until { !rows_under(node).include? row }
end
end
failure_message_for_should do |text|
"expected to find table row #{@row}"
end
failure_message_for_should_not do |text|
"expected not to find table row #{@row}"
end
def rows_under(node)
node.all('tr').map { |tr| tr.all('th, td').map(&:text) }
end
def false_on_timeout_error
yield
rescue TimeoutError
false
else
true
end
end

View File

@@ -0,0 +1,23 @@
require 'spec_helper'
describe "admin/json/_enterprise_relationships.json.rabl" do
let(:parent) { create(:enterprise) }
let(:child) { create(:enterprise) }
let(:enterprise_relationship) { create(:enterprise_relationship, parent: parent, child: child) }
let(:render) { Rabl.render([enterprise_relationship], 'admin/json/enterprise_relationships', view_path: 'app/views', scope: RablHelper::FakeContext.instance) }
it "renders a list of enterprise relationships" do
render.should have_json_type(Array).at_path ''
render.should have_json_type(Object).at_path '0'
end
it "renders enterprise ids" do
render.should be_json_eql(parent.id).at_path '0/parent_id'
render.should be_json_eql(child.id).at_path '0/child_id'
end
it "renders enterprise names" do
render.should be_json_eql(parent.name.to_json).at_path '0/parent_name'
render.should be_json_eql(child.name.to_json).at_path '0/child_name'
end
end