Merge branch 'order-cycles-exchange-products'

Conflicts:
	Gemfile.lock
This commit is contained in:
Rohan Mitchell
2013-01-22 10:40:56 +11:00
53 changed files with 12634 additions and 338 deletions

View File

@@ -145,13 +145,13 @@ GEM
multi_json (~> 1.0)
builder (3.0.4)
cancan (1.6.7)
capybara (1.1.3)
capybara (2.0.2)
mime-types (>= 1.16)
nokogiri (>= 1.3.3)
rack (>= 1.0.0)
rack-test (>= 0.5.4)
selenium-webdriver (~> 2.0)
xpath (~> 0.1.4)
xpath (~> 1.0.0)
childprocess (0.3.6)
ffi (~> 1.0, >= 1.0.6)
cocaine (0.4.2)
@@ -236,7 +236,7 @@ GEM
nested_set (1.7.0)
activerecord (>= 3.0.0)
railties (>= 3.0.0)
nokogiri (1.5.5)
nokogiri (1.5.6)
orm_adapter (0.0.7)
paperclip (2.8.0)
activerecord (>= 2.3.0)
@@ -257,7 +257,7 @@ GEM
rabl (0.6.5)
activesupport (>= 2.3.14)
multi_json (~> 1.0)
rack (1.4.3)
rack (1.4.4)
rack-cache (1.2)
rack (>= 0.4)
rack-ssl (1.3.2)
@@ -352,7 +352,7 @@ GEM
warden (1.1.1)
rack (>= 1.0)
websocket (1.0.6)
xpath (0.1.4)
xpath (1.0.0)
nokogiri (~> 1.3)
PLATFORMS

View File

@@ -1,153 +0,0 @@
function AdminCreateOrderCycleCtrl($scope, OrderCycle, Enterprise) {
$scope.enterprises = Enterprise.index();
$scope.order_cycle = OrderCycle.order_cycle;
$scope.addSupplier = function($event) {
OrderCycle.addSupplier($event, $scope.new_supplier_id);
};
$scope.submit = function() {
OrderCycle.create();
};
}
function AdminEditOrderCycleCtrl($scope, OrderCycle, Enterprise) {
$scope.enterprises = Enterprise.index();
var order_cycle_id = window.location.pathname.match(/\/admin\/order_cycles\/(\d+)/)[1];
$scope.order_cycle = OrderCycle.load(order_cycle_id);
$scope.addSupplier = function($event) {
OrderCycle.addSupplier($event, $scope.new_supplier_id);
};
$scope.submit = function() {
OrderCycle.update();
};
}
angular.module('order_cycle', ['ngResource']).
config(function($httpProvider) {
$httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content');
}).
factory('OrderCycle', function($resource) {
var OrderCycle = $resource('/admin/order_cycles/:order_cycle_id.json', {},
{'index': { method: 'GET', isArray: true},
'create': { method: 'POST'},
'update': { method: 'PUT'}});
return {
order_cycle: {incoming_exchanges: [],
outgoing_exchanges: []},
addSupplier: function(event, new_supplier_id) {
event.preventDefault();
this.order_cycle.incoming_exchanges.push({enterprise_id: new_supplier_id, active: true});
},
load: function(order_cycle_id) {
var service = this;
OrderCycle.get({order_cycle_id: order_cycle_id}, function(oc) {
$.extend(service.order_cycle, oc);
service.order_cycle.incoming_exchanges = [];
service.order_cycle.outgoing_exchanges = [];
for(i in service.order_cycle.exchanges) {
var exchange = service.order_cycle.exchanges[i];
if(exchange.sender_id == service.order_cycle.coordinator_id) {
service.order_cycle.outgoing_exchanges.push({enterprise_id: exchange.receiver_id, active: true});
} else if(exchange.receiver_id == service.order_cycle.coordinator_id) {
service.order_cycle.incoming_exchanges.push({enterprise_id: exchange.sender_id, active: true});
} else {
console.log('Exchange between two enterprises, neither of which is coordinator!');
}
}
delete(service.order_cycle.exchanges);
});
return this.order_cycle;
},
create: function() {
this.removeInactiveExchanges();
var oc = new OrderCycle({order_cycle: this.order_cycle});
oc.$create(function(data) {
if(data['success']) {
window.location = '/admin/order_cycles';
} else {
console.log('fail');
}
});
},
update: function() {
this.removeInactiveExchanges();
var oc = new OrderCycle({order_cycle: this.order_cycle});
oc.$update({order_cycle_id: this.order_cycle.id}, function(data) {
if(data['success']) {
window.location = '/admin/order_cycles';
} else {
console.log('fail');
}
});
},
removeInactiveExchanges: function() {
for(var i=0; i < this.order_cycle.incoming_exchanges.length; i++) {
if(!this.order_cycle.incoming_exchanges[i].active) {
this.order_cycle.incoming_exchanges.splice(i, 1);
i--;
}
}
}
};
}).
factory('Enterprise', function($resource) {
var Enterprise = $resource('/admin/enterprises/:enterprise_id.json', {},
{'index': { method: 'GET', isArray: true}});
return {
enterprises: {},
index: function() {
var service = this;
Enterprise.index(function(data) {
for(i in data) {
service.enterprises[data[i]['id']] = data[i];
}
});
return this.enterprises;
}
};
}).
directive('datetimepicker', ['$parse', function($parse) {
return function(scope, element, attrs) {
// using $parse instead of scope[attrs.datetimepicker] for cases
// where attrs.datetimepicker is 'foo.bar.lol'
$(element).datetimepicker({
dateFormat: 'yy-mm-dd',
timeFormat: 'HH:mm:ss',
showOn: "button",
buttonImage: "<%= asset_path 'datepicker/cal.gif' %>",
buttonImageOnly: true,
stepMinute: 15,
onSelect: function(dateText, inst) {
scope.$apply(function() {
parsed = $parse(attrs.datetimepicker);
parsed.assign(scope, dateText);
});
}
});
};
}]);

View File

@@ -0,0 +1,225 @@
app = angular.module('order_cycle', ['ngResource'])
app.controller 'AdminCreateOrderCycleCtrl', ($scope, OrderCycle, Enterprise) ->
$scope.enterprises = Enterprise.index()
$scope.supplied_products = Enterprise.supplied_products
$scope.order_cycle = OrderCycle.order_cycle
$scope.exchangeSelectedVariants = (exchange) ->
OrderCycle.exchangeSelectedVariants(exchange)
$scope.enterpriseTotalVariants = (enterprise) ->
Enterprise.totalVariants(enterprise)
$scope.productSuppliedToOrderCycle = (product) ->
OrderCycle.productSuppliedToOrderCycle(product)
$scope.variantSuppliedToOrderCycle = (variant) ->
OrderCycle.variantSuppliedToOrderCycle(variant)
$scope.incomingExchangesVariants = ->
OrderCycle.incomingExchangesVariants()
$scope.toggleProducts = ($event, exchange) ->
$event.preventDefault()
OrderCycle.toggleProducts(exchange)
$scope.addSupplier = ($event) ->
$event.preventDefault()
OrderCycle.addSupplier($scope.new_supplier_id)
$scope.addDistributor = ($event) ->
$event.preventDefault()
OrderCycle.addDistributor($scope.new_distributor_id)
$scope.submit = ->
OrderCycle.create()
app.controller 'AdminEditOrderCycleCtrl', ($scope, $location, OrderCycle, Enterprise) ->
$scope.enterprises = Enterprise.index()
$scope.supplied_products = Enterprise.supplied_products
order_cycle_id = $location.absUrl().match(/\/admin\/order_cycles\/(\d+)/)[1]
$scope.order_cycle = OrderCycle.load(order_cycle_id)
$scope.exchangeSelectedVariants = (exchange) ->
OrderCycle.exchangeSelectedVariants(exchange)
$scope.enterpriseTotalVariants = (enterprise) ->
Enterprise.totalVariants(enterprise)
$scope.productSuppliedToOrderCycle = (product) ->
OrderCycle.productSuppliedToOrderCycle(product)
$scope.variantSuppliedToOrderCycle = (variant) ->
OrderCycle.variantSuppliedToOrderCycle(variant)
$scope.incomingExchangesVariants = ->
OrderCycle.incomingExchangesVariants()
$scope.toggleProducts = ($event, exchange) ->
$event.preventDefault()
OrderCycle.toggleProducts(exchange)
$scope.addSupplier = ($event) ->
$event.preventDefault()
OrderCycle.addSupplier($scope.new_supplier_id)
$scope.addDistributor = ($event) ->
$event.preventDefault()
OrderCycle.addDistributor($scope.new_distributor_id)
$scope.submit = ->
OrderCycle.update()
app.config ($httpProvider) ->
$httpProvider.defaults.headers.common['X-CSRF-Token'] = $('meta[name=csrf-token]').attr('content')
app.factory 'OrderCycle', ($resource, $window) ->
OrderCycle = $resource '/admin/order_cycles/:order_cycle_id.json', {}, {
'index': { method: 'GET', isArray: true}
'create': { method: 'POST'}
'update': { method: 'PUT'}}
{
order_cycle:
incoming_exchanges: []
outgoing_exchanges: []
exchangeSelectedVariants: (exchange) ->
numActiveVariants = 0
numActiveVariants++ for id, active of exchange.variants when active
numActiveVariants
toggleProducts: (exchange) ->
exchange.showProducts = !exchange.showProducts
addSupplier: (new_supplier_id) ->
this.order_cycle.incoming_exchanges.push({enterprise_id: new_supplier_id, active: true, variants: {}})
addDistributor: (new_distributor_id) ->
this.order_cycle.outgoing_exchanges.push({enterprise_id: new_distributor_id, active: true, variants: {}})
productSuppliedToOrderCycle: (product) ->
product_variant_ids = (variant.id for variant in product.variants)
variant_ids = [product.master_id].concat(product_variant_ids)
incomingExchangesVariants = this.incomingExchangesVariants()
# TODO: This is an O(n^2) implementation of set intersection and thus is slooow.
# Use a better algorithm if needed.
# Also, incomingExchangesVariants is called every time, when it only needs to be
# called once per change to incoming variants. Some sort of caching?
ids = (variant_id for variant_id in variant_ids when incomingExchangesVariants.indexOf(variant_id) != -1)
ids.length > 0
variantSuppliedToOrderCycle: (variant) ->
this.incomingExchangesVariants().indexOf(variant.id) != -1
incomingExchangesVariants: ->
variant_ids = []
for exchange in this.order_cycle.incoming_exchanges
variant_ids.push(parseInt(id)) for id, active of exchange.variants when active
variant_ids
load: (order_cycle_id) ->
service = this
OrderCycle.get {order_cycle_id: order_cycle_id}, (oc) ->
angular.extend(service.order_cycle, oc)
service.order_cycle.incoming_exchanges = []
service.order_cycle.outgoing_exchanges = []
for exchange in service.order_cycle.exchanges
if exchange.sender_id == service.order_cycle.coordinator_id
angular.extend(exchange, {enterprise_id: exchange.receiver_id, active: true})
delete(exchange.sender_id)
service.order_cycle.outgoing_exchanges.push(exchange)
else if exchange.receiver_id == service.order_cycle.coordinator_id
angular.extend(exchange, {enterprise_id: exchange.sender_id, active: true})
delete(exchange.receiver_id)
service.order_cycle.incoming_exchanges.push(exchange)
else
console.log('Exchange between two enterprises, neither of which is coordinator!')
delete(service.order_cycle.exchanges)
this.order_cycle
create: ->
this.removeInactiveExchanges()
oc = new OrderCycle({order_cycle: this.order_cycle})
oc.$create (data) ->
if data['success']
$window.location = '/admin/order_cycles'
else
console.log('fail')
update: ->
this.removeInactiveExchanges()
oc = new OrderCycle({order_cycle: this.order_cycle})
oc.$update {order_cycle_id: this.order_cycle.id}, (data) ->
if data['success']
$window.location = '/admin/order_cycles'
else
console.log('fail')
removeInactiveExchanges: ->
this.order_cycle.incoming_exchanges =
(exchange for exchange in this.order_cycle.incoming_exchanges when exchange.active)
this.order_cycle.outgoing_exchanges =
(exchange for exchange in this.order_cycle.outgoing_exchanges when exchange.active)
}
app.factory 'Enterprise', ($resource) ->
Enterprise = $resource('/admin/enterprises/:enterprise_id.json', {}, {'index': {method: 'GET', isArray: true}})
{
Enterprise: Enterprise
enterprises: {}
supplied_products: []
index: ->
service = this
Enterprise.index (data) ->
for enterprise in data
service.enterprises[enterprise.id] = enterprise
for product in enterprise.supplied_products
service.supplied_products.push(product)
this.enterprises
totalVariants: (enterprise) ->
numVariants = 0
counts = for product in enterprise.supplied_products
numVariants += if product.variants.length == 0 then 1 else product.variants.length
numVariants
}
app.directive 'datetimepicker', ['$parse', ($parse) ->
(scope, element, attrs) ->
# using $parse instead of scope[attrs.datetimepicker] for cases
# where attrs.datetimepicker is 'foo.bar.lol'
$(element).datetimepicker
dateFormat: 'yy-mm-dd'
timeFormat: 'HH:mm:ss'
showOn: "button"
buttonImage: "<%= asset_path 'datepicker/cal.gif' %>"
buttonImageOnly: true
stepMinute: 15
onSelect: (dateText, inst) ->
scope.$apply ->
parsed = $parse(attrs.datetimepicker)
parsed.assign(scope, dateText)
]

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -36,5 +36,36 @@ form.order_cycle {
tr td.active {
width: 20px;
}
tr.supplier td {
border-bottom: 2px solid #C3D9FF;
}
.exchange-product {
float: left;
overflow: auto;
width: 18%;
min-height: 7.5em;
margin: 0 1% 1% 0;
border: 1px solid #DAE7FF;
padding: 5px;
.exchange-product-details {
clear: both;
margin-bottom: 1em;
.supplier {
font-weight: bold;
}
}
.exchange-product-variant {
float: left;
margin-right: 2em;
}
}
}
.actions {
margin-top: 3em;
}
}

View File

@@ -42,7 +42,7 @@ module Admin
if @order_cycle.update_attributes(params[:order_cycle])
OpenFoodWeb::OrderCycleFormApplicator.new(@order_cycle).go!
flash[:notice] = 'Your order cycle has been saved.'
flash[:notice] = 'Your order cycle has been updated.'
format.html { redirect_to admin_order_cycles_path }
format.json { render :json => {:success => true} }
else

View File

@@ -0,0 +1,5 @@
module OrderCyclesHelper
def coordinating_enterprises
Enterprise.is_distributor.order('name')
end
end

View File

@@ -12,4 +12,6 @@ class Exchange < ActiveRecord::Base
validates_presence_of :order_cycle, :sender, :receiver
validates_uniqueness_of :sender_id, :scope => [:order_cycle_id, :receiver_id]
accepts_nested_attributes_for :variants
end

View File

@@ -0,0 +1,13 @@
class VariantPresenter
attr_accessor :variant
def initialize(variant)
@variant = variant
end
delegate :id, :to => :variant
def image_url
@variant.images.first.attachment.url :mini if @variant.images.present?
end
end

View File

@@ -1,4 +1,15 @@
r.list_of :enterprises, @collection do
r.element :id
r.element :name
r.list_of :supplied_products do |product|
r.element :name
r.element :supplier_name, product.supplier.andand.name
r.element :image_url, product.images.present? ? product.images.first.attachment.url(:mini) : nil
r.element :master_id, product.master.id
r.list_of :variants do |variant|
r.element :id
r.element :label, variant.options_text
end
end
end

View File

@@ -0,0 +1,10 @@
%td{:colspan => 3}
.exchange-product{'ng-repeat' => 'product in supplied_products | filter:productSuppliedToOrderCycle'}
.exchange-product-details
.supplier {{ product.supplier_name }}
= check_box_tag 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', 1, 1, 'ng-hide' => 'product.variants', 'ng-model' => 'exchange.variants[product.master_id]', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}'
%img{'ng-src' => '{{ product.image_url }}'}
{{ product.name }}
.exchange-product-variant{'ng-repeat' => 'variant in product.variants | filter:variantSuppliedToOrderCycle'}
= check_box_tag 'order_cycle_outgoing_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}', 1, 1, 'ng-model' => 'exchange.variants[variant.id]', 'id' => 'order_cycle_outgoing_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}'
{{ variant.label }}

View File

@@ -0,0 +1,15 @@
%td.active= check_box_tag 'order_cycle_exchange_{{ $index }}_active', 1, 1, 'ng-model' => 'exchange.active', 'id' => 'order_cycle_exchange_{{ $index }}_active'
%td{:class => "#{type}_name"} {{ enterprises[exchange.enterprise_id].name }}
%td.products
= f.submit 'Products', 'ng-click' => 'toggleProducts($event, exchange)'
{{ exchangeSelectedVariants(exchange) }} /
- if type == 'supplier'
{{ enterpriseTotalVariants(enterprises[exchange.enterprise_id]) }}
- else
{{ incomingExchangesVariants().length }}
selected
- if type == 'distributor'
%td.collection-details
= text_field_tag 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', '', 'id' => 'order_cycle_outgoing_exchange_{{ $index }}_pickup_time', 'placeholder' => 'Pickup time / date', 'ng-model' => 'exchange.pickup_time'
%br/
= text_field_tag 'order_cycle_outgoing_exchange_{{ $index }}_pickup_instructions', '', 'id' => 'order_cycle_outgoing_exchange_{{ $index }}_pickup_instructions', 'placeholder' => 'Delivery instructions', 'ng-model' => 'exchange.pickup_instructions'

View File

@@ -0,0 +1,10 @@
/ TODO: Unify this with exchange_distributed_products_form
%td{:colspan => 3}
.exchange-product{'ng-repeat' => 'product in enterprises[exchange.enterprise_id].supplied_products'}
.exchange-product-details
= check_box_tag 'order_cycle_incoming_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}', 1, 1, 'ng-hide' => 'product.variants', 'ng-model' => 'exchange.variants[product.master_id]', 'id' => 'order_cycle_incoming_exchange_{{ $parent.$index }}_variants_{{ product.master_id }}'
%img{'ng-src' => '{{ product.image_url }}'}
{{ product.name }}
.exchange-product-variant{'ng-repeat' => 'variant in product.variants'}
= check_box_tag 'order_cycle_incoming_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}', 1, 1, 'ng-model' => 'exchange.variants[variant.id]', 'id' => 'order_cycle_incoming_exchange_{{ $parent.$parent.$index }}_variants_{{ variant.id }}'
{{ variant.label }}

View File

@@ -13,9 +13,16 @@
%h2 Incoming
%table.exchanges
%tr{'ng-repeat' => 'exchange in order_cycle.incoming_exchanges'}
%td.active= check_box_tag 'order_cycle_exchange_{{ $index }}_active', 1, 1, 'ng-model' => 'exchange.active', 'id' => 'order_cycle_exchange_{{ $index }}_active'
%td {{ enterprises[exchange.enterprise_id].name }}
%thead
%tr
%th
%th Supplier
%th Products
%tbody{'ng-repeat' => 'exchange in order_cycle.incoming_exchanges'}
%tr.supplier
= render 'exchange_form', :f => f, :type => 'supplier'
%tr.products{'ng-show' => 'exchange.showProducts'}
= render 'exchange_supplied_products_form'
= select_tag :new_supplier_id, options_from_collection_for_select(Enterprise.is_primary_producer, :id, :name), {'ng-model' => 'new_supplier_id'}
= f.submit 'Add supplier', 'ng-click' => 'addSupplier($event)'
@@ -23,17 +30,33 @@
%h2 Coordinator
= f.label :coordinator_id, 'Coordinator'
= f.collection_select :coordinator_id, Enterprise.all, :id, :name, {}, {'ng-model' => 'order_cycle.coordinator_id', 'required' => true}
= f.collection_select :coordinator_id, coordinating_enterprises, :id, :name, {}, {'ng-model' => 'order_cycle.coordinator_id', 'required' => true}
%h2 Outgoing
%p TODO
%table.exchanges
%thead
%tr
%th
%th Distributor
%th Products
%th Collection details
%tbody{'ng-repeat' => 'exchange in order_cycle.outgoing_exchanges'}
%tr.distributor
= render 'exchange_form', :f => f, :type => 'distributor'
%tr.products{'ng-show' => 'exchange.showProducts'}
= render 'exchange_distributed_products_form'
= f.submit @order_cycle.new_record? ? 'Create' : 'Update'
or
= link_to 'Cancel', main_app.admin_order_cycles_path
= select_tag :new_distributor_id, options_from_collection_for_select(Enterprise.is_distributor, :id, :name), {'ng-model' => 'new_distributor_id'}
= f.submit 'Add distributor', 'ng-click' => 'addDistributor($event)'
.actions
= f.submit @order_cycle.new_record? ? 'Create' : 'Update'
or
= link_to 'Cancel', main_app.admin_order_cycles_path
<h2>Debug information</h2>
%h2 Debug information
%pre order_cycle = {{ order_cycle | json }}
%pre enterprises = {{ enterprises | json }}
%pre supplied_products = {{ supplied_products | json }}

View File

@@ -26,11 +26,11 @@
%td= order_cycle_form.text_field :orders_open_at, :class => 'datetimepicker', :value => order_cycle.orders_open_at
%td= order_cycle_form.text_field :orders_close_at, :class => 'datetimepicker', :value => order_cycle.orders_close_at
%td= order_cycle.coordinator.name
%td
%td.suppliers
- order_cycle.suppliers.each do |s|
= s.name
%br/
%td
%td.distributors
- order_cycle.distributors.each do |d|
= d.name
%br/

View File

@@ -1,11 +1,17 @@
r.element :order_cycle, @order_cycle do
r.element :id
r.element :name
r.element :orders_open_at
r.element :orders_close_at
r.element :orders_open_at, @order_cycle.orders_open_at.to_s
r.element :orders_close_at, @order_cycle.orders_close_at.to_s
r.element :coordinator_id
r.list_of :exchanges do
r.list_of :exchanges do |exchange|
r.element :id
r.element :sender_id
r.element :receiver_id
r.element :variants, Hash[ exchange.variants.map { |v| [v.id, true] } ], {}
r.element :pickup_time
r.element :pickup_instructions
end
end

View File

@@ -1,8 +0,0 @@
<script type="text/javascript">
var uvOptions = {};
(function() {
var uv = document.createElement('script'); uv.type = 'text/javascript'; uv.async = true;
uv.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + 'widget.uservoice.com/mCbT1cbjQZkPELXOix9Ag.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(uv, s);
})();
</script>

View File

@@ -0,0 +1,10 @@
<% if Rails.env.production? %>
<script type="text/javascript">
var uvOptions = {};
(function() {
var uv = document.createElement('script'); uv.type = 'text/javascript'; uv.async = true;
uv.src = ('https:' == document.location.protocol ? 'https://' : 'http://') + 'widget.uservoice.com/mCbT1cbjQZkPELXOix9Ag.js';
var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(uv, s);
})();
</script>
<% end %>

View File

@@ -0,0 +1,39 @@
<% content_for :head do %>
<% if Rails.env.production? %>
<%= stylesheet_link_tag 'https://api.jirafe.com/dashboard/css/spree_ui.css', :media => 'all' %>
<%= javascript_include_tag 'https://jirafe.com/dashboard/js/spree_namespaced_ui.js' %>
<% end %>
<% end %>
<% if Rails.env.production? %>
<% if Spree::Dash::Config.configured? %>
<h1><%= t(:overview) %></h1>
<div id="jirafe"></div>
<%= javascript_tag :defer => 'defer' do %>
jirafe.jQuery('#jirafe').jirafe({
api_url: 'https://api.jirafe.com/v1',
api_token: '<%= Spree::Dash::Config.token %>',
app_id: '<%= Spree::Dash::Config.app_id %>',
version: 'spree-v0.1.0',
locale: '<%= Spree::Dash::Config.locale %>' });
setTimeout(function() {
if ($('mod-jirafe') == undefined) {
$('messages').insert ("<ul class=\"messages\"><li class=\"error-msg\">We're unable to connect with the Jirafe service for the moment. Please wait a few minutes and refresh this page later.</li></ul>");
}
}, 10000);
<% end %>
<div id="jirafe_locales">
<%= raw jirafe_locale_links.join(' | ') %>
</div>
<% else %>
<div class="analytics_splash">
<%= image_tag 'analytics_dashboard_preview.png', :alt => 'Spree Analytics' %>
</div>
<br/>
<div class="preview-buttons">
<%= link_to content_tag(:span, t(:activate)), admin_analytics_sign_up_path, :class => 'button green' %>
<%= t(:or) %> &nbsp;
<%= link_to content_tag(:span, t(:learn_more)), "http://spreecommerce.com/blog/2012/01/31/introducing-spree-analytics/", :class => 'button', :target => '_blank' %>
</div>
<% end %>
<% end %>

View File

@@ -0,0 +1,22 @@
basePath = '../';
files = [
ANGULAR_SCENARIO,
ANGULAR_SCENARIO_ADAPTER,
'test/e2e/**/*.js'
];
autoWatch = false;
browsers = ['Chrome'];
singleRun = true;
proxies = {
'/': 'http://localhost:8000/'
};
junitReporter = {
outputFile: 'test_out/e2e.xml',
suite: 'e2e'
};

View File

@@ -0,0 +1,24 @@
basePath = '../';
files = [
JASMINE,
JASMINE_ADAPTER,
'app/assets/javascripts/shared/jquery-1.8.0.js', // TODO: Can we link to Rails' jquery?
'app/assets/javascripts/shared/angular.js',
'app/assets/javascripts/shared/angular-*.js',
'app/assets/javascripts/admin/order_cycle.js.erb.coffee',
'spec/javascripts/unit/**/*.js*'
];
exclude = ['**/.#*']
autoWatch = true;
browsers = ['Chrome'];
junitReporter = {
outputFile: 'log/testacular-unit.xml',
suite: 'unit'
};

View File

@@ -0,0 +1,9 @@
class ChangeExchangePickupTimeToString < ActiveRecord::Migration
def up
change_column :exchanges, :pickup_time, :string
end
def down
change_column :exchanges, :pickup_time, :datetime
end
end

View File

@@ -11,7 +11,7 @@
#
# It's strongly recommended to check this file into your version control system.
ActiveRecord::Schema.define(:version => 20121125232613) do
ActiveRecord::Schema.define(:version => 20130118031610) do
create_table "cms_blocks", :force => true do |t|
t.integer "page_id", :null => false
@@ -177,7 +177,7 @@ ActiveRecord::Schema.define(:version => 20121125232613) do
t.integer "sender_id"
t.integer "receiver_id"
t.integer "payment_enterprise_id"
t.datetime "pickup_time"
t.string "pickup_time"
t.string "pickup_instructions"
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false

View File

@@ -8,29 +8,47 @@ module OpenFoodWeb
@touched_exchanges = []
@order_cycle.incoming_exchanges.each do |exchange|
variant_ids = exchange_variant_ids(exchange)
if exchange_exists?(exchange[:enterprise_id], @order_cycle.coordinator_id)
update_exchange(exchange[:enterprise_id], @order_cycle.coordinator_id)
update_exchange(exchange[:enterprise_id], @order_cycle.coordinator_id, {variant_ids: variant_ids})
else
add_exchange(exchange[:enterprise_id], @order_cycle.coordinator_id)
add_exchange(exchange[:enterprise_id], @order_cycle.coordinator_id, {variant_ids: variant_ids})
end
end
@order_cycle.outgoing_exchanges.each do |exchange|
variant_ids = exchange_variant_ids(exchange)
if exchange_exists?(@order_cycle.coordinator_id, exchange[:enterprise_id])
update_exchange(@order_cycle.coordinator_id, exchange[:enterprise_id], {variant_ids: variant_ids, pickup_time: exchange[:pickup_time], pickup_instructions: exchange[:pickup_instructions]})
else
add_exchange(@order_cycle.coordinator_id, exchange[:enterprise_id], {variant_ids: variant_ids, pickup_time: exchange[:pickup_time], pickup_instructions: exchange[:pickup_instructions]})
end
end
destroy_untouched_exchanges
end
private
attr_accessor :touched_exchanges
def exchange_exists?(sender_id, receiver_id)
@order_cycle.exchanges.where(:sender_id => sender_id, :receiver_id => receiver_id).present?
end
def add_exchange(sender_id, receiver_id)
exchange = @order_cycle.exchanges.create! :sender_id => sender_id, :receiver_id => receiver_id
def add_exchange(sender_id, receiver_id, attrs={})
attrs = attrs.reverse_merge(:sender_id => sender_id, :receiver_id => receiver_id)
exchange = @order_cycle.exchanges.create! attrs
@touched_exchanges << exchange
end
def update_exchange(sender_id, receiver_id)
# NOOP - when we're setting data on the exchange, we can do so here
def update_exchange(sender_id, receiver_id, attrs={})
exchange = @order_cycle.exchanges.where(:sender_id => sender_id, :receiver_id => receiver_id).first
exchange.update_attributes!(attrs)
@touched_exchanges << exchange
end
@@ -42,5 +60,9 @@ module OpenFoodWeb
@order_cycle.exchanges - @touched_exchanges
end
def exchange_variant_ids(exchange)
exchange[:variants].select { |k, v| v }.keys.map { |k| k.to_i }
end
end
end

View File

@@ -0,0 +1,11 @@
@echo off
REM Windows script for running e2e tests
REM You have to run server and capture some browser first
REM
REM Requirements:
REM - NodeJS (http://nodejs.org/)
REM - Testacular (npm install -g testacular)
set BASE_DIR=%~dp0
testacular start "%BASE_DIR%\..\config\testacular-e2e.conf.js" %*

9
script-angular/e2e-test.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
BASE_DIR=`dirname $0`
echo ""
echo "Starting Testacular Server (http://vojtajina.github.com/testacular)"
echo "-------------------------------------------------------------------"
testacular start $BASE_DIR/../config/testacular-e2e.conf.js $*

View File

@@ -0,0 +1,19 @@
@echo off
REM Windows script for starting JSTD server
REM
REM Requirements:
REM - Java (http://www.java.com)
set BASE_DIR=%~dp0
set PORT=9876
echo Starting JsTestDriver Server (http://code.google.com/p/js-test-driver/)
echo Please open the following url and capture one or more browsers:
echo http://localhost:%PORT%/
java -jar "%BASE_DIR%\..\test\lib\jstestdriver\JsTestDriver.jar" ^
--port %PORT% ^
--browserTimeout 20000 ^
--config "%BASE_DIR%\..\config\jsTestDriver.conf" ^
--basePath "%BASE_DIR%\.."

14
script-angular/test-server.sh Executable file
View File

@@ -0,0 +1,14 @@
#!/bin/bash
BASE_DIR=`dirname $0`
PORT=9876
echo "Starting JsTestDriver Server (http://code.google.com/p/js-test-driver/)"
echo "Please open the following url and capture one or more browsers:"
echo "http://localhost:$PORT"
java -jar "$BASE_DIR/../test/lib/jstestdriver/JsTestDriver.jar" \
--port $PORT \
--browserTimeout 20000 \
--config "$BASE_DIR/../config/jsTestDriver.conf" \
--basePath "$BASE_DIR/.."

11
script-angular/test.bat Normal file
View File

@@ -0,0 +1,11 @@
@echo off
REM Windows script for running unit tests
REM You have to run server and capture some browser first
REM
REM Requirements:
REM - NodeJS (http://nodejs.org/)
REM - Testacular (npm install -g testacular)
set BASE_DIR=%~dp0
testacular start "%BASE_DIR%\..\config\testacular.conf.js" %*

9
script-angular/test.sh Executable file
View File

@@ -0,0 +1,9 @@
#!/bin/bash
BASE_DIR=`dirname $0`
echo ""
echo "Starting Testacular Server (http://vojtajina.github.com/testacular)"
echo "-------------------------------------------------------------------"
testacular start $BASE_DIR/../config-angular/testacular.conf.js $*

13
script-angular/update-repo.sh Executable file
View File

@@ -0,0 +1,13 @@
#!/bin/bash
###
# This scripts updates the local repo with the latest changes from github.
#
# The master branch will be REPLACED with what's in github and all local changes
# will be LOST.
###
git checkout master
git fetch -f origin
git fetch --tags origin
git reset --hard origin/master

19
script-angular/watchr.rb Executable file
View File

@@ -0,0 +1,19 @@
#!/usr/bin/env watchr
# config file for watchr http://github.com/mynyml/watchr
# install: gem install watchr
# run: watch watchr.rb
# note: make sure that you have jstd server running (server.sh) and a browser captured
log_file = File.expand_path(File.dirname(__FILE__) + '/../logs/jstd.log')
`cd ..`
`touch #{log_file}`
puts "String watchr... log file: #{log_file}"
watch( '(app/js|test/unit)' ) do
`echo "\n\ntest run started @ \`date\`" > #{log_file}`
`scripts/test.sh &> #{log_file}`
end

244
script-angular/web-server.js Executable file
View File

@@ -0,0 +1,244 @@
#!/usr/bin/env node
var util = require('util'),
http = require('http'),
fs = require('fs'),
url = require('url'),
events = require('events');
var DEFAULT_PORT = 8000;
function main(argv) {
new HttpServer({
'GET': createServlet(StaticServlet),
'HEAD': createServlet(StaticServlet)
}).start(Number(argv[2]) || DEFAULT_PORT);
}
function escapeHtml(value) {
return value.toString().
replace('<', '&lt;').
replace('>', '&gt;').
replace('"', '&quot;');
}
function createServlet(Class) {
var servlet = new Class();
return servlet.handleRequest.bind(servlet);
}
/**
* An Http server implementation that uses a map of methods to decide
* action routing.
*
* @param {Object} Map of method => Handler function
*/
function HttpServer(handlers) {
this.handlers = handlers;
this.server = http.createServer(this.handleRequest_.bind(this));
}
HttpServer.prototype.start = function(port) {
this.port = port;
this.server.listen(port);
util.puts('Http Server running at http://localhost:' + port + '/');
};
HttpServer.prototype.parseUrl_ = function(urlString) {
var parsed = url.parse(urlString);
parsed.pathname = url.resolve('/', parsed.pathname);
return url.parse(url.format(parsed), true);
};
HttpServer.prototype.handleRequest_ = function(req, res) {
var logEntry = req.method + ' ' + req.url;
if (req.headers['user-agent']) {
logEntry += ' ' + req.headers['user-agent'];
}
util.puts(logEntry);
req.url = this.parseUrl_(req.url);
var handler = this.handlers[req.method];
if (!handler) {
res.writeHead(501);
res.end();
} else {
handler.call(this, req, res);
}
};
/**
* Handles static content.
*/
function StaticServlet() {}
StaticServlet.MimeMap = {
'txt': 'text/plain',
'html': 'text/html',
'css': 'text/css',
'xml': 'application/xml',
'json': 'application/json',
'js': 'application/javascript',
'jpg': 'image/jpeg',
'jpeg': 'image/jpeg',
'gif': 'image/gif',
'png': 'image/png',
  'svg': 'image/svg+xml'
};
StaticServlet.prototype.handleRequest = function(req, res) {
var self = this;
var path = ('./' + req.url.pathname).replace('//','/').replace(/%(..)/g, function(match, hex){
return String.fromCharCode(parseInt(hex, 16));
});
var parts = path.split('/');
if (parts[parts.length-1].charAt(0) === '.')
return self.sendForbidden_(req, res, path);
fs.stat(path, function(err, stat) {
if (err)
return self.sendMissing_(req, res, path);
if (stat.isDirectory())
return self.sendDirectory_(req, res, path);
return self.sendFile_(req, res, path);
});
}
StaticServlet.prototype.sendError_ = function(req, res, error) {
res.writeHead(500, {
'Content-Type': 'text/html'
});
res.write('<!doctype html>\n');
res.write('<title>Internal Server Error</title>\n');
res.write('<h1>Internal Server Error</h1>');
res.write('<pre>' + escapeHtml(util.inspect(error)) + '</pre>');
util.puts('500 Internal Server Error');
util.puts(util.inspect(error));
};
StaticServlet.prototype.sendMissing_ = function(req, res, path) {
path = path.substring(1);
res.writeHead(404, {
'Content-Type': 'text/html'
});
res.write('<!doctype html>\n');
res.write('<title>404 Not Found</title>\n');
res.write('<h1>Not Found</h1>');
res.write(
'<p>The requested URL ' +
escapeHtml(path) +
' was not found on this server.</p>'
);
res.end();
util.puts('404 Not Found: ' + path);
};
StaticServlet.prototype.sendForbidden_ = function(req, res, path) {
path = path.substring(1);
res.writeHead(403, {
'Content-Type': 'text/html'
});
res.write('<!doctype html>\n');
res.write('<title>403 Forbidden</title>\n');
res.write('<h1>Forbidden</h1>');
res.write(
'<p>You do not have permission to access ' +
escapeHtml(path) + ' on this server.</p>'
);
res.end();
util.puts('403 Forbidden: ' + path);
};
StaticServlet.prototype.sendRedirect_ = function(req, res, redirectUrl) {
res.writeHead(301, {
'Content-Type': 'text/html',
'Location': redirectUrl
});
res.write('<!doctype html>\n');
res.write('<title>301 Moved Permanently</title>\n');
res.write('<h1>Moved Permanently</h1>');
res.write(
'<p>The document has moved <a href="' +
redirectUrl +
'">here</a>.</p>'
);
res.end();
util.puts('301 Moved Permanently: ' + redirectUrl);
};
StaticServlet.prototype.sendFile_ = function(req, res, path) {
var self = this;
var file = fs.createReadStream(path);
res.writeHead(200, {
'Content-Type': StaticServlet.
MimeMap[path.split('.').pop()] || 'text/plain'
});
if (req.method === 'HEAD') {
res.end();
} else {
file.on('data', res.write.bind(res));
file.on('close', function() {
res.end();
});
file.on('error', function(error) {
self.sendError_(req, res, error);
});
}
};
StaticServlet.prototype.sendDirectory_ = function(req, res, path) {
var self = this;
if (path.match(/[^\/]$/)) {
req.url.pathname += '/';
var redirectUrl = url.format(url.parse(url.format(req.url)));
return self.sendRedirect_(req, res, redirectUrl);
}
fs.readdir(path, function(err, files) {
if (err)
return self.sendError_(req, res, error);
if (!files.length)
return self.writeDirectoryIndex_(req, res, path, []);
var remaining = files.length;
files.forEach(function(fileName, index) {
fs.stat(path + '/' + fileName, function(err, stat) {
if (err)
return self.sendError_(req, res, err);
if (stat.isDirectory()) {
files[index] = fileName + '/';
}
if (!(--remaining))
return self.writeDirectoryIndex_(req, res, path, files);
});
});
});
};
StaticServlet.prototype.writeDirectoryIndex_ = function(req, res, path, files) {
path = path.substring(1);
res.writeHead(200, {
'Content-Type': 'text/html'
});
if (req.method === 'HEAD') {
res.end();
return;
}
res.write('<!doctype html>\n');
res.write('<title>' + escapeHtml(path) + '</title>\n');
res.write('<style>\n');
res.write(' ol { list-style-type: none; font-size: 1.2em; }\n');
res.write('</style>\n');
res.write('<h1>Directory: ' + escapeHtml(path) + '</h1>');
res.write('<ol>');
files.forEach(function(fileName) {
if (fileName.charAt(0) !== '.') {
res.write('<li><a href="' +
escapeHtml(fileName) + '">' +
escapeHtml(fileName) + '</a></li>');
}
});
res.write('</ol>');
res.end();
};
// Must be last,
main(process.argv);

View File

@@ -5,22 +5,20 @@ FactoryGirl.define do
factory :order_cycle, :parent => :simple_order_cycle do
after(:create) do |oc|
# Suppliers
create(:exchange, :order_cycle => oc, :receiver => oc.coordinator)
create(:exchange, :order_cycle => oc, :receiver => oc.coordinator)
ex1 = create(:exchange, :order_cycle => oc, :receiver => oc.coordinator)
ex2 = create(:exchange, :order_cycle => oc, :receiver => oc.coordinator)
# Distributors
create(:exchange, :order_cycle => oc, :sender => oc.coordinator)
create(:exchange, :order_cycle => oc, :sender => oc.coordinator)
create(:exchange, :order_cycle => oc, :sender => oc.coordinator, :pickup_time => 'time 0', :pickup_instructions => 'instructions 0')
create(:exchange, :order_cycle => oc, :sender => oc.coordinator, :pickup_time => 'time 1', :pickup_instructions => 'instructions 1')
# Products with images
ex = oc.exchanges.first
2.times do
product = create(:product)
[ex1, ex2].each do |exchange|
product = create(:product, supplier: exchange.sender)
image = File.open(File.expand_path('../../app/assets/images/logo.jpg', __FILE__))
Spree::Image.create({:viewable_id => product.master.id, :viewable_type => 'Spree::Variant', :alt => "position 1", :attachment => image, :position => 1})
ex.variants << product.master
exchange.variants << product.master
end
end
end
@@ -31,7 +29,7 @@ FactoryGirl.define do
orders_open_at { Time.zone.now - 1.day }
orders_close_at { Time.zone.now + 1.week }
coordinator { Enterprise.first || FactoryGirl.create(:enterprise) }
coordinator { Enterprise.is_distributor.first || FactoryGirl.create(:distributor_enterprise) }
end
factory :exchange, :class => Exchange do

View File

@@ -0,0 +1,241 @@
require 'spec_helper'
feature %q{
As an administrator
I want to manage order cycles
}, js: true do
include AuthenticationWorkflow
include WebHelper
scenario "listing order cycles" do
# Given an order cycle
oc = create(:order_cycle)
# When I go to the admin order cycles page
login_to_admin_section
click_link 'Order Cycles'
# Then I should see the basic fields
page.should have_selector 'a', text: oc.name
page.should have_selector "input[value='#{oc.orders_open_at}']"
page.should have_selector "input[value='#{oc.orders_close_at}']"
page.should have_content oc.coordinator.name
# And I should see the suppliers and distributors
oc.suppliers.each { |s| page.should have_content s.name }
oc.distributors.each { |d| page.should have_content d.name }
# And I should see a thumbnail image for each product
all('td.products img').count.should == 2
end
scenario "creating an order cycle" do
# Given coordinating, supplying and distributing enterprises with some products with variants
create(:distributor_enterprise, name: 'My coordinator')
supplier = create(:supplier_enterprise, name: 'My supplier')
product = create(:product, supplier: supplier)
create(:variant, product: product)
create(:variant, product: product)
distributor = create(:distributor_enterprise, name: 'My distributor')
# When I go to the new order cycle page
login_to_admin_section
click_link 'Order Cycles'
click_link 'New Order Cycle'
# And I fill in the basic fields
fill_in 'order_cycle_name', with: 'Plums & Avos'
fill_in 'order_cycle_orders_open_at', with: '2012-11-06 06:00:00'
fill_in 'order_cycle_orders_close_at', with: '2012-11-13 17:00:00'
select 'My coordinator', from: 'order_cycle_coordinator_id'
# And I add a supplier and some products
select 'My supplier', from: 'new_supplier_id'
click_button 'Add supplier'
page.find('table.exchanges tr.supplier td.products input').click
check 'order_cycle_incoming_exchange_0_variants_2'
check 'order_cycle_incoming_exchange_0_variants_3'
# And I add a distributor with the same products
select 'My distributor', from: 'new_distributor_id'
click_button 'Add distributor'
fill_in 'order_cycle_outgoing_exchange_0_pickup_time', with: 'pickup time'
fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'pickup instructions'
page.find('table.exchanges tr.distributor td.products input').click
check 'order_cycle_outgoing_exchange_0_variants_2'
check 'order_cycle_outgoing_exchange_0_variants_3'
# And I click Create
click_button 'Create'
# Then my order cycle should have been created
page.should have_content 'Your order cycle has been created.'
page.should have_selector 'a', text: 'Plums & Avos'
page.should have_selector "input[value='2012-11-06 06:00:00 UTC']"
page.should have_selector "input[value='2012-11-13 17:00:00 UTC']"
page.should have_content 'My coordinator'
page.should have_selector 'td.suppliers', text: 'My supplier'
page.should have_selector 'td.distributors', text: 'My distributor'
# And it should have some variants selected
OrderCycle.last.exchanges.first.variants.count.should == 2
OrderCycle.last.exchanges.last.variants.count.should == 2
# And my pickup time and instructions should have been saved
oc = OrderCycle.last
exchange = oc.exchanges.where(:sender_id => oc.coordinator_id).first
exchange.pickup_time.should == 'pickup time'
exchange.pickup_instructions.should == 'pickup instructions'
end
scenario "editing an order cycle" do
# Given an order cycle with all the settings
oc = create(:order_cycle)
# When I edit it
login_to_admin_section
click_link 'Order Cycles'
click_link oc.name
# Then I should see the basic settings
sleep(1)
page.find('#order_cycle_name').value.should == oc.name
page.find('#order_cycle_orders_open_at').value.should == oc.orders_open_at.to_s
page.find('#order_cycle_orders_close_at').value.should == oc.orders_close_at.to_s
page.find('#order_cycle_coordinator_id').value.to_i.should == oc.coordinator_id
# And I should see the suppliers with products
page.should have_selector 'td.supplier_name', :text => oc.suppliers.first.name
page.should have_selector 'td.supplier_name', :text => oc.suppliers.last.name
page.all('table.exchanges tbody tr.supplier').each do |row|
row.find('td.products input').click
products_row = page.all('table.exchanges tr.products').select { |r| r.visible? }.first
products_row.should have_selector "input[type='checkbox'][checked='checked']"
row.find('td.products input').click
end
# And I should see the distributors with products
page.should have_selector 'td.distributor_name', :text => oc.distributors.first.name
page.should have_selector 'td.distributor_name', :text => oc.distributors.last.name
page.find('#order_cycle_outgoing_exchange_0_pickup_time').value.should == 'time 0'
page.find('#order_cycle_outgoing_exchange_0_pickup_instructions').value.should == 'instructions 0'
page.find('#order_cycle_outgoing_exchange_1_pickup_time').value.should == 'time 1'
page.find('#order_cycle_outgoing_exchange_1_pickup_instructions').value.should == 'instructions 1'
page.all('table.exchanges tbody tr.distributor').each do |row|
row.find('td.products input').click
products_row = page.all('table.exchanges tr.products').select { |r| r.visible? }.first
products_row.should have_selector "input[type='checkbox'][checked='checked']"
row.find('td.products input').click
end
end
scenario "updating an order cycle" do
# Given an order cycle with all the settings
oc = create(:order_cycle)
# And a coordinating, supplying and distributing enterprise with some products with variants
create(:distributor_enterprise, name: 'My coordinator')
supplier = create(:supplier_enterprise, name: 'My supplier')
distributor = create(:distributor_enterprise, name: 'My distributor')
product = create(:product, supplier: supplier)
v1 = create(:variant, product: product)
v2 = create(:variant, product: product)
# When I go to its edit page
login_to_admin_section
click_link 'Order Cycles'
click_link oc.name
sleep 1
# And I update it
fill_in 'order_cycle_name', with: 'Plums & Avos'
fill_in 'order_cycle_orders_open_at', with: '2012-11-06 06:00:00'
fill_in 'order_cycle_orders_close_at', with: '2012-11-13 17:00:00'
select 'My coordinator', from: 'order_cycle_coordinator_id'
# And I add a supplier and some products
select 'My supplier', from: 'new_supplier_id'
click_button 'Add supplier'
page.all("table.exchanges tr.supplier td.products input").each { |e| e.click }
uncheck "order_cycle_incoming_exchange_1_variants_2"
check "order_cycle_incoming_exchange_2_variants_#{v1.id}"
check "order_cycle_incoming_exchange_2_variants_#{v2.id}"
# And I add a distributor and some products
select 'My distributor', from: 'new_distributor_id'
click_button 'Add distributor'
fill_in 'order_cycle_outgoing_exchange_0_pickup_time', with: 'New time 0'
fill_in 'order_cycle_outgoing_exchange_0_pickup_instructions', with: 'New instructions 0'
fill_in 'order_cycle_outgoing_exchange_1_pickup_time', with: 'New time 1'
fill_in 'order_cycle_outgoing_exchange_1_pickup_instructions', with: 'New instructions 1'
page.all("table.exchanges tr.distributor td.products input").each { |e| e.click }
uncheck "order_cycle_outgoing_exchange_2_variants_#{v1.id}"
check "order_cycle_outgoing_exchange_2_variants_#{v2.id}"
# And I click Update
click_button 'Update'
# Then my order cycle should have been updated
page.should have_content 'Your order cycle has been updated.'
page.should have_selector 'a', text: 'Plums & Avos'
page.should have_selector "input[value='2012-11-06 06:00:00 UTC']"
page.should have_selector "input[value='2012-11-13 17:00:00 UTC']"
page.should have_content 'My coordinator'
page.should have_selector 'td.suppliers', text: 'My supplier'
page.should have_selector 'td.distributors', text: 'My distributor'
# And it should have some variants selected
OrderCycle.last.variants.map { |v| v.id }.sort.should == [1, v1.id, v2.id].sort
# And the collection details should have been updated
OrderCycle.last.exchanges.where(pickup_time: 'New time 0', pickup_instructions: 'New instructions 0').should be_present
OrderCycle.last.exchanges.where(pickup_time: 'New time 1', pickup_instructions: 'New instructions 1').should be_present
end
scenario "updating many order cycle opening/closing times at once" do
# Given three order cycles
3.times { create(:order_cycle) }
# When I go to the order cycles page
login_to_admin_section
click_link 'Order Cycles'
# And I fill in some new opening/closing times and save them
fill_in 'order_cycle_set_collection_attributes_0_orders_open_at', :with => '2012-12-01 12:00:00'
fill_in 'order_cycle_set_collection_attributes_0_orders_close_at', :with => '2012-12-01 12:00:01'
fill_in 'order_cycle_set_collection_attributes_1_orders_open_at', :with => '2012-12-01 12:00:02'
fill_in 'order_cycle_set_collection_attributes_1_orders_close_at', :with => '2012-12-01 12:00:03'
fill_in 'order_cycle_set_collection_attributes_2_orders_open_at', :with => '2012-12-01 12:00:04'
fill_in 'order_cycle_set_collection_attributes_2_orders_close_at', :with => '2012-12-01 12:00:05'
click_button 'Update'
# Then my times should have been saved
flash_message.should == 'Order cycles have been updated.'
OrderCycle.all.map { |oc| oc.orders_open_at.sec }.should == [0, 2, 4]
OrderCycle.all.map { |oc| oc.orders_close_at.sec }.should == [1, 3, 5]
end
end

View File

@@ -0,0 +1,394 @@
describe 'OrderCycle controllers', ->
describe 'AdminCreateOrderCycleCtrl', ->
ctrl = null
scope = null
event = null
OrderCycle = null
Enterprise = null
beforeEach ->
scope = {}
event =
preventDefault: jasmine.createSpy('preventDefault')
OrderCycle =
order_cycle: 'my order cycle'
exchangeSelectedVariants: jasmine.createSpy('exchangeSelectedVariants').andReturn('variants selected')
productSuppliedToOrderCycle: jasmine.createSpy('productSuppliedToOrderCycle').andReturn('product supplied')
variantSuppliedToOrderCycle: jasmine.createSpy('variantSuppliedToOrderCycle').andReturn('variant supplied')
toggleProducts: jasmine.createSpy('toggleProducts')
addSupplier: jasmine.createSpy('addSupplier')
addDistributor: jasmine.createSpy('addDistributor')
create: jasmine.createSpy('create')
Enterprise =
index: jasmine.createSpy('index').andReturn('enterprises list')
supplied_products: 'supplied products'
totalVariants: jasmine.createSpy('totalVariants').andReturn('variants total')
module('order_cycle')
inject ($controller) ->
ctrl = $controller 'AdminCreateOrderCycleCtrl', {$scope: scope, OrderCycle: OrderCycle, Enterprise: Enterprise}
it 'Loads enterprises and supplied products', ->
expect(Enterprise.index).toHaveBeenCalled()
expect(scope.enterprises).toEqual('enterprises list')
expect(scope.supplied_products).toEqual('supplied products')
it 'Loads order cycles', ->
expect(scope.order_cycle).toEqual('my order cycle')
it 'Delegates exchangeSelectedVariants to OrderCycle', ->
expect(scope.exchangeSelectedVariants('exchange')).toEqual('variants selected')
expect(OrderCycle.exchangeSelectedVariants).toHaveBeenCalledWith('exchange')
it 'Delegates enterpriseTotalVariants to Enterprise', ->
expect(scope.enterpriseTotalVariants('enterprise')).toEqual('variants total')
expect(Enterprise.totalVariants).toHaveBeenCalledWith('enterprise')
it 'Delegates productSuppliedToOrderCycle to OrderCycle', ->
expect(scope.productSuppliedToOrderCycle('product')).toEqual('product supplied')
expect(OrderCycle.productSuppliedToOrderCycle).toHaveBeenCalledWith('product')
it 'Delegates variantSuppliedToOrderCycle to OrderCycle', ->
expect(scope.variantSuppliedToOrderCycle('variant')).toEqual('variant supplied')
expect(OrderCycle.variantSuppliedToOrderCycle).toHaveBeenCalledWith('variant')
it 'Delegates toggleProducts to OrderCycle', ->
scope.toggleProducts(event, 'exchange')
expect(event.preventDefault).toHaveBeenCalled()
expect(OrderCycle.toggleProducts).toHaveBeenCalledWith('exchange')
it 'Adds order cycle suppliers', ->
scope.new_supplier_id = 'new supplier id'
scope.addSupplier(event)
expect(event.preventDefault).toHaveBeenCalled()
expect(OrderCycle.addSupplier).toHaveBeenCalledWith('new supplier id')
it 'Adds order cycle distributors', ->
scope.new_distributor_id = 'new distributor id'
scope.addDistributor(event)
expect(event.preventDefault).toHaveBeenCalled()
expect(OrderCycle.addDistributor).toHaveBeenCalledWith('new distributor id')
it 'Submits the order cycle via OrderCycle create', ->
scope.submit()
expect(OrderCycle.create).toHaveBeenCalled()
describe 'AdminEditOrderCycleCtrl', ->
ctrl = null
scope = null
event = null
location = null
OrderCycle = null
Enterprise = null
beforeEach ->
scope = {}
event =
preventDefault: jasmine.createSpy('preventDefault')
location =
absUrl: ->
'example.com/admin/order_cycles/27/edit'
OrderCycle =
load: jasmine.createSpy('load')
exchangeSelectedVariants: jasmine.createSpy('exchangeSelectedVariants').andReturn('variants selected')
productSuppliedToOrderCycle: jasmine.createSpy('productSuppliedToOrderCycle').andReturn('product supplied')
variantSuppliedToOrderCycle: jasmine.createSpy('variantSuppliedToOrderCycle').andReturn('variant supplied')
toggleProducts: jasmine.createSpy('toggleProducts')
addSupplier: jasmine.createSpy('addSupplier')
addDistributor: jasmine.createSpy('addDistributor')
update: jasmine.createSpy('update')
Enterprise =
index: jasmine.createSpy('index').andReturn('enterprises list')
supplied_products: 'supplied products'
totalVariants: jasmine.createSpy('totalVariants').andReturn('variants total')
module('order_cycle')
inject ($controller) ->
ctrl = $controller 'AdminEditOrderCycleCtrl', {$scope: scope, $location: location, OrderCycle: OrderCycle, Enterprise: Enterprise}
it 'Loads enterprises and supplied products', ->
expect(Enterprise.index).toHaveBeenCalled()
expect(scope.enterprises).toEqual('enterprises list')
expect(scope.supplied_products).toEqual('supplied products')
it 'Loads order cycles', ->
expect(OrderCycle.load).toHaveBeenCalledWith('27')
it 'Delegates exchangeSelectedVariants to OrderCycle', ->
expect(scope.exchangeSelectedVariants('exchange')).toEqual('variants selected')
expect(OrderCycle.exchangeSelectedVariants).toHaveBeenCalledWith('exchange')
it 'Delegates totalVariants to Enterprise', ->
expect(scope.enterpriseTotalVariants('enterprise')).toEqual('variants total')
expect(Enterprise.totalVariants).toHaveBeenCalledWith('enterprise')
it 'Delegates productSuppliedToOrderCycle to OrderCycle', ->
expect(scope.productSuppliedToOrderCycle('product')).toEqual('product supplied')
expect(OrderCycle.productSuppliedToOrderCycle).toHaveBeenCalledWith('product')
it 'Delegates variantSuppliedToOrderCycle to OrderCycle', ->
expect(scope.variantSuppliedToOrderCycle('variant')).toEqual('variant supplied')
expect(OrderCycle.variantSuppliedToOrderCycle).toHaveBeenCalledWith('variant')
it 'Delegates toggleProducts to OrderCycle', ->
scope.toggleProducts(event, 'exchange')
expect(event.preventDefault).toHaveBeenCalled()
expect(OrderCycle.toggleProducts).toHaveBeenCalledWith('exchange')
it 'Adds order cycle suppliers', ->
scope.new_supplier_id = 'new supplier id'
scope.addSupplier(event)
expect(event.preventDefault).toHaveBeenCalled()
expect(OrderCycle.addSupplier).toHaveBeenCalledWith('new supplier id')
it 'Adds order cycle distributors', ->
scope.new_distributor_id = 'new distributor id'
scope.addDistributor(event)
expect(event.preventDefault).toHaveBeenCalled()
expect(OrderCycle.addDistributor).toHaveBeenCalledWith('new distributor id')
it 'Submits the order cycle via OrderCycle update', ->
scope.submit()
expect(OrderCycle.update).toHaveBeenCalled()
describe 'OrderCycle services', ->
describe 'Enterprise service', ->
$httpBackend = null
Enterprise = null
beforeEach ->
module 'order_cycle'
inject ($injector, _$httpBackend_)->
Enterprise = $injector.get('Enterprise')
$httpBackend = _$httpBackend_
$httpBackend.whenGET('/admin/enterprises.json').respond [
{id: 1, name: 'One', supplied_products: [1, 2]}
{id: 2, name: 'Two', supplied_products: [3, 4]}
{id: 3, name: 'Three', supplied_products: [5, 6]}
]
it 'loads enterprises as a hash', ->
enterprises = Enterprise.index()
$httpBackend.flush()
expect(enterprises).toEqual
1: new Enterprise.Enterprise({id: 1, name: 'One', supplied_products: [1, 2]})
2: new Enterprise.Enterprise({id: 2, name: 'Two', supplied_products: [3, 4]})
3: new Enterprise.Enterprise({id: 3, name: 'Three', supplied_products: [5, 6]})
it 'collates all supplied products', ->
enterprises = Enterprise.index()
$httpBackend.flush()
expect(Enterprise.supplied_products).toEqual [1, 2, 3, 4, 5, 6]
it 'counts total variants supplied by an enterprise', ->
enterprise =
supplied_products: [
{variants: []},
{variants: []},
{variants: [{}, {}, {}]}
]
expect(Enterprise.totalVariants(enterprise)).toEqual(5)
describe 'OrderCycle service', ->
OrderCycle = null
$httpBackend = null
$window = null
beforeEach ->
$window = {navigator: {userAgent: 'foo'}}
module 'order_cycle', ($provide)->
$provide.value('$window', $window)
null
inject ($injector, _$httpBackend_)->
OrderCycle = $injector.get('OrderCycle')
$httpBackend = _$httpBackend_
$httpBackend.whenGET('/admin/order_cycles/123.json').respond
id: 123
name: 'Test Order Cycle'
coordinator_id: 456
exchanges: [
{sender_id: 1, receiver_id: 456}
{sender_id: 456, receiver_id: 2}
]
it 'initialises order cycle', ->
expect(OrderCycle.order_cycle).toEqual
incoming_exchanges: []
outgoing_exchanges: []
it 'counts selected variants in an exchange', ->
result = OrderCycle.exchangeSelectedVariants({variants: {1: true, 2: false, 3: true}})
expect(result).toEqual(2)
describe 'toggling products', ->
exchange = null
beforeEach ->
exchange = {}
it 'sets a blank value to true', ->
OrderCycle.toggleProducts(exchange)
expect(exchange.showProducts).toEqual(true)
it 'sets a true value to false', ->
exchange.showProducts = true
OrderCycle.toggleProducts(exchange)
expect(exchange.showProducts).toEqual(false)
it 'sets a false value to true', ->
exchange.showProducts = false
OrderCycle.toggleProducts(exchange)
expect(exchange.showProducts).toEqual(true)
describe 'adding suppliers', ->
exchange = null
it 'adds the supplier to incoming exchanges', ->
OrderCycle.addSupplier('123')
expect(OrderCycle.order_cycle.incoming_exchanges).toEqual [
{enterprise_id: '123', active: true, variants: {}}
]
describe 'adding distributors', ->
exchange = null
it 'adds the distributor to outgoing exchanges', ->
OrderCycle.addDistributor('123')
expect(OrderCycle.order_cycle.outgoing_exchanges).toEqual [
{enterprise_id: '123', active: true, variants: {}}
]
describe 'fetching all variants supplied on incoming exchanges', ->
it 'collects variants from incoming exchanges', ->
OrderCycle.order_cycle.incoming_exchanges = [
{variants: {1: true, 2: false}}
{variants: {3: false, 4: true}}
{variants: {5: true, 6: false}}
]
expect(OrderCycle.incomingExchangesVariants()).toEqual [1, 4, 5]
describe 'checking whether a product is supplied to the order cycle', ->
product_master_present = product_variant_present = product_master_absent = product_variant_absent = null
beforeEach ->
product_master_present =
name: "Linseed (500g)"
master_id: 1
variants: []
product_variant_present =
name: "Linseed (500g)"
master_id: 2
variants: [{id: 3}, {id: 4}]
product_master_absent =
name: "Linseed (500g)"
master_id: 5
variants: []
product_variant_absent =
name: "Linseed (500g)"
master_id: 6
variants: [{id: 7}, {id: 8}]
spyOn(OrderCycle, 'incomingExchangesVariants').andReturn([1, 3])
it 'returns true for products whose master is supplied', ->
expect(OrderCycle.productSuppliedToOrderCycle(product_master_present)).toBeTruthy()
it 'returns true for products for whom a variant is supplied', ->
expect(OrderCycle.productSuppliedToOrderCycle(product_variant_present)).toBeTruthy()
it 'returns false for products whose master is not supplied', ->
expect(OrderCycle.productSuppliedToOrderCycle(product_master_absent)).toBeFalsy()
it 'returns false for products whose variants are not supplied', ->
expect(OrderCycle.productSuppliedToOrderCycle(product_variant_absent)).toBeFalsy()
describe 'checking whether a variant is supplied to the order cycle', ->
beforeEach ->
spyOn(OrderCycle, 'incomingExchangesVariants').andReturn([1, 3])
it 'returns true for variants that are supplied', ->
expect(OrderCycle.variantSuppliedToOrderCycle({id: 1})).toBeTruthy()
it 'returns false for variants that are not supplied', ->
expect(OrderCycle.variantSuppliedToOrderCycle({id: 999})).toBeFalsy()
describe 'loading an order cycle', ->
beforeEach ->
OrderCycle.load('123')
$httpBackend.flush()
it 'loads basic fields', ->
expect(OrderCycle.order_cycle.id).toEqual(123)
expect(OrderCycle.order_cycle.name).toEqual('Test Order Cycle')
expect(OrderCycle.order_cycle.coordinator_id).toEqual(456)
it 'splits exchanges into incoming and outgoing', ->
expect(OrderCycle.order_cycle.incoming_exchanges).toEqual [
sender_id: 1
enterprise_id: 1
active: true
]
expect(OrderCycle.order_cycle.outgoing_exchanges).toEqual [
receiver_id: 2
enterprise_id: 2
active: true
]
it 'removes original exchanges array', ->
expect(OrderCycle.order_cycle.exchanges).toEqual(undefined)
describe 'creating an order cycle', ->
it 'redirects to the order cycles page on success', ->
OrderCycle.order_cycle = 'this is the order cycle'
spyOn(OrderCycle, 'removeInactiveExchanges')
$httpBackend.expectPOST('/admin/order_cycles.json', {
order_cycle: 'this is the order cycle'
}).respond {success: true}
OrderCycle.create()
$httpBackend.flush()
expect(OrderCycle.removeInactiveExchanges).toHaveBeenCalled()
expect($window.location).toEqual('/admin/order_cycles')
it 'does not redirect on error', ->
OrderCycle.order_cycle = 'this is the order cycle'
spyOn(OrderCycle, 'removeInactiveExchanges')
$httpBackend.expectPOST('/admin/order_cycles.json', {
order_cycle: 'this is the order cycle'
}).respond {success: false}
OrderCycle.create()
$httpBackend.flush()
expect(OrderCycle.removeInactiveExchanges).toHaveBeenCalled()
expect($window.location).toEqual(undefined)
it 'removes inactive exchanges', ->
OrderCycle.order_cycle =
incoming_exchanges: [
{enterprise_id: "1", active: false}
{enterprise_id: "2", active: true}
{enterprise_id: "3", active: false}
]
outgoing_exchanges: [
{enterprise_id: "4", active: true}
{enterprise_id: "5", active: false}
{enterprise_id: "6", active: true}
]
OrderCycle.removeInactiveExchanges()
expect(OrderCycle.order_cycle.incoming_exchanges).toEqual [
{enterprise_id: "2", active: true}
]
expect(OrderCycle.order_cycle.outgoing_exchanges).toEqual [
{enterprise_id: "4", active: true}
{enterprise_id: "6", active: true}
]

View File

@@ -1,56 +0,0 @@
require 'open_food_web/order_cycle_form_applicator'
module OpenFoodWeb
describe OrderCycleFormApplicator do
it "creates new exchanges for incoming_exchanges" do
coordinator_id = 123
supplier_id = 456
oc = double(:order_cycle, :coordinator_id => coordinator_id, :exchanges => [], :incoming_exchanges => [{:enterprise_id => supplier_id}])
applicator = OrderCycleFormApplicator.new(oc)
applicator.should_receive(:exchange_exists?).and_return(false)
applicator.should_receive(:add_exchange).with(supplier_id, coordinator_id)
applicator.should_receive(:destroy_untouched_exchanges)
applicator.go!
end
it "updates existing exchanges for incoming_exchanges" do
coordinator_id = 123
supplier_id = 456
oc = double(:order_cycle,
:coordinator_id => coordinator_id,
:exchanges => [double(:exchange, :sender_id => supplier_id, :receiver_id => coordinator_id)],
:incoming_exchanges => [{:enterprise_id => supplier_id}])
applicator = OrderCycleFormApplicator.new(oc)
applicator.should_receive(:exchange_exists?).and_return(true)
applicator.should_receive(:update_exchange).with(supplier_id, coordinator_id)
applicator.should_receive(:destroy_untouched_exchanges)
applicator.go!
end
it "removes exchanges that are no longer present in incoming_exchanges" do
coordinator_id = 123
supplier_id = 456
exchange = double(:exchange, :sender_id => supplier_id, :receiver_id => coordinator_id)
oc = double(:order_cycle,
:coordinator_id => coordinator_id,
:exchanges => [exchange],
:incoming_exchanges => [])
applicator = OrderCycleFormApplicator.new(oc)
applicator.should_receive(:destroy_untouched_exchanges)
applicator.go!
applicator.untouched_exchanges.should == [exchange]
end
end
end

View File

@@ -0,0 +1,168 @@
require 'open_food_web/order_cycle_form_applicator'
module OpenFoodWeb
describe OrderCycleFormApplicator do
context "unit specs" do
it "creates new exchanges for incoming_exchanges" do
coordinator_id = 123
supplier_id = 456
incoming_exchange = {:enterprise_id => supplier_id, :variants => {'1' => true, '2' => false, '3' => true}}
oc = double(:order_cycle, :coordinator_id => coordinator_id, :exchanges => [], :incoming_exchanges => [incoming_exchange], :outgoing_exchanges => [])
applicator = OrderCycleFormApplicator.new(oc)
applicator.should_receive(:exchange_variant_ids).with(incoming_exchange).and_return([1, 3])
applicator.should_receive(:exchange_exists?).with(supplier_id, coordinator_id).and_return(false)
applicator.should_receive(:add_exchange).with(supplier_id, coordinator_id, {:variant_ids => [1, 3]})
applicator.should_receive(:destroy_untouched_exchanges)
applicator.go!
end
it "creates new exchanges for outgoing_exchanges" do
coordinator_id = 123
distributor_id = 456
outgoing_exchange = {:enterprise_id => distributor_id, :variants => {'1' => true, '2' => false, '3' => true}, :pickup_time => 'pickup time', :pickup_instructions => 'pickup instructions'}
oc = double(:order_cycle, :coordinator_id => coordinator_id, :exchanges => [], :incoming_exchanges => [], :outgoing_exchanges => [outgoing_exchange])
applicator = OrderCycleFormApplicator.new(oc)
applicator.should_receive(:exchange_variant_ids).with(outgoing_exchange).and_return([1, 3])
applicator.should_receive(:exchange_exists?).with(coordinator_id, distributor_id).and_return(false)
applicator.should_receive(:add_exchange).with(coordinator_id, distributor_id, {:variant_ids => [1, 3], :pickup_time => 'pickup time', :pickup_instructions => 'pickup instructions'})
applicator.should_receive(:destroy_untouched_exchanges)
applicator.go!
end
it "updates existing exchanges for incoming_exchanges" do
coordinator_id = 123
supplier_id = 456
incoming_exchange = {:enterprise_id => supplier_id, :variants => {'1' => true, '2' => false, '3' => true}}
oc = double(:order_cycle,
:coordinator_id => coordinator_id,
:exchanges => [double(:exchange, :sender_id => supplier_id, :receiver_id => coordinator_id)],
:incoming_exchanges => [incoming_exchange],
:outgoing_exchanges => [])
applicator = OrderCycleFormApplicator.new(oc)
applicator.should_receive(:exchange_variant_ids).with(incoming_exchange).and_return([1, 3])
applicator.should_receive(:exchange_exists?).with(supplier_id, coordinator_id).and_return(true)
applicator.should_receive(:update_exchange).with(supplier_id, coordinator_id, {:variant_ids => [1, 3]})
applicator.should_receive(:destroy_untouched_exchanges)
applicator.go!
end
it "updates existing exchanges for outgoing_exchanges" do
coordinator_id = 123
distributor_id = 456
outgoing_exchange = {:enterprise_id => distributor_id, :variants => {'1' => true, '2' => false, '3' => true}, :pickup_time => 'pickup time', :pickup_instructions => 'pickup instructions'}
oc = double(:order_cycle,
:coordinator_id => coordinator_id,
:exchanges => [double(:exchange, :sender_id => coordinator_id, :receiver_id => distributor_id)],
:incoming_exchanges => [],
:outgoing_exchanges => [outgoing_exchange])
applicator = OrderCycleFormApplicator.new(oc)
applicator.should_receive(:exchange_variant_ids).with(outgoing_exchange).and_return([1, 3])
applicator.should_receive(:exchange_exists?).with(coordinator_id, distributor_id).and_return(true)
applicator.should_receive(:update_exchange).with(coordinator_id, distributor_id, {:variant_ids => [1, 3], :pickup_time => 'pickup time', :pickup_instructions => 'pickup instructions'})
applicator.should_receive(:destroy_untouched_exchanges)
applicator.go!
end
it "removes exchanges that are no longer present" do
coordinator_id = 123
supplier_id = 456
exchange = double(:exchange, :sender_id => supplier_id, :receiver_id => coordinator_id)
oc = double(:order_cycle,
:coordinator_id => coordinator_id,
:exchanges => [exchange],
:incoming_exchanges => [],
:outgoing_exchanges => [])
applicator = OrderCycleFormApplicator.new(oc)
applicator.should_receive(:destroy_untouched_exchanges)
applicator.go!
applicator.send(:untouched_exchanges).should == [exchange]
end
it "converts exchange variant ids hash to an array of ids" do
applicator = OrderCycleFormApplicator.new(nil)
applicator.send(:exchange_variant_ids, {:enterprise_id => 123, :variants => {'1' => true, '2' => false, '3' => true}}).should == [1, 3]
end
end
context "integration specs" do
before(:all) do
require 'spec_helper'
end
it "checks whether exchanges exist" do
oc = FactoryGirl.create(:simple_order_cycle)
exchange = FactoryGirl.create(:exchange, order_cycle: oc)
applicator = OrderCycleFormApplicator.new(oc)
applicator.send(:exchange_exists?, exchange.sender_id, exchange.receiver_id).should be_true
applicator.send(:exchange_exists?, exchange.receiver_id, exchange.sender_id).should be_false
applicator.send(:exchange_exists?, exchange.sender_id, 999).should be_false
applicator.send(:exchange_exists?, 999, exchange.receiver_id).should be_false
applicator.send(:exchange_exists?, 999, 888).should be_false
end
it "adds exchanges" do
oc = FactoryGirl.create(:simple_order_cycle)
applicator = OrderCycleFormApplicator.new(oc)
sender = FactoryGirl.create(:enterprise)
receiver = FactoryGirl.create(:enterprise)
variant1 = FactoryGirl.create(:variant)
variant2 = FactoryGirl.create(:variant)
applicator.send(:touched_exchanges=, [])
applicator.send(:add_exchange, sender.id, receiver.id, {:variant_ids => [variant1.id, variant2.id]})
exchange = Exchange.last
exchange.sender.should == sender
exchange.receiver.should == receiver
exchange.variants.sort.should == [variant1, variant2].sort
applicator.send(:touched_exchanges).should == [exchange]
end
it "updates exchanges" do
oc = FactoryGirl.create(:simple_order_cycle)
applicator = OrderCycleFormApplicator.new(oc)
sender = FactoryGirl.create(:enterprise)
receiver = FactoryGirl.create(:enterprise)
variant1 = FactoryGirl.create(:variant)
variant2 = FactoryGirl.create(:variant)
variant3 = FactoryGirl.create(:variant)
exchange = FactoryGirl.create(:exchange, order_cycle: oc, sender: sender, receiver: receiver, variant_ids: [variant1, variant2])
applicator.send(:touched_exchanges=, [])
applicator.send(:update_exchange, sender.id, receiver.id, {:variant_ids => [variant1.id, variant3.id]})
exchange.reload
exchange.variant_ids.should == [variant1.id, variant3.id]
applicator.send(:touched_exchanges).should == [exchange]
end
end
end
end

View File

@@ -1,82 +0,0 @@
require 'spec_helper'
feature %q{
As an administrator
I want to manage order cycles
}, js: true do
include AuthenticationWorkflow
include WebHelper
scenario "listing order cycles" do
# Given an order cycle
oc = create(:order_cycle)
# When I go to the admin order cycles page
login_to_admin_section
click_link 'Order Cycles'
# Then I should see the basic fields
page.should have_selector 'a', text: oc.name
page.should have_selector "input[value='#{oc.orders_open_at}']"
page.should have_selector "input[value='#{oc.orders_close_at}']"
page.should have_content oc.coordinator.name
# And I should see the suppliers and distributors
oc.suppliers.each { |s| page.should have_content s.name }
oc.distributors.each { |d| page.should have_content d.name }
# And I should see a thumbnail image for each product
all('td.products img').count.should == 2
end
scenario "creating an order cycle" do
# Given a coordinating enterprise
create(:enterprise, name: 'My coordinator')
# When I go to the new order cycle page
login_to_admin_section
click_link 'Order Cycles'
click_link 'New Order Cycle'
# And I fill in the basic fields and click Create
fill_in 'order_cycle_name', with: 'Plums & Avos'
fill_in 'order_cycle_orders_open_at', with: '2012-11-06 06:00:00'
fill_in 'order_cycle_orders_close_at', with: '2012-11-13 17:00:00'
select 'My coordinator', from: 'order_cycle_coordinator_id'
click_button 'Create'
# Then my order cycle should have been created
page.should have_content 'Your order cycle has been created.'
page.should have_selector 'a', text: 'Plums & Avos'
page.should have_selector "input[value='2012-11-06 06:00:00 UTC']"
page.should have_selector "input[value='2012-11-13 17:00:00 UTC']"
page.should have_content 'My coordinator'
end
scenario "updating many order cycle opening/closing times at once" do
# Given three order cycles
3.times { create(:order_cycle) }
# When I go to the order cycles page
login_to_admin_section
click_link 'Order Cycles'
# And I fill in some new opening/closing times and save them
fill_in 'order_cycle_set_collection_attributes_0_orders_open_at', :with => '2012-12-01 12:00:00'
fill_in 'order_cycle_set_collection_attributes_0_orders_close_at', :with => '2012-12-01 12:00:01'
fill_in 'order_cycle_set_collection_attributes_1_orders_open_at', :with => '2012-12-01 12:00:02'
fill_in 'order_cycle_set_collection_attributes_1_orders_close_at', :with => '2012-12-01 12:00:03'
fill_in 'order_cycle_set_collection_attributes_2_orders_open_at', :with => '2012-12-01 12:00:04'
fill_in 'order_cycle_set_collection_attributes_2_orders_close_at', :with => '2012-12-01 12:00:05'
click_button 'Update'
# Then my times should have been saved
flash_message.should == 'Order cycles have been updated.'
OrderCycle.all.map { |oc| oc.orders_open_at.sec }.should == [0, 2, 4]
OrderCycle.all.map { |oc| oc.orders_close_at.sec }.should == [1, 3, 5]
end
end