mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
Merge remote-tracking branch 'origin/master' into i18n
Conflicts: config/locales/en.yml
This commit is contained in:
@@ -1 +1 @@
|
||||
1.9.3-p392
|
||||
2.1.5
|
||||
|
||||
@@ -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.
|
||||
|
||||
6
Gemfile
6
Gemfile
@@ -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'
|
||||
|
||||
23
Gemfile.lock
23
Gemfile.lock
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
6
app/jobs/order_cycle_notification_job.rb
Normal file
6
app/jobs/order_cycle_notification_job.rb
Normal 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
|
||||
53
app/mailers/producer_mailer.rb
Normal file
53
app/mailers/producer_mailer.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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'
|
||||
|
||||
@@ -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'}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -1,2 +1,2 @@
|
||||
object OpenStruct.new(flash)
|
||||
object OpenStruct.new(flash.to_hash)
|
||||
attributes :info, :success, :error
|
||||
|
||||
33
app/views/producer_mailer/order_cycle_report.text.haml
Normal file
33
app/views/producer_mailer/order_cycle_report.text.haml
Normal 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}
|
||||
25
app/views/spree/admin/adjustments/_edit_form.html.haml
Normal file
25
app/views/spree/admin/adjustments/_edit_form.html.haml
Normal 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
|
||||
19
app/views/spree/admin/adjustments/_new_form.html.haml
Normal file
19
app/views/spree/admin/adjustments/_new_form.html.haml
Normal 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
|
||||
@@ -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.
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
class AddReceivalTimeToExchange < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :exchanges, :receival_time, :string
|
||||
add_column :exchanges, :receival_instructions, :string
|
||||
end
|
||||
end
|
||||
@@ -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"
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) }
|
||||
|
||||
|
||||
60
spec/controllers/spree/admin/adjustments_controller_spec.rb
Normal file
60
spec/controllers/spree/admin/adjustments_controller_spec.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
89
spec/features/admin/adjustments_spec.rb
Normal file
89
spec/features/admin/adjustments_spec.rb
Normal 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
|
||||
@@ -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}"
|
||||
|
||||
@@ -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) }
|
||||
|
||||
12
spec/jobs/order_cycle_notification_job_spec.rb
Normal file
12
spec/jobs/order_cycle_notification_job_spec.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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!
|
||||
|
||||
81
spec/mailers/producer_mailer_spec.rb
Normal file
81
spec/mailers/producer_mailer_spec.rb
Normal 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
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user