mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
Merge branch 'master' into folklabs-producer-emails
This commit is contained in:
10
Gemfile.lock
10
Gemfile.lock
@@ -23,7 +23,7 @@ GIT
|
||||
|
||||
GIT
|
||||
remote: git://github.com/openfoodfoundation/spree.git
|
||||
revision: 4e0075b07acb56864aca89eee3d9670136176c23
|
||||
revision: afcc23e489eb604a3e2651598a7c8364e2acc7b3
|
||||
branch: 1-3-stable
|
||||
specs:
|
||||
spree (1.3.6.beta)
|
||||
@@ -123,7 +123,7 @@ GEM
|
||||
active_link_to (1.0.0)
|
||||
active_model_serializers (0.8.1)
|
||||
activemodel (>= 3.0)
|
||||
activemerchant (1.46.0)
|
||||
activemerchant (1.48.0)
|
||||
activesupport (>= 3.2.14, < 5.0.0)
|
||||
builder (>= 2.1.2, < 4.0.0)
|
||||
i18n (>= 0.6.9)
|
||||
@@ -181,7 +181,7 @@ GEM
|
||||
climate_control (0.0.3)
|
||||
activesupport (>= 3.0)
|
||||
cliver (0.3.2)
|
||||
cocaine (0.5.5)
|
||||
cocaine (0.5.7)
|
||||
climate_control (>= 0.0.3, < 1.0)
|
||||
coderay (1.0.9)
|
||||
coffee-rails (3.2.2)
|
||||
@@ -191,7 +191,7 @@ GEM
|
||||
coffee-script-source
|
||||
execjs
|
||||
coffee-script-source (1.3.3)
|
||||
colorize (0.7.5)
|
||||
colorize (0.7.7)
|
||||
columnize (0.3.6)
|
||||
comfortable_mexican_sofa (1.6.24)
|
||||
active_link_to (~> 1.0.0)
|
||||
@@ -496,7 +496,7 @@ GEM
|
||||
sprockets (>= 2.0.0)
|
||||
turn (0.8.3)
|
||||
ansi
|
||||
tzinfo (0.3.43)
|
||||
tzinfo (0.3.44)
|
||||
uglifier (1.2.4)
|
||||
execjs (>= 0.3.0)
|
||||
multi_json (>= 1.0.2)
|
||||
|
||||
3
app/assets/stylesheets/admin/icons.css.scss
Normal file
3
app/assets/stylesheets/admin/icons.css.scss
Normal file
@@ -0,0 +1,3 @@
|
||||
@import 'plugins/font-awesome';
|
||||
|
||||
.icon-refund:before { @extend .icon-ok:before }
|
||||
@@ -9,6 +9,13 @@ Spree::Admin::OrdersController.class_eval do
|
||||
# in an auth failure as the @order object is nil for collection actions
|
||||
before_filter :check_authorization, :except => :bulk_management
|
||||
|
||||
# After updating an order, the fees should be updated as well
|
||||
# Currently, adding or deleting line items does not trigger updating the
|
||||
# fees! This is a quick fix for that.
|
||||
# TODO: update fees when adding/removing line items
|
||||
# instead of the update_distribution_charge method.
|
||||
after_filter :update_distribution_charge, :only => :update
|
||||
|
||||
respond_override :index => { :html =>
|
||||
{ :success => lambda {
|
||||
# Filter orders to only show those distributed by current user (or all for admin user)
|
||||
@@ -17,4 +24,17 @@ Spree::Admin::OrdersController.class_eval do
|
||||
page(params[:page]).
|
||||
per(params[:per_page] || Spree::Config[:orders_per_page])
|
||||
} } }
|
||||
|
||||
# Overwrite to use confirm_email_for_customer instead of confirm_email.
|
||||
# This uses a new template. See mailers/spree/order_mailer_decorator.rb.
|
||||
def resend
|
||||
Spree::OrderMailer.confirm_email_for_customer(@order.id, true).deliver
|
||||
flash[:success] = t(:order_email_resent)
|
||||
|
||||
respond_with(@order) { |format| format.html { redirect_to :back } }
|
||||
end
|
||||
|
||||
def update_distribution_charge
|
||||
@order.update_distribution_charge!
|
||||
end
|
||||
end
|
||||
|
||||
@@ -6,15 +6,18 @@ Spree::Admin::VariantsController.class_eval do
|
||||
|
||||
@variants = Spree::Variant.ransack(search_params.merge(:m => 'or')).result
|
||||
|
||||
if params[:distributor_id].present?
|
||||
distributor = Enterprise.find params[:distributor_id]
|
||||
@variants = @variants.in_distributor(distributor)
|
||||
end
|
||||
|
||||
if params[:order_cycle_id].present?
|
||||
order_cycle = OrderCycle.find params[:order_cycle_id]
|
||||
@variants = @variants.in_order_cycle(order_cycle)
|
||||
end
|
||||
|
||||
if params[:distributor_id].present?
|
||||
distributor = Enterprise.find params[:distributor_id]
|
||||
@variants = @variants.in_distributor(distributor)
|
||||
# Perform scoping after all filtering is done.
|
||||
# Filtering could be a problem on scoped variants.
|
||||
@variants.each { |v| v.scope_to_hub(distributor) }
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
||||
@@ -198,6 +198,18 @@ Spree::Order.class_eval do
|
||||
end
|
||||
end
|
||||
|
||||
# Does this order have shipments that can be shipped?
|
||||
def ready_to_ship?
|
||||
self.shipments.any?{|s| s.can_ship?}
|
||||
end
|
||||
|
||||
# Ship all pending orders
|
||||
def ship
|
||||
self.shipments.each do |s|
|
||||
s.ship if s.can_ship?
|
||||
end
|
||||
end
|
||||
|
||||
def available_shipping_methods(display_on = nil)
|
||||
Spree::ShippingMethod.all_available(self, display_on)
|
||||
end
|
||||
|
||||
52
app/models/spree/payment_decorator.rb
Normal file
52
app/models/spree/payment_decorator.rb
Normal file
@@ -0,0 +1,52 @@
|
||||
module Spree
|
||||
Payment.class_eval do
|
||||
# Pin payments lacks void and credit methods, but it does have refund
|
||||
# Here we swap credit out for refund and remove void as a possible action
|
||||
def actions_with_pin_payment_adaptations
|
||||
actions = actions_without_pin_payment_adaptations
|
||||
if payment_method.is_a? Gateway::Pin
|
||||
actions << 'refund' if actions.include? 'credit'
|
||||
actions.reject! { |a| ['credit', 'void'].include? a }
|
||||
end
|
||||
actions
|
||||
end
|
||||
alias_method_chain :actions, :pin_payment_adaptations
|
||||
|
||||
|
||||
def refund!(refund_amount=nil)
|
||||
protect_from_connection_error do
|
||||
check_environment
|
||||
|
||||
refund_amount = calculate_refund_amount(refund_amount)
|
||||
|
||||
if payment_method.payment_profiles_supported?
|
||||
response = payment_method.refund((refund_amount * 100).round, source, response_code, gateway_options)
|
||||
else
|
||||
response = payment_method.refund((refund_amount * 100).round, response_code, gateway_options)
|
||||
end
|
||||
|
||||
record_response(response)
|
||||
|
||||
if response.success?
|
||||
self.class.create({ :order => order,
|
||||
:source => self,
|
||||
:payment_method => payment_method,
|
||||
:amount => refund_amount.abs * -1,
|
||||
:response_code => response.authorization,
|
||||
:state => 'completed' }, :without_protection => true)
|
||||
else
|
||||
gateway_error(response)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
private
|
||||
|
||||
def calculate_refund_amount(refund_amount=nil)
|
||||
refund_amount ||= credit_allowed >= order.outstanding_balance.abs ? order.outstanding_balance.abs : credit_allowed.abs
|
||||
refund_amount.to_f
|
||||
end
|
||||
|
||||
end
|
||||
end
|
||||
@@ -74,6 +74,12 @@ Spree::Variant.class_eval do
|
||||
self.option_values.destroy ovs
|
||||
end
|
||||
|
||||
# Used like "product.name - full_name". If called like this, a product with
|
||||
# name "Bread" would be displayed as one of these:
|
||||
# Bread - 1kg # if display_name blank
|
||||
# Bread - Spelt Sourdough, 1kg # if display_name is "Spelt Sourdough, 1kg"
|
||||
# Bread - 1kg Spelt Sourdough # if unit_to_display is "1kg Spelt Sourdough"
|
||||
# Bread - Spelt Sourdough (1kg) # if display_name is "Spelt Sourdough" and unit_to_display is "1kg"
|
||||
def full_name
|
||||
return unit_to_display if display_name.blank?
|
||||
return display_name if display_name.downcase.include? unit_to_display.downcase
|
||||
|
||||
@@ -0,0 +1,3 @@
|
||||
/ insert_before "code[erb-loud]:contains('button_link_to t(:resend)')"
|
||||
- if @order.ready_to_ship?
|
||||
%li= button_link_to t(:ship), fire_admin_order_url(@order, :e => 'ship'), :method => :put, :data => { :confirm => t(:are_you_sure) }
|
||||
@@ -0,0 +1,6 @@
|
||||
/ insert_bottom "[data-hook='admin_orders_index_row_actions']"
|
||||
-# See also: app/overrides/add_capture_order_shortcut.rb
|
||||
|
||||
- if order.ready_to_ship?
|
||||
- # copied from backend/app/views/spree/admin/payments/_list.html.erb
|
||||
= link_to_with_icon "icon-road", t(:ship), fire_admin_order_url(order, :e => 'ship'), :method => :put, :no_text => true, :data => {:action => 'ship', :confirm => t(:are_you_sure)}
|
||||
@@ -0,0 +1,6 @@
|
||||
/ insert_bottom "[data-hook='admin_orders_index_rows'] td:nth-child(3)"
|
||||
|
||||
- if order.special_instructions.present?
|
||||
%br
|
||||
%span{class: "icon-warning-sign with-tip", title: order.special_instructions}
|
||||
notes
|
||||
@@ -0,0 +1,6 @@
|
||||
/ insert_after "code[erb-silent]:contains('content_for :page_title')"
|
||||
|
||||
- if @order.bill_address.present?
|
||||
= @order.bill_address.firstname
|
||||
= @order.bill_address.lastname
|
||||
\-
|
||||
33
app/views/spree/admin/variants/_autocomplete.js.erb
Normal file
33
app/views/spree/admin/variants/_autocomplete.js.erb
Normal file
@@ -0,0 +1,33 @@
|
||||
<script type='text/template' id='variant_autocomplete_template'>
|
||||
<div class='variant-autocomplete-item'>
|
||||
<figure class='variant-image'>
|
||||
{{#if variant.image }}
|
||||
<img src='{{variant.image}}' />
|
||||
{{ else }}
|
||||
<img src='/assets/noimage/mini.png' />
|
||||
{{/if}}
|
||||
</figure>
|
||||
|
||||
<div class='variant-details'>
|
||||
|
||||
<h6 class="variant-name">{{variant.name}}</h6>
|
||||
|
||||
<ul>
|
||||
<li><strong>Producer:</strong> {{variant.producer_name}}</li>
|
||||
</ul>
|
||||
<ul class='variant-data'>
|
||||
<li class='variant-sku'><strong>{{t 'sku'}}:</strong> {{variant.sku}}</li>
|
||||
<li class='variant-on_hand'><strong>{{t 'on_hand' }}:</strong> {{variant.count_on_hand}}</li>
|
||||
</ul>
|
||||
|
||||
{{#if variant.option_values}}
|
||||
<ul class='variant-options'>
|
||||
{{#each variant.option_values}}
|
||||
<li><strong>{{this.option_type.presentation}}:</strong> {{this.presentation}}</li>
|
||||
{{/each}}
|
||||
</ul>
|
||||
{{/if}}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</script>
|
||||
34
app/views/spree/admin/variants/search.rabl
Normal file
34
app/views/spree/admin/variants/search.rabl
Normal file
@@ -0,0 +1,34 @@
|
||||
#
|
||||
# overriding spree/core/app/views/spree/admin/variants/search.rabl
|
||||
#
|
||||
collection @variants
|
||||
attributes :sku, :options_text, :count_on_hand, :id, :cost_price
|
||||
|
||||
node(:name) do |v|
|
||||
# TODO: when products must have a unit, full_name will always be present
|
||||
variant_specific = v.full_name
|
||||
if variant_specific.present?
|
||||
"#{v.name} - #{v.full_name}"
|
||||
else
|
||||
v.name
|
||||
end
|
||||
end
|
||||
|
||||
node(:full_name) do |v|
|
||||
v.full_name
|
||||
end
|
||||
|
||||
node(:producer_name) do |v|
|
||||
v.product.supplier.name
|
||||
end
|
||||
|
||||
child(:images => :images) do
|
||||
attributes :mini_url
|
||||
end
|
||||
|
||||
child(:option_values => :option_values) do
|
||||
child(:option_type => :option_type) do
|
||||
attributes :name, :presentation
|
||||
end
|
||||
attributes :name, :presentation
|
||||
end
|
||||
30
spec/controllers/spree/admin/orders_controller_spec.rb
Normal file
30
spec/controllers/spree/admin/orders_controller_spec.rb
Normal file
@@ -0,0 +1,30 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Spree::Admin::OrdersController do
|
||||
let!(:order) { create(:order) }
|
||||
|
||||
context "updating an order with line items" do
|
||||
let(:line_item) { create(:line_item) }
|
||||
before { login_as_admin }
|
||||
|
||||
it "updates distribution charges" do
|
||||
order.line_items << line_item
|
||||
order.save
|
||||
Spree::Order.any_instance.should_receive(:update_distribution_charge!)
|
||||
spree_put :update, {
|
||||
id: order,
|
||||
order: {
|
||||
number: order.number,
|
||||
distributor_id: order.distributor_id,
|
||||
order_cycle_id: order.order_cycle_id,
|
||||
line_items_attributes: [
|
||||
{
|
||||
id: line_item.id,
|
||||
quantity: line_item.quantity
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -8,6 +8,7 @@ module Spree
|
||||
describe "search action" do
|
||||
let!(:p1) { create(:simple_product, name: 'Product 1') }
|
||||
let!(:p2) { create(:simple_product, name: 'Product 2') }
|
||||
let!(:vo) { create(:variant_override, variant: p1.master, hub: d, count_on_hand: 44) }
|
||||
let!(:d) { create(:distributor_enterprise) }
|
||||
let!(:oc) { create(:simple_order_cycle, distributors: [d], variants: [p1.master]) }
|
||||
|
||||
@@ -16,6 +17,12 @@ module Spree
|
||||
assigns(:variants).should == [p1.master]
|
||||
end
|
||||
|
||||
it "applies variant overrides" do
|
||||
spree_get :search, q: 'Prod', distributor_id: d.id.to_s
|
||||
assigns(:variants).should == [p1.master]
|
||||
assigns(:variants).first.count_on_hand.should == 44
|
||||
end
|
||||
|
||||
it "filters by order cycle" do
|
||||
spree_get :search, q: 'Prod', order_cycle_id: oc.id.to_s
|
||||
assigns(:variants).should == [p1.master]
|
||||
|
||||
@@ -186,6 +186,59 @@ describe Spree::Order do
|
||||
end
|
||||
end
|
||||
|
||||
describe "an order without shipping method" do
|
||||
let(:order) { create(:order) }
|
||||
|
||||
it "cannot be shipped" do
|
||||
order.ready_to_ship?.should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "an unpaid order with a shipment" do
|
||||
let(:order) { create(:order, shipping_method: shipping_method) }
|
||||
let(:shipping_method) { create(:shipping_method) }
|
||||
|
||||
before do
|
||||
order.create_shipment!
|
||||
order.reload
|
||||
order.state = 'complete'
|
||||
order.shipment.update!(order)
|
||||
end
|
||||
|
||||
it "cannot be shipped" do
|
||||
order.ready_to_ship?.should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "a paid order without a shipment" do
|
||||
let(:order) { create(:order) }
|
||||
|
||||
before do
|
||||
order.payment_state = 'paid'
|
||||
order.state = 'complete'
|
||||
end
|
||||
|
||||
it "cannot be shipped" do
|
||||
order.ready_to_ship?.should == false
|
||||
end
|
||||
end
|
||||
|
||||
describe "a paid order with a shipment" do
|
||||
let(:order) { create(:order, shipping_method: shipping_method) }
|
||||
let(:shipping_method) { create(:shipping_method) }
|
||||
|
||||
before do
|
||||
order.create_shipment!
|
||||
order.payment_state = 'paid'
|
||||
order.state = 'complete'
|
||||
order.shipment.update!(order)
|
||||
end
|
||||
|
||||
it "can be shipped" do
|
||||
order.ready_to_ship?.should == true
|
||||
end
|
||||
end
|
||||
|
||||
describe "getting the shipping tax" do
|
||||
let(:order) { create(:order, shipping_method: shipping_method) }
|
||||
let(:shipping_method) { create(:shipping_method, calculator: Spree::Calculator::FlatRate.new(preferred_amount: 50.0)) }
|
||||
|
||||
128
spec/models/spree/payment_spec.rb
Normal file
128
spec/models/spree/payment_spec.rb
Normal file
@@ -0,0 +1,128 @@
|
||||
require 'spec_helper'
|
||||
|
||||
module Spree
|
||||
describe Payment do
|
||||
describe "available actions" do
|
||||
context "for most gateways" do
|
||||
let(:payment) { create(:payment, source: create(:credit_card)) }
|
||||
|
||||
it "can capture and void" do
|
||||
payment.actions.sort.should == %w(capture void).sort
|
||||
end
|
||||
|
||||
describe "when a payment has been taken" do
|
||||
before do
|
||||
payment.stub(:state) { 'completed' }
|
||||
payment.stub(:order) { double(:order, payment_state: 'credit_owed') }
|
||||
end
|
||||
|
||||
it "can void and credit" do
|
||||
payment.actions.sort.should == %w(void credit).sort
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context "for Pin Payments" do
|
||||
let(:d) { create(:distributor_enterprise) }
|
||||
let(:pin) { Gateway::Pin.create! name: 'pin', distributor_ids: [d.id]}
|
||||
let(:payment) { create(:payment, source: create(:credit_card), payment_method: pin) }
|
||||
|
||||
it "does not void" do
|
||||
payment.actions.should_not include 'void'
|
||||
end
|
||||
|
||||
describe "when a payment has been taken" do
|
||||
before do
|
||||
payment.stub(:state) { 'completed' }
|
||||
payment.stub(:order) { double(:order, payment_state: 'credit_owed') }
|
||||
end
|
||||
|
||||
it "can refund instead of crediting" do
|
||||
payment.actions.should_not include 'credit'
|
||||
payment.actions.should include 'refund'
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
describe "refunding" do
|
||||
let(:payment) { create(:payment) }
|
||||
let(:success) { double(:success? => true, authorization: 'abc123') }
|
||||
let(:failure) { double(:success? => false) }
|
||||
|
||||
it "always checks the environment" do
|
||||
payment.payment_method.stub(:refund) { success }
|
||||
payment.should_receive(:check_environment)
|
||||
payment.refund!
|
||||
end
|
||||
|
||||
describe "calculating refund amount" do
|
||||
it "returns the parameter amount when given" do
|
||||
payment.send(:calculate_refund_amount, 123).should === 123.0
|
||||
end
|
||||
|
||||
it "refunds up to the value of the payment when the outstanding balance is larger" do
|
||||
payment.stub(:credit_allowed) { 123 }
|
||||
payment.stub(:order) { double(:order, outstanding_balance: 1000) }
|
||||
payment.send(:calculate_refund_amount).should == 123
|
||||
end
|
||||
|
||||
it "refunds up to the outstanding balance of the order when the payment is larger" do
|
||||
payment.stub(:credit_allowed) { 1000 }
|
||||
payment.stub(:order) { double(:order, outstanding_balance: 123) }
|
||||
payment.send(:calculate_refund_amount).should == 123
|
||||
end
|
||||
end
|
||||
|
||||
describe "performing refunds" do
|
||||
before do
|
||||
payment.stub(:calculate_refund_amount) { 123 }
|
||||
payment.payment_method.should_receive(:refund).and_return(success)
|
||||
end
|
||||
|
||||
it "performs the refund without payment profiles" do
|
||||
payment.payment_method.stub(:payment_profiles_supported?) { false }
|
||||
payment.refund!
|
||||
end
|
||||
|
||||
it "performs the refund with payment profiles" do
|
||||
payment.payment_method.stub(:payment_profiles_supported?) { true }
|
||||
payment.refund!
|
||||
end
|
||||
end
|
||||
|
||||
it "records the response" do
|
||||
payment.stub(:calculate_refund_amount) { 123 }
|
||||
payment.payment_method.stub(:refund).and_return(success)
|
||||
payment.should_receive(:record_response).with(success)
|
||||
payment.refund!
|
||||
end
|
||||
|
||||
it "records a payment on success" do
|
||||
payment.stub(:calculate_refund_amount) { 123 }
|
||||
payment.payment_method.stub(:refund).and_return(success)
|
||||
payment.stub(:record_response)
|
||||
|
||||
expect do
|
||||
payment.refund!
|
||||
end.to change(Payment, :count).by(1)
|
||||
|
||||
p = Payment.last
|
||||
p.order.should == payment.order
|
||||
p.source.should == payment
|
||||
p.payment_method.should == payment.payment_method
|
||||
p.amount.should == -123
|
||||
p.response_code.should == success.authorization
|
||||
p.state.should == 'completed'
|
||||
end
|
||||
|
||||
it "logs the error on failure" do
|
||||
payment.stub(:calculate_refund_amount) { 123 }
|
||||
payment.payment_method.stub(:refund).and_return(failure)
|
||||
payment.stub(:record_response)
|
||||
payment.should_receive(:gateway_error).with(failure)
|
||||
payment.refund!
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
Reference in New Issue
Block a user