Merge remote-tracking branch 'origin/master' into i18n

Conflicts:
	config/locales/en.yml
This commit is contained in:
Maikel Linke
2015-11-13 10:07:29 +11:00
44 changed files with 677 additions and 123 deletions

View File

@@ -1 +1 @@
1.9.3-p392
2.1.5

View File

@@ -3,7 +3,7 @@ sudo: false
cache: bundler
bundler_args: --without development
rvm:
- "1.9.3"
- "2.1.5"
# The test cases are roughly split according to their test times.
# It would be better to use https://github.com/ArturT/knapsack.

View File

@@ -1,10 +1,12 @@
source 'https://rubygems.org'
ruby "1.9.3"
ruby "2.1.5"
gem 'rails', '3.2.21'
gem 'rails-i18n', '~> 3.0.0'
gem 'i18n', '~> 0.6.11'
gem 'nokogiri'
gem 'pg'
gem 'spree', :github => 'openfoodfoundation/spree', :branch => '1-3-stable'
gem 'spree_i18n', :github => 'spree/spree_i18n', :branch => '1-3-stable'
@@ -114,7 +116,7 @@ group :test do
end
group :development do
gem 'pry-debugger'
gem 'pry-byebug'
gem 'debugger-linecache'
gem 'guard'
gem 'guard-livereload'

View File

@@ -171,6 +171,9 @@ GEM
httparty (>= 0.6, < 1.0)
multi_json (~> 1.0)
builder (3.0.4)
byebug (2.7.0)
columnize (~> 0.3)
debugger-linecache (~> 1.2)
cancan (1.6.8)
capybara (2.2.1)
mime-types (>= 1.16)
@@ -196,7 +199,7 @@ GEM
execjs
coffee-script-source (1.3.3)
colorize (0.7.7)
columnize (0.3.6)
columnize (0.9.0)
comfortable_mexican_sofa (1.6.24)
active_link_to (~> 1.0.0)
paperclip (>= 2.3.0)
@@ -230,12 +233,7 @@ GEM
activerecord (~> 3.0)
fog (~> 1.0)
rails (~> 3.0)
debugger (1.6.1)
columnize (>= 0.3.1)
debugger-linecache (~> 1.2.0)
debugger-ruby_core_source (~> 1.2.3)
debugger-linecache (1.2.0)
debugger-ruby_core_source (1.2.3)
delayed_job (4.0.4)
activesupport (>= 3.0, < 4.2)
delayed_job_active_record (4.0.2)
@@ -253,7 +251,7 @@ GEM
eventmachine (>= 0.12.9)
http_parser.rb (~> 0.5.3)
erubis (2.7.0)
eventmachine (1.0.3)
eventmachine (1.0.8)
excon (0.25.3)
execjs (2.5.2)
factory_girl (3.3.0)
@@ -336,7 +334,7 @@ GEM
addressable (~> 2.3)
letter_opener (1.0.0)
launchy (>= 2.0.4)
libv8 (3.16.14.3)
libv8 (3.16.14.11)
listen (2.2.0)
celluloid (>= 0.15.2)
rb-fsevent (>= 0.9.3)
@@ -391,9 +389,9 @@ GEM
coderay (~> 1.0.5)
method_source (~> 0.8)
slop (~> 3.4)
pry-debugger (0.2.2)
debugger (~> 1.3)
pry (~> 0.9.10)
pry-byebug (1.3.2)
byebug (~> 2.7)
pry (~> 0.9.12)
rabl (0.7.2)
activesupport (>= 2.3.14)
multi_json (~> 1.0)
@@ -594,13 +592,14 @@ DEPENDENCIES
letter_opener
momentjs-rails
newrelic_rpm
nokogiri
oj
paper_trail (~> 3.0.8)
paperclip
parallel_tests
pg
poltergeist
pry-debugger
pry-byebug
rabl
rack-livereload
rack-ssl

View File

@@ -20,7 +20,7 @@ Below are instructions for setting up a development environment for Open Food Ne
## Dependencies
* Rails 3.2.x
* Ruby 1.9.3
* Ruby 2.1.5
* PostgreSQL database
* PhantomJS (for testing)
* See Gemfile for a list of gems required
@@ -44,7 +44,7 @@ You can download the source with the command:
For those new to Rails, the following tutorial will help get you up to speed with configuring a Rails environment: http://guides.rubyonrails.org/getting_started.html .
First, check your dependencies: Ensure that you have Ruby >= 1.9.3 installed:
First, check your dependencies: Ensure that you have Ruby 2.1.5 installed:
ruby --version

View File

@@ -53,7 +53,6 @@ module Admin
respond_to do |format|
if @order_cycle.update_attributes(params[:order_cycle])
OpenFoodNetwork::OrderCycleFormApplicator.new(@order_cycle, spree_current_user).go!
flash[:notice] = 'Your order cycle has been updated.'
format.html { redirect_to admin_order_cycles_path }
format.json { render :json => {:success => true} }
@@ -79,6 +78,13 @@ module Admin
redirect_to main_app.admin_order_cycles_path, :notice => "Your order cycle #{@order_cycle.name} has been cloned."
end
# Send notifications to all producers who are part of the order cycle
def notify_producers
Delayed::Job.enqueue OrderCycleNotificationJob.new(params[:id].to_i)
redirect_to main_app.admin_order_cycles_path, :notice => 'Emails to be sent to producers have been queued for sending.'
end
protected
def collection(show_more=false)

View File

@@ -0,0 +1,44 @@
module Spree
module Admin
AdjustmentsController.class_eval do
before_filter :set_included_tax, only: [:create, :update]
before_filter :set_default_tax_rate, only: :edit
private
# Choose a default tax rate to show on the edit form. The adjustment stores its included
# tax in dollars, but doesn't store the source of the tax (ie. TaxRate that generated it).
# We guess which tax rate here, choosing:
# 1. A tax rate that will compute to the same amount as the existing tax
# 2. If that's not present, the first tax rate that's valid for the current order
# When we have to go with 2, we show an error message to ask the admin to check that the
# correct tax is being applied.
def set_default_tax_rate
if @adjustment.included_tax > 0
trs = TaxRate.match(@order)
tr_yielding_matching_tax = trs.select { |tr| tr.compute_tax(@adjustment.amount) == @adjustment.included_tax }.first.andand.id
tr_valid_for_order = TaxRate.match(@order).first.andand.id
@tax_rate_id = tr_yielding_matching_tax || tr_valid_for_order
if tr_yielding_matching_tax.nil?
@adjustment.errors.add :tax_rate_id, "^Please check that the tax rate for this adjustment is correct."
end
end
end
def set_included_tax
if params[:tax_rate_id].present?
tax_rate = TaxRate.find params[:tax_rate_id]
amount = params[:adjustment][:amount].to_f
params[:adjustment][:included_tax] = tax_rate.compute_tax amount
else
params[:adjustment][:included_tax] = 0
end
end
end
end
end

View File

@@ -0,0 +1,6 @@
OrderCycleNotificationJob = Struct.new(:order_cycle_id) do
def perform
order_cycle = OrderCycle.find order_cycle_id
order_cycle.suppliers.each { |supplier| ProducerMailer.order_cycle_report(supplier, order_cycle).deliver }
end
end

View File

@@ -0,0 +1,53 @@
class ProducerMailer < Spree::BaseMailer
def order_cycle_report(producer, order_cycle)
@producer = producer
@coordinator = order_cycle.coordinator
@order_cycle = order_cycle
@line_items = aggregated_line_items_from(@order_cycle, @producer)
@receival_time = @order_cycle.receival_time_for @producer
@receival_instructions = @order_cycle.receival_instructions_for @producer
subject = "[#{Spree::Config.site_name}] Order cycle report for #{producer.name}"
if has_orders? order_cycle, producer
mail(to: @producer.email,
from: from_address,
subject: subject,
reply_to: @coordinator.email,
cc: @coordinator.email)
end
end
private
def has_orders?(order_cycle, producer)
line_items_from(order_cycle, producer).any?
end
def aggregated_line_items_from(order_cycle, producer)
aggregate_line_items line_items_from(order_cycle, producer)
end
def line_items_from(order_cycle, producer)
Spree::LineItem.
joins(:order => :order_cycle, :variant => :product).
where('order_cycles.id = ?', order_cycle).
merge(Spree::Product.in_supplier(producer)).
merge(Spree::Order.complete)
end
def aggregate_line_items(line_items)
# Arrange the items in a hash to group quantities
line_items.inject({}) do |lis, li|
if lis.key? li.variant
lis[li.variant].quantity += li.quantity
else
lis[li.variant] = li
end
lis
end
end
end

View File

@@ -201,6 +201,18 @@ class OrderCycle < ActiveRecord::Base
exchanges.outgoing.to_enterprises([distributor]).first
end
def exchange_for_supplier(supplier)
exchanges.incoming.from_enterprises([supplier]).first
end
def receival_time_for(supplier)
exchange_for_supplier(supplier).andand.receival_time
end
def receival_instructions_for(supplier)
exchange_for_supplier(supplier).andand.receival_instructions
end
def pickup_time_for(distributor)
exchange_for_distributor(distributor).andand.pickup_time || distributor.next_collection_at
end

View File

@@ -132,7 +132,7 @@ class AbilityDecorator
can [:admin, :index, :read, :edit, :update], OrderCycle do |order_cycle|
OrderCycle.accessible_by(user).include? order_cycle
end
can [:bulk_update, :clone, :destroy], OrderCycle do |order_cycle|
can [:bulk_update, :clone, :destroy, :notify_producers], OrderCycle do |order_cycle|
user.enterprises.include? order_cycle.coordinator
end
can [:for_order_cycle], Enterprise

View File

@@ -24,6 +24,10 @@ module Spree
update_attributes! included_tax: tax.round(2)
end
def display_included_tax
Spree::Money.new(included_tax, { :currency => currency })
end
def has_tax?
included_tax > 0
end

View File

@@ -1,20 +1,61 @@
Spree::TaxRate.class_eval do
class << self
def match_with_sales_tax_registration(order)
return [] if order.distributor && !order.distributor.charges_sales_tax
match_without_sales_tax_registration(order)
module Spree
TaxRate.class_eval do
class << self
def match_with_sales_tax_registration(order)
return [] if order.distributor && !order.distributor.charges_sales_tax
match_without_sales_tax_registration(order)
end
alias_method_chain :match, :sales_tax_registration
end
alias_method_chain :match, :sales_tax_registration
end
def adjust_with_included_tax(order)
adjust_without_included_tax(order)
def adjust_with_included_tax(order)
adjust_without_included_tax(order)
order.reload
(order.adjustments.tax + order.price_adjustments).each do |a|
a.set_absolute_included_tax! a.amount
order.reload
(order.adjustments.tax + order.price_adjustments).each do |a|
a.set_absolute_included_tax! a.amount
end
end
alias_method_chain :adjust, :included_tax
# Manually apply a TaxRate to a particular amount. TaxRates normally compute against
# LineItems or Orders, so we mock out a line item here to fit the interface
# that our calculator (usually DefaultTax) expects.
def compute_tax(amount)
product = OpenStruct.new tax_category: tax_category
line_item = LineItem.new quantity: 1
line_item.define_singleton_method(:product) { product }
line_item.define_singleton_method(:price) { amount }
# Tax on adjustments (represented by the included_tax field) is always inclusive of
# tax. However, there's nothing to stop an admin from setting one up with a tax rate
# that's marked as not inclusive of tax, and that would result in the DefaultTax
# calculator generating a slightly incorrect value. Therefore, we treat the tax
# rate as inclusive of tax for the calculations below, regardless of its original
# setting.
with_tax_included_in_price do
calculator.compute line_item
end
end
private
def with_tax_included_in_price
old_included_in_price = self.included_in_price
self.included_in_price = true
calculator.calculable.included_in_price = true
result = yield
ensure
self.included_in_price = old_included_in_price
calculator.calculable.included_in_price = old_included_in_price
result
end
end
alias_method_chain :adjust, :included_tax
end

View File

@@ -0,0 +1,9 @@
/ replace_contents "[data-hook='adjustment_row']"
%td.align-center.created_at= pretty_time(adjustment.created_at)
%td.align-center.label= adjustment.label
%td.align-center.amount= adjustment.display_amount.to_html
%td.align-center.included-tax= adjustment.display_included_tax.to_html
%td.actions
= link_to_edit adjustment, no_text: true
= link_to_delete adjustment, no_text: true

View File

@@ -0,0 +1,8 @@
/ replace_contents "[data-hook='adjustmment_head']"
%tr
%th= "#{t('spree.date')}/#{t('spree.time')}"
%th= t(:description)
%th= t(:amount)
%th= t(:included_tax)
%th.actions

View File

@@ -0,0 +1,6 @@
/ replace_contents "[data-hook='admin_adjustment_form_fields']"
- if @adjustment.new_record?
= render 'new_form', f: f
- else
= render 'edit_form', f: f

View File

@@ -7,6 +7,11 @@
- else
{{ (incomingExchangeVariantsFor(exchange.enterprise_id)).length }}
selected
- if type == 'supplier'
%td.receival-details
= text_field_tag 'order_cycle_incoming_exchange_{{ $index }}_receival_time', '', 'id' => 'order_cycle_incoming_exchange_{{ $index }}_receival_time', 'placeholder' => 'Receive at (ie. Date / Time)', 'ng-model' => 'exchange.receival_time'
%br/
= text_field_tag 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', '', 'id' => 'order_cycle_incoming_exchange_{{ $index }}_receival_instructions', 'placeholder' => 'Receival instructions', 'ng-model' => 'exchange.receival_instructions'
- 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' => 'Ready for (ie. Date / Time)', 'ng-model' => 'exchange.pickup_time', 'ng-disabled' => '!enterprises[exchange.enterprise_id].managed && !order_cycle.viewing_as_coordinator'

View File

@@ -9,6 +9,7 @@
%tr
%th Supplier
%th Products
%th Receival details
%th Fees
%th.actions
%tbody{'ng-repeat' => 'exchange in order_cycle.incoming_exchanges'}

View File

@@ -1,7 +1,13 @@
- if can? :notify_producers, @order_cycle
= content_for :page_actions do
%li
= button_to "Notify producers", main_app.notify_producers_admin_order_cycle_path, :id => 'admin_notify_producers', :confirm => 'Are you sure?'
%h1 Edit Order Cycle
- ng_controller = order_cycles_simple_form ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl'
- ng_controller = order_cycles_simple_form ? 'AdminSimpleEditOrderCycleCtrl' : 'AdminEditOrderCycleCtrl'
= form_for [main_app, :admin, @order_cycle], :url => '', :html => {:class => 'ng order_cycle', 'ng-app' => 'admin.order_cycles', 'ng-controller' => ng_controller, 'ng-submit' => 'submit($event)'} do |f|
- if order_cycles_simple_form
= render 'simple_form', f: f

View File

@@ -1,2 +1,2 @@
object OpenStruct.new(flash)
object OpenStruct.new(flash.to_hash)
attributes :info, :success, :error

View File

@@ -0,0 +1,33 @@
Dear #{@producer.name},
\
We now have all the consumer orders for next food drop. Please drop off your delivery at #{@receival_time}.
- if @receival_instructions
Extra instructions: #{@receival_instructions}
Please deliver to #{@coordinator.address.address1}, #{@coordinator.address.city}, #{@coordinator.address.zipcode} during the regular delivery time. If this is not convenient then please call #{@coordinator.phone}.
Note: If you have to arrange a different delivery day and time, it is requested that you do not come on site during drop off/pick up times.
\
Orders summary
================
\
Here is a summary of the orders for your products:
\
- @line_items.each_pair do |variant, line_item|
#{variant.sku} - #{raw(variant.product.supplier.name)} - #{raw(variant.product_and_variant_name)} (QTY: #{line_item.quantity}) @ #{line_item.single_money} = #{line_item.display_amount}
\
Details
=========
\
For a detailed orders breakdown, please log into your account.
Please confirm that you have received this email.
Please send me an invoice for this amount so we can send you payment.
If you need to phone on the day please call #{@coordinator.phone}.
\
Thanks and best wishes - #{@coordinator.name}

View File

@@ -0,0 +1,25 @@
.row
.alpha.four.columns
= f.field_container :amount do
= f.label :amount, raw(t(:amount) + content_tag(:span, " *", :class => "required"))
= text_field :adjustment, :amount, :class => 'fullwidth'
= f.error_message_on :amount
.four.columns
= f.field_container :included_tax do
= f.label :included_tax, t(:included_tax)
= text_field :adjustment, :included_tax, disabled: true, class: 'fullwidth'
= f.error_message_on :included_tax
.omega.four.columns
= f.field_container :tax_rate_id do
= f.label :tax_rate_id, t(:tax_rate)
= select_tag :tax_rate_id, options_from_collection_for_select(Spree::TaxRate.all, :id, :name, @tax_rate_id), prompt: t(:remove_tax), class: 'select2 fullwidth'
= f.error_message_on :tax_rate_id
.row
.alpha.omega.twelve.columns
= f.field_container :label do
= f.label :label, raw(t(:description) + content_tag(:span, " *", :class => "required"))
= text_field :adjustment, :label, :class => 'fullwidth'
= f.error_message_on :label

View File

@@ -0,0 +1,19 @@
.row
.alpha.three.columns
= f.field_container :amount do
= f.label :amount, raw(t(:amount) + content_tag(:span, " *", :class => "required"))
= text_field :adjustment, :amount, :class => 'fullwidth'
= f.error_message_on :amount
.omega.three.columns
= f.field_container :tax_rate_id do
= f.label :tax_rate_id, t(:tax_rate)
= select_tag :tax_rate_id, options_from_collection_for_select(Spree::TaxRate.all, :id, :name), prompt: t(:none), class: 'select2 fullwidth'
= f.error_message_on :tax_rate_id
.row
.alpha.omega.twelve.columns
= f.field_container :label do
= f.label :label, raw(t(:description) + content_tag(:span, " *", :class => "required"))
= text_field :adjustment, :label, :class => 'fullwidth'
= f.error_message_on :label

View File

@@ -47,7 +47,10 @@ module Openfoodnetwork
# -- all .rb files in that directory are automatically loaded.
# Custom directories with classes and modules you want to be autoloadable.
config.autoload_paths += %W(#{config.root}/app/presenters)
config.autoload_paths += %W(
#{config.root}/app/presenters
#{config.root}/app/jobs
)
# Only load the plugins named here, in the order given (default is alphabetical).
# :all can be used as a placeholder for all plugins not explicitly named.

View File

@@ -631,3 +631,5 @@ Please follow the instructions there to make your enterprise visible on the Open
transport_fee: "Transport fee"
fundraising_fee: "Fundraising fee"
price_graph: "Price graph"
included_tax: "Included tax"
remove_tax: "Remove tax"

View File

@@ -62,7 +62,11 @@ Openfoodnetwork::Application.routes.draw do
namespace :admin do
resources :order_cycles do
post :bulk_update, on: :collection, as: :bulk_update
get :clone, on: :member
member do
get :clone
post :notify_producers
end
end
resources :enterprises do

View File

@@ -0,0 +1,6 @@
class AddReceivalTimeToExchange < ActiveRecord::Migration
def change
add_column :exchanges, :receival_time, :string
add_column :exchanges, :receival_instructions, :string
end
end

View File

@@ -386,6 +386,8 @@ ActiveRecord::Schema.define(:version => 20151002020537) do
t.datetime "created_at", :null => false
t.datetime "updated_at", :null => false
t.boolean "incoming", :default => false, :null => false
t.string "receival_time"
t.string "receival_instructions"
end
add_index "exchanges", ["order_cycle_id"], :name => "index_exchanges_on_order_cycle_id"

View File

@@ -35,43 +35,8 @@ module OpenFoodNetwork
tax_rates = enterprise_fee.tax_category ? enterprise_fee.tax_category.tax_rates.match(order) : []
tax_rates.sum do |rate|
compute_tax rate, adjustment.amount
rate.compute_tax adjustment.amount
end
end
# Apply a TaxRate to a particular amount. TaxRates normally compute against
# LineItems or Orders, so we mock out a line item here to fit the interface
# that our calculator (usually DefaultTax) expects.
def compute_tax(tax_rate, amount)
product = OpenStruct.new tax_category: tax_rate.tax_category
line_item = Spree::LineItem.new quantity: 1
line_item.define_singleton_method(:product) { product }
line_item.define_singleton_method(:price) { amount }
# The enterprise fee adjustments for which we're calculating tax are always inclusive of
# tax. However, there's nothing to stop an admin from setting one up with a tax rate
# that's marked as not inclusive of tax, and that would result in the DefaultTax
# calculator generating a slightly incorrect value. Therefore, we treat the tax
# rate as inclusive of tax for the calculations below, regardless of its original
# setting.
with_tax_included_in_price(tax_rate) do
tax_rate.calculator.compute line_item
end
end
def with_tax_included_in_price(tax_rate)
old_included_in_price = tax_rate.included_in_price
tax_rate.included_in_price = true
tax_rate.calculator.calculable.included_in_price = true
result = yield
tax_rate.included_in_price = old_included_in_price
tax_rate.calculator.calculable.included_in_price = old_included_in_price
result
end
end
end

View File

@@ -1,3 +1,5 @@
require 'open_food_network/order_cycle_permissions'
module OpenFoodNetwork
# There are two translator classes on the boundary between Angular and Rails: On the Angular side,
@@ -21,10 +23,14 @@ module OpenFoodNetwork
if exchange_exists?(exchange[:enterprise_id], @order_cycle.coordinator_id, true)
update_exchange(exchange[:enterprise_id], @order_cycle.coordinator_id, true,
{variant_ids: variant_ids, enterprise_fee_ids: enterprise_fee_ids})
{variant_ids: variant_ids, enterprise_fee_ids: enterprise_fee_ids,
receival_time: exchange[:receival_time],
receival_instructions: exchange[:receival_instructions]})
else
add_exchange(exchange[:enterprise_id], @order_cycle.coordinator_id, true,
{variant_ids: variant_ids, enterprise_fee_ids: enterprise_fee_ids})
{variant_ids: variant_ids, enterprise_fee_ids: enterprise_fee_ids,
receival_time: exchange[:receival_time],
receival_instructions: exchange[:receival_instructions],})
end
end
@@ -35,12 +41,16 @@ module OpenFoodNetwork
if exchange_exists?(@order_cycle.coordinator_id, exchange[:enterprise_id], false)
update_exchange(@order_cycle.coordinator_id, exchange[:enterprise_id], false,
{variant_ids: variant_ids, enterprise_fee_ids: enterprise_fee_ids,
pickup_time: exchange[:pickup_time], pickup_instructions: exchange[:pickup_instructions]})
{variant_ids: variant_ids,
enterprise_fee_ids: enterprise_fee_ids,
pickup_time: exchange[:pickup_time],
pickup_instructions: exchange[:pickup_instructions]})
else
add_exchange(@order_cycle.coordinator_id, exchange[:enterprise_id], false,
{variant_ids: variant_ids, enterprise_fee_ids: enterprise_fee_ids,
pickup_time: exchange[:pickup_time], pickup_instructions: exchange[:pickup_instructions]})
{variant_ids: variant_ids,
enterprise_fee_ids: enterprise_fee_ids,
pickup_time: exchange[:pickup_time],
pickup_instructions: exchange[:pickup_instructions]})
end
end

View File

@@ -1,5 +1,5 @@
function load_environment {
source /var/lib/jenkins/.rvm/environments/ruby-1.9.3-p392
source /var/lib/jenkins/.rvm/environments/ruby-2.1.5
if [ ! -f config/application.yml ]; then
ln -s application.yml.example config/application.yml
fi

View File

@@ -3,6 +3,7 @@ require 'spec_helper'
module Admin
describe OrderCyclesController do
include AuthenticationWorkflow
let!(:distributor_owner) { create_enterprise_user enterprise_limit: 2 }
before do
@@ -101,6 +102,34 @@ module Admin
end
end
describe "notifying producers" do
let(:user) { create_enterprise_user }
let(:admin_user) do
user = create(:user)
user.spree_roles << Spree::Role.find_or_create_by_name!('admin')
user
end
let(:order_cycle) { create(:simple_order_cycle) }
before do
controller.stub spree_current_user: admin_user
end
it "enqueues a job" do
expect do
spree_post :notify_producers, {id: order_cycle.id}
end.to enqueue_job OrderCycleNotificationJob
end
it "redirects back to the order cycles path with a success message" do
spree_post :notify_producers, {id: order_cycle.id}
expect(response).to redirect_to admin_order_cycles_path
flash[:notice].should == 'Emails to be sent to producers have been queued for sending.'
end
end
describe "destroy" do
let!(:distributor) { create(:distributor_enterprise, owner: distributor_owner) }

View File

@@ -0,0 +1,60 @@
require 'spec_helper'
module Spree
describe Admin::AdjustmentsController do
include AuthenticationWorkflow
before { login_as_admin }
describe "setting included tax" do
let(:order) { create(:order) }
let(:tax_rate) { create(:tax_rate, amount: 0.1, calculator: Spree::Calculator::DefaultTax.new) }
describe "creating an adjustment" do
it "sets included tax to zero when no tax rate is specified" do
spree_post :create, {order_id: order.number, adjustment: {label: 'Testing included tax', amount: '110'}, tax_rate_id: ''}
response.should redirect_to spree.admin_order_adjustments_path(order)
a = Adjustment.last
a.label.should == 'Testing included tax'
a.amount.should == 110
a.included_tax.should == 0
end
it "calculates included tax when a tax rate is provided" do
spree_post :create, {order_id: order.number, adjustment: {label: 'Testing included tax', amount: '110'}, tax_rate_id: tax_rate.id.to_s}
response.should redirect_to spree.admin_order_adjustments_path(order)
a = Adjustment.last
a.label.should == 'Testing included tax'
a.amount.should == 110
a.included_tax.should == 10
end
end
describe "updating an adjustment" do
let(:adjustment) { create(:adjustment, adjustable: order, amount: 1100, included_tax: 100) }
it "sets included tax to zero when no tax rate is specified" do
spree_put :update, {order_id: order.number, id: adjustment.id, adjustment: {label: 'Testing included tax', amount: '110'}, tax_rate_id: ''}
response.should redirect_to spree.admin_order_adjustments_path(order)
a = Adjustment.last
a.label.should == 'Testing included tax'
a.amount.should == 110
a.included_tax.should == 0
end
it "calculates included tax when a tax rate is provided" do
spree_put :update, {order_id: order.number, id: adjustment.id, adjustment: {label: 'Testing included tax', amount: '110'}, tax_rate_id: tax_rate.id.to_s}
response.should redirect_to spree.admin_order_adjustments_path(order)
a = Adjustment.last
a.label.should == 'Testing included tax'
a.amount.should == 110
a.included_tax.should == 10
end
end
end
end
end

View File

@@ -15,9 +15,11 @@ FactoryGirl.define do
# Incoming Exchanges
ex1 = create(:exchange, :order_cycle => oc, :incoming => true,
:sender => supplier1, :receiver => oc.coordinator)
:sender => supplier1, :receiver => oc.coordinator,
:receival_time => 'time 0', :receival_instructions => 'instructions 0')
ex2 = create(:exchange, :order_cycle => oc, :incoming => true,
:sender => supplier2, :receiver => oc.coordinator)
:sender => supplier2, :receiver => oc.coordinator,
:receival_time => 'time 1', :receival_instructions => 'instructions 1')
ExchangeFee.create!(exchange: ex1,
enterprise_fee: create(:enterprise_fee, enterprise: ex1.sender))
ExchangeFee.create!(exchange: ex2,
@@ -71,7 +73,7 @@ FactoryGirl.define do
after(:create) do |oc, proxy|
proxy.suppliers.each do |supplier|
ex = create(:exchange, :order_cycle => oc, :sender => supplier, :receiver => oc.coordinator, :incoming => true, :pickup_time => 'time', :pickup_instructions => 'instructions')
ex = create(:exchange, :order_cycle => oc, :sender => supplier, :receiver => oc.coordinator, :incoming => true, :receival_time => 'time', :receival_instructions => 'instructions')
proxy.variants.each { |v| ex.variants << v }
end

View File

@@ -0,0 +1,89 @@
require "spec_helper"
feature %q{
As an administrator
I want to manage adjustments on orders
} do
include AuthenticationWorkflow
include WebHelper
let!(:user) { create(:user) }
let!(:distributor) { create(:distributor_enterprise, charges_sales_tax: true) }
let!(:order_cycle) { create(:simple_order_cycle, distributors: [distributor]) }
let!(:order) { create(:order_with_totals_and_distribution, user: user, distributor: distributor, order_cycle: order_cycle, state: 'complete', payment_state: 'balance_due') }
let!(:tax_rate) { create(:tax_rate, name: 'GST', calculator: build(:calculator, preferred_amount: 10), zone: create(:zone_with_member)) }
before do
order.finalize!
create(:check_payment, order: order, amount: order.total)
end
scenario "adding taxed adjustments to an order" do
# When I go to the adjustments page for the order
login_to_admin_section
visit spree.admin_orders_path
page.find('td.actions a.icon-edit').click
click_link 'Adjustments'
# And I create a new adjustment with tax
click_link 'New Adjustment'
fill_in 'adjustment_amount', with: 110
fill_in 'adjustment_label', with: 'Late fee'
select 'GST', from: 'tax_rate_id'
click_button 'Continue'
# Then I should see the adjustment, with the correct tax
page.should have_selector 'td.label', text: 'Late fee'
page.should have_selector 'td.amount', text: '110'
page.should have_selector 'td.included-tax', text: '10'
end
scenario "modifying taxed adjustments on an order" do
# Given a taxed adjustment
adjustment = create(:adjustment, adjustable: order, amount: 110, included_tax: 10)
# When I go to the adjustments page for the order
login_to_admin_section
visit spree.admin_orders_path
page.find('td.actions a.icon-edit').click
click_link 'Adjustments'
page.find('td.actions a.icon-edit').click
# Then I should see the uneditable included tax and our tax rate as the default
page.should have_field :adjustment_included_tax, with: '10.00', disabled: true
page.should have_select :tax_rate_id, selected: 'GST'
# When I edit the adjustment, removing the tax
select 'Remove tax', from: :tax_rate_id
click_button 'Continue'
# Then the adjustment tax should be cleared
page.should have_selector 'td.amount', text: '110'
page.should have_selector 'td.included-tax', text: '0'
end
scenario "modifying an untaxed adjustment on an order" do
# Given an untaxed adjustment
adjustment = create(:adjustment, adjustable: order, amount: 110, included_tax: 0)
# When I go to the adjustments page for the order
login_to_admin_section
visit spree.admin_orders_path
page.find('td.actions a.icon-edit').click
click_link 'Adjustments'
page.find('td.actions a.icon-edit').click
# Then I should see the uneditable included tax and 'Remove tax' as the default tax rate
page.should have_field :adjustment_included_tax, with: '0.00', disabled: true
page.should have_select :tax_rate_id, selected: []
# When I edit the adjustment, setting a tax rate
select 'GST', from: :tax_rate_id
click_button 'Continue'
# Then the adjustment tax should be recalculated
page.should have_selector 'td.amount', text: '110'
page.should have_selector 'td.included-tax', text: '10'
end
end

View File

@@ -294,7 +294,6 @@ feature %q{
expect(page).to have_selector "tr#li_#{li1.id}"
expect(page).to have_selector "tr#li_#{li2.id}"
select2_select oc1.name, from: "order_cycle_filter"
expect(page).to have_selector "#loading img.spinner"
expect(page).to_not have_selector "#loading img.spinner"
expect(page).to have_selector "tr#li_#{li1.id}"
expect(page).to_not have_selector "tr#li_#{li2.id}"

View File

@@ -1,8 +1,8 @@
require "spec_helper"
feature %q{
As a payment administrator
I want to capture multiple payments quickly from the one page
As an administrator
I want to manage orders
} do
include AuthenticationWorkflow
include WebHelper
@@ -10,7 +10,7 @@ feature %q{
background do
@user = create(:user)
@product = create(:simple_product)
@distributor = create(:distributor_enterprise)
@distributor = create(:distributor_enterprise, charges_sales_tax: true)
@order_cycle = create(:simple_order_cycle, distributors: [@distributor], variants: [@product.variants.first])
@order = create(:order_with_totals_and_distribution, user: @user, distributor: @distributor, order_cycle: @order_cycle, state: 'complete', payment_state: 'balance_due')
@@ -47,7 +47,7 @@ feature %q{
o.order_cycle.should == order_cycle
end
scenario "can add a product to an existing order", js: true do
scenario "can add a product to an existing order", js: true, retry: 3 do
login_to_admin_section
visit '/admin/orders'
@@ -106,6 +106,7 @@ feature %q{
current_path.should == spree.admin_orders_path
end
context "as an enterprise manager" do
let(:coordinator1) { create(:distributor_enterprise) }
let(:coordinator2) { create(:distributor_enterprise) }

View File

@@ -0,0 +1,12 @@
require 'spec_helper'
describe OrderCycleNotificationJob do
let(:order_cycle) { create(:order_cycle) }
it "sends a mail to each supplier" do
mail = double(:mail)
allow(mail).to receive(:deliver)
expect(ProducerMailer).to receive(:order_cycle_report).twice.and_return(mail)
run_job OrderCycleNotificationJob.new(order_cycle.id)
end
end

View File

@@ -65,33 +65,4 @@ module OpenFoodNetwork
efa.send(:order_adjustment_label).should == "Whole order - packing fee by distributor Ballantyne"
end
end
describe "ensuring that tax rate is marked as tax included_in_price" do
let(:efa) { EnterpriseFeeApplicator.new nil, nil, nil }
let(:tax_rate) { create(:tax_rate, included_in_price: false, calculator: Spree::Calculator::DefaultTax.new) }
it "sets included_in_price to true" do
efa.send(:with_tax_included_in_price, tax_rate) do
tax_rate.included_in_price.should be_true
end
end
it "sets the included_in_price value accessible to the calculator to true" do
efa.send(:with_tax_included_in_price, tax_rate) do
tax_rate.calculator.calculable.included_in_price.should be_true
end
end
it "passes through the return value of the block" do
efa.send(:with_tax_included_in_price, tax_rate) do
'asdf'
end.should == 'asdf'
end
it "restores both values to their original afterwards" do
efa.send(:with_tax_included_in_price, tax_rate) {}
tax_rate.included_in_price.should be_false
tax_rate.calculator.calculable.included_in_price.should be_false
end
end
end

View File

@@ -11,7 +11,7 @@ module OpenFoodNetwork
coordinator_id = 123
supplier_id = 456
incoming_exchange = {:enterprise_id => supplier_id, :incoming => true, :variants => {'1' => true, '2' => false, '3' => true}, :enterprise_fee_ids => [1, 2]}
incoming_exchange = {:enterprise_id => supplier_id, :incoming => true, :variants => {'1' => true, '2' => false, '3' => true}, :enterprise_fee_ids => [1, 2], :receival_time => 'receival time', :receival_instructions => 'receival instructions'}
oc = double(:order_cycle, :coordinator_id => coordinator_id, :exchanges => [], :incoming_exchanges => [incoming_exchange], :outgoing_exchanges => [])
@@ -19,7 +19,7 @@ module OpenFoodNetwork
applicator.should_receive(:incoming_exchange_variant_ids).with(incoming_exchange).and_return([1, 3])
applicator.should_receive(:exchange_exists?).with(supplier_id, coordinator_id, true).and_return(false)
applicator.should_receive(:add_exchange).with(supplier_id, coordinator_id, true, {:variant_ids => [1, 3], :enterprise_fee_ids => [1, 2]})
applicator.should_receive(:add_exchange).with(supplier_id, coordinator_id, true, {:variant_ids => [1, 3], :enterprise_fee_ids => [1, 2], :receival_time => 'receival time', :receival_instructions => 'receival instructions'})
applicator.should_receive(:destroy_untouched_exchanges)
applicator.go!
@@ -47,7 +47,7 @@ module OpenFoodNetwork
coordinator_id = 123
supplier_id = 456
incoming_exchange = {:enterprise_id => supplier_id, :incoming => true, :variants => {'1' => true, '2' => false, '3' => true}, :enterprise_fee_ids => [1, 2]}
incoming_exchange = {:enterprise_id => supplier_id, :incoming => true, :variants => {'1' => true, '2' => false, '3' => true}, :enterprise_fee_ids => [1, 2], :receival_time => 'receival time', :receival_instructions => 'receival instructions'}
oc = double(:order_cycle,
:coordinator_id => coordinator_id,
@@ -59,7 +59,7 @@ module OpenFoodNetwork
applicator.should_receive(:incoming_exchange_variant_ids).with(incoming_exchange).and_return([1, 3])
applicator.should_receive(:exchange_exists?).with(supplier_id, coordinator_id, true).and_return(true)
applicator.should_receive(:update_exchange).with(supplier_id, coordinator_id, true, {:variant_ids => [1, 3], :enterprise_fee_ids => [1, 2]})
applicator.should_receive(:update_exchange).with(supplier_id, coordinator_id, true, {:variant_ids => [1, 3], :enterprise_fee_ids => [1, 2], :receival_time => 'receival time', :receival_instructions => 'receival instructions'})
applicator.should_receive(:destroy_untouched_exchanges)
applicator.go!

View File

@@ -0,0 +1,81 @@
require 'spec_helper'
require 'yaml'
describe ProducerMailer do
let(:s1) { create(:supplier_enterprise) }
let(:s2) { create(:supplier_enterprise) }
let(:s3) { create(:supplier_enterprise) }
let(:d1) { create(:distributor_enterprise) }
let(:d2) { create(:distributor_enterprise) }
let(:p1) { create(:product, price: 12.34, supplier: s1) }
let(:p2) { create(:product, price: 23.45, supplier: s2) }
let(:p3) { create(:product, price: 34.56, supplier: s1) }
let(:order_cycle) { create(:simple_order_cycle) }
let!(:incoming_exchange) { order_cycle.exchanges.create! sender: s1, receiver: d1, incoming: true, receival_time: '10am Saturday', receival_instructions: 'Outside shed.' }
let!(:order) do
order = create(:order, distributor: d1, order_cycle: order_cycle, state: 'complete')
order.line_items << create(:line_item, variant: p1.master)
order.line_items << create(:line_item, variant: p1.master)
order.line_items << create(:line_item, variant: p2.master)
order.finalize!
order.save
order
end
let!(:order_incomplete) do
order = create(:order, distributor: d1, order_cycle: order_cycle, state: 'payment')
order.line_items << create(:line_item, variant: p3.master)
order.save
order
end
let(:mail) { ActionMailer::Base.deliveries.last }
before do
ActionMailer::Base.deliveries.clear
ProducerMailer.order_cycle_report(s1, order_cycle).deliver
end
it "should send an email when an order cycle is closed" do
ActionMailer::Base.deliveries.count.should == 1
end
it "sets a reply-to of the enterprise email" do
mail.reply_to.should == [s1.email]
end
it "includes receival time" do
mail.body.should include '10am Saturday'
end
it "includes receival instructions" do
mail.body.should include 'Outside shed.'
end
it "cc's the enterprise" do
mail.cc.should == [s1.email]
end
it "contains an aggregated list of produce" do
body_lines_including(mail, p1.name).each do |line|
line.should include 'QTY: 2'
line.should include '@ $10.00 = $20.00'
end
end
it "does not include incomplete orders" do
mail.body.should_not include p3.name
end
it "sends no mail when the producer has no orders" do
expect do
ProducerMailer.order_cycle_report(s3, order_cycle).deliver
end.to change(ActionMailer::Base.deliveries, :count).by(0)
end
private
def body_lines_including(mail, s)
mail.body.to_s.lines.select { |line| line.include? s }
end
end

View File

@@ -277,6 +277,7 @@ describe Exchange do
'payment_enterprise_id' => exchange.payment_enterprise_id, 'variant_ids' => exchange.variant_ids.sort,
'enterprise_fee_ids' => exchange.enterprise_fee_ids.sort,
'pickup_time' => exchange.pickup_time, 'pickup_instructions' => exchange.pickup_instructions,
'receival_time' => exchange.receival_time, 'receival_instructions' => exchange.receival_instructions,
'created_at' => exchange.created_at, 'updated_at' => exchange.updated_at}
end
@@ -286,7 +287,8 @@ describe Exchange do
'incoming' => exchange.incoming,
'payment_enterprise_id' => exchange.payment_enterprise_id, 'variant_ids' => exchange.variant_ids.sort,
'enterprise_fee_ids' => exchange.enterprise_fee_ids.sort,
'pickup_time' => exchange.pickup_time, 'pickup_instructions' => exchange.pickup_instructions}
'pickup_time' => exchange.pickup_time, 'pickup_instructions' => exchange.pickup_instructions,
'receival_time' => exchange.receival_time, 'receival_instructions' => exchange.receival_instructions}
end
end

View File

@@ -29,5 +29,42 @@ module Spree
end
end
end
describe "ensuring that tax rate is marked as tax included_in_price" do
let(:tax_rate) { create(:tax_rate, included_in_price: false, calculator: Spree::Calculator::DefaultTax.new) }
it "sets included_in_price to true" do
tax_rate.send(:with_tax_included_in_price) do
tax_rate.included_in_price.should be_true
end
end
it "sets the included_in_price value accessible to the calculator to true" do
tax_rate.send(:with_tax_included_in_price) do
tax_rate.calculator.calculable.included_in_price.should be_true
end
end
it "passes through the return value of the block" do
tax_rate.send(:with_tax_included_in_price) do
'asdf'
end.should == 'asdf'
end
it "restores both values to their original afterwards" do
tax_rate.send(:with_tax_included_in_price) {}
tax_rate.included_in_price.should be_false
tax_rate.calculator.calculable.included_in_price.should be_false
end
it "restores both values when an exception is raised" do
expect do
tax_rate.send(:with_tax_included_in_price) { raise Exception.new 'oops' }
end.to raise_error 'oops'
tax_rate.included_in_price.should be_false
tax_rate.calculator.calculable.included_in_price.should be_false
end
end
end
end

View File

@@ -1,6 +1,6 @@
module OpenFoodNetwork
module HtmlHelper
def save_and_open(html)
def html_save_and_open(html)
require "launchy"
file = Tempfile.new('html')
file.write html