mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-02-11 23:17:48 +00:00
Merge pull request #5998 from andrewpbrett/shipping-per-pound
Per-pound shipping calculator
This commit is contained in:
@@ -84,13 +84,13 @@ module Spree
|
||||
|
||||
def permitted_resource_params
|
||||
params.require(:shipping_method).permit(
|
||||
:name, :description, :display_on,
|
||||
:require_ship_address, :tag_list, :calculator_type,
|
||||
:name, :description, :display_on, :require_ship_address, :tag_list, :calculator_type,
|
||||
distributor_ids: [],
|
||||
calculator_attributes: [
|
||||
:id, :preferred_currency, :preferred_amount, :preferred_per_kg, :preferred_flat_percent,
|
||||
:preferred_first_item, :preferred_additional_item, :preferred_max_items,
|
||||
:preferred_minimal_amount, :preferred_normal_amount, :preferred_discount_amount
|
||||
:id, :preferred_currency, :preferred_amount, :preferred_unit_from_list,
|
||||
:preferred_per_unit, :preferred_flat_percent, :preferred_first_item,
|
||||
:preferred_additional_item, :preferred_max_items, :preferred_minimal_amount,
|
||||
:preferred_normal_amount, :preferred_discount_amount
|
||||
]
|
||||
)
|
||||
end
|
||||
|
||||
@@ -45,14 +45,14 @@ module Spree
|
||||
end
|
||||
end
|
||||
|
||||
def preference_field_for(form, field, options)
|
||||
def preference_field_for(form, field, options, object)
|
||||
case options[:type]
|
||||
when :integer
|
||||
form.text_field(field, preference_field_options(options))
|
||||
when :boolean
|
||||
form.check_box(field, preference_field_options(options))
|
||||
when :string
|
||||
form.text_field(field, preference_field_options(options))
|
||||
preference_field_for_text_field(form, field, options, object)
|
||||
when :password
|
||||
form.password_field(field, preference_field_options(options))
|
||||
when :text
|
||||
@@ -62,6 +62,21 @@ module Spree
|
||||
end
|
||||
end
|
||||
|
||||
# Here we show a text field for all string fields except when the field name ends in
|
||||
# "_from_list", in that case we render a dropdown.
|
||||
# In this specific case, to render the dropdown, the object provided must have a method named
|
||||
# like "#{field}_values" that returns an array with the string options to be listed.
|
||||
def preference_field_for_text_field(form, field, options, object)
|
||||
if field.end_with?('_from_list') && object.respond_to?("#{field}_values")
|
||||
list_values = object.__send__("#{field}_values")
|
||||
selected_value = object.__send__(field)
|
||||
form.select(field, options_for_select(list_values, selected_value),
|
||||
preference_field_options(options))
|
||||
else
|
||||
form.text_field(field, preference_field_options(options))
|
||||
end
|
||||
end
|
||||
|
||||
def preference_field_options(options)
|
||||
field_options = case options[:type]
|
||||
when :integer
|
||||
@@ -91,13 +106,21 @@ module Spree
|
||||
)
|
||||
end
|
||||
|
||||
# maps each preference to a hash containing the label and field html.
|
||||
# E.g. { :label => "<label>...", :field => "<select>..." }
|
||||
def preference_fields(object, form)
|
||||
return unless object.respond_to?(:preferences)
|
||||
|
||||
object.preferences.keys.map{ |key|
|
||||
form.label("preferred_#{key}", Spree.t(key) + ": ") +
|
||||
preference_field_for(form, "preferred_#{key}", type: object.preference_type(key))
|
||||
}.join("<br />").html_safe
|
||||
object.preferences.keys.map { |key|
|
||||
preference_label = form.label("preferred_#{key}",
|
||||
Spree.t(key.to_s.gsub("_from_list", "")) + ": ").html_safe
|
||||
preference_field = preference_field_for(
|
||||
form,
|
||||
"preferred_#{key}",
|
||||
{ type: object.preference_type(key) }, object
|
||||
).html_safe
|
||||
{ label: preference_label, field: preference_field }
|
||||
}
|
||||
end
|
||||
|
||||
def link_to_add_fields(name, target, options = {})
|
||||
|
||||
@@ -3,16 +3,30 @@ require 'spree/localized_number'
|
||||
module Calculator
|
||||
class Weight < Spree::Calculator
|
||||
extend Spree::LocalizedNumber
|
||||
preference :per_kg, :decimal, default: 0.0
|
||||
localize_number :preferred_per_kg
|
||||
preference :unit_from_list, :string, default: "kg"
|
||||
preference :per_unit, :decimal, default: 0.0
|
||||
|
||||
localize_number :preferred_per_unit
|
||||
|
||||
def self.description
|
||||
I18n.t('spree.weight')
|
||||
end
|
||||
|
||||
def set_preference(name, value)
|
||||
if name == :unit_from_list && !["kg", "lb"].include?(value)
|
||||
calculable.errors.add(:preferred_unit_from_list, I18n.t(:calculator_preferred_unit_error))
|
||||
else
|
||||
__send__ self.class.preference_setter_method(name), value
|
||||
end
|
||||
end
|
||||
|
||||
def compute(object)
|
||||
line_items = line_items_for object
|
||||
(total_weight(line_items) * preferred_per_kg).round(2)
|
||||
(total_weight(line_items) * preferred_per_unit).round(2)
|
||||
end
|
||||
|
||||
def preferred_unit_from_list_values
|
||||
["kg", "lb"]
|
||||
end
|
||||
|
||||
private
|
||||
@@ -33,8 +47,8 @@ module Calculator
|
||||
|
||||
def weight_per_variant(line_item)
|
||||
if variant_unit(line_item) == 'weight'
|
||||
# The calculator price is per_kg so we need to convert unit_value to kg
|
||||
convert_g_to_kg(line_item.variant.andand.unit_value)
|
||||
# Convert unit_value to the preferred unit
|
||||
convert_weight(line_item.variant.andand.unit_value)
|
||||
else
|
||||
line_item.variant.andand.weight || 0
|
||||
end
|
||||
@@ -42,8 +56,8 @@ module Calculator
|
||||
|
||||
def weight_per_final_weight_volume(line_item)
|
||||
if variant_unit(line_item) == 'weight'
|
||||
# The calculator price is per_kg so we need to convert final_weight_volume to kg
|
||||
convert_g_to_kg(line_item.final_weight_volume)
|
||||
# Convert final_weight_volume to the preferred unit
|
||||
convert_weight(line_item.final_weight_volume)
|
||||
else
|
||||
weight_per_variant(line_item) * quantity_implied_in_final_weight_volume(line_item)
|
||||
end
|
||||
@@ -66,10 +80,14 @@ module Calculator
|
||||
line_item.variant.product.andand.variant_unit
|
||||
end
|
||||
|
||||
def convert_g_to_kg(value)
|
||||
return 0 unless value
|
||||
def convert_weight(value)
|
||||
return 0 unless value && ["kg", "lb"].include?(preferences[:unit_from_list])
|
||||
|
||||
value / 1000
|
||||
if preferences[:unit_from_list] == "kg"
|
||||
value / 1000
|
||||
elsif preferences[:unit_from_list] == "lb"
|
||||
value / 453.6
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,6 +7,10 @@
|
||||
- if !enterprise_fee.new_record?
|
||||
.calculator-settings
|
||||
= f.fields_for :calculator do |calculator_form|
|
||||
= preference_fields(enterprise_fee.calculator, calculator_form)
|
||||
- preference_fields(enterprise_fee.calculator, calculator_form).each do |field|
|
||||
.field.alpha.one.columns
|
||||
= field[:label]
|
||||
.field.omega.two.columns
|
||||
= field[:field]
|
||||
|
||||
= calculator_form_string
|
||||
|
||||
@@ -10,4 +10,8 @@
|
||||
= t(:provider_settings)
|
||||
.preference-settings
|
||||
= fields_for :payment_method, @payment_method do |payment_method_form|
|
||||
= preference_fields(@payment_method, payment_method_form)
|
||||
- preference_fields(@payment_method, payment_method_form).each do |field|
|
||||
.field.alpha.three.columns
|
||||
= field[:label]
|
||||
.field.omega.eight.columns
|
||||
= field[:field]
|
||||
|
||||
@@ -2,12 +2,18 @@
|
||||
%legend{align: "center"}= t(:calculator)
|
||||
#preference-settings
|
||||
.field
|
||||
= f.label(:calculator_type, t(:calculator), for: 'calc_type')
|
||||
= f.select(:calculator_type, @calculators.map { |c| [c.description, c.name] }, {}, {id: 'calc_type', class: 'select2 fullwidth'})
|
||||
.alpha.three.columns
|
||||
= f.label(:calculator_type, t(:calculator), for: 'calc_type')
|
||||
.omega.eight.columns
|
||||
= f.select(:calculator_type, @calculators.map { |c| [c.description, c.name] }, {}, {id: 'calc_type', class: 'select2 fullwidth'})
|
||||
- if !@object.new_record?
|
||||
.field
|
||||
.calculator-settings
|
||||
= f.fields_for :calculator do |calculator_form|
|
||||
= preference_fields(@object.calculator, calculator_form)
|
||||
- preference_fields(@object.calculator, calculator_form).each do |field|
|
||||
.field.alpha.three.columns
|
||||
= field[:label]
|
||||
.field.omega.eight.columns
|
||||
= field[:field]
|
||||
- if @object.calculator.respond_to?(:preferences)
|
||||
%span.calculator-settings-warning.info.warning= t(:calculator_settings_warning)
|
||||
|
||||
@@ -44,8 +44,7 @@
|
||||
%tags-with-translation#something{ object: "shippingMethod", 'find-tags' => 'findTags(query)' }
|
||||
|
||||
.row
|
||||
.alpha.eleven.columns
|
||||
= render partial: 'spree/admin/shared/calculator_fields', locals: { f: f }
|
||||
= render partial: 'spree/admin/shared/calculator_fields', locals: { f: f }
|
||||
|
||||
.row
|
||||
.alpha.eleven.columns
|
||||
|
||||
@@ -2125,6 +2125,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
calculator: "Calculator"
|
||||
calculator_values: "Calculator values"
|
||||
calculator_settings_warning: "If you are changing the calculator type, you must save first before you can edit the calculator settings"
|
||||
calculator_preferred_unit_error: "must be kg or lb"
|
||||
flat_percent_per_item: "Flat Percent (per item)"
|
||||
flat_rate_per_item: "Flat Rate (per item)"
|
||||
flat_rate_per_order: "Flat Rate (per order)"
|
||||
@@ -3146,7 +3147,7 @@ See the %{link} to find out more about %{sitename}'s features and to start using
|
||||
inventory_error_flash_for_insufficient_quantity: "An item in your cart has become unavailable."
|
||||
inventory: Inventory
|
||||
zipcode: Postcode
|
||||
weight: Weight (per kg)
|
||||
weight: Weight (per kg or lb)
|
||||
error_user_destroy_with_orders: "Users with completed orders may not be deleted"
|
||||
cannot_create_payment_without_payment_methods: "You cannot create a payment for an order without any payment methods defined."
|
||||
please_define_payment_methods: "Please define some payment methods first."
|
||||
|
||||
13
db/migrate/20200912190210_update_weight_calculators.rb
Normal file
13
db/migrate/20200912190210_update_weight_calculators.rb
Normal file
@@ -0,0 +1,13 @@
|
||||
class UpdateWeightCalculators < ActiveRecord::Migration
|
||||
def change
|
||||
Spree::Calculator.connection.execute(
|
||||
"UPDATE spree_preferences SET key = replace( key, 'per_kg', 'per_unit') WHERE key ilike '/calculator/weight/per_kg/%'"
|
||||
)
|
||||
|
||||
Calculator::Weight.all.each { |calculator|
|
||||
calculator.preferred_unit_from_list = 'kg'
|
||||
Rails.cache.delete("/calculator/weight/per_kg/#{calculator.id}")
|
||||
calculator.save
|
||||
}
|
||||
end
|
||||
end
|
||||
@@ -29,13 +29,22 @@ describe Spree::Admin::ShippingMethodsController, type: :controller do
|
||||
expect(shipping_method.reload.calculator.preferred_currency).to eq "EUR"
|
||||
end
|
||||
|
||||
it "updates preferred_per_kg of a Weight calculator" do
|
||||
it "updates preferred_per_unit of a Weight calculator" do
|
||||
shipping_method.calculator = create(:weight_calculator, calculable: shipping_method)
|
||||
params[:shipping_method][:calculator_attributes][:preferred_per_kg] = 10
|
||||
params[:shipping_method][:calculator_attributes][:preferred_per_unit] = 10
|
||||
|
||||
spree_post :update, params
|
||||
|
||||
expect(shipping_method.reload.calculator.preferred_per_kg).to eq 10
|
||||
expect(shipping_method.reload.calculator.preferred_per_unit).to eq 10
|
||||
end
|
||||
|
||||
it "updates preferred_unit of a Weight calculator" do
|
||||
shipping_method.calculator = create(:weight_calculator, calculable: shipping_method)
|
||||
params[:shipping_method][:calculator_attributes][:preferred_unit_from_list] = "kg"
|
||||
|
||||
spree_post :update, params
|
||||
|
||||
expect(shipping_method.reload.calculator.preferred_unit_from_list).to eq "kg"
|
||||
end
|
||||
|
||||
it "updates preferred_flat_percent of a FlatPercentPerItem calculator" do
|
||||
|
||||
@@ -13,7 +13,14 @@ FactoryBot.define do
|
||||
end
|
||||
|
||||
factory :weight_calculator, class: Calculator::Weight do
|
||||
after(:build) { |c| c.set_preference(:per_kg, 0.5) }
|
||||
after(:create) { |c| c.set_preference(:per_kg, 0.5); c.save! }
|
||||
after(:build) { |c|
|
||||
c.set_preference(:per_unit, 0.5)
|
||||
c.set_preference(:unit_from_list, "kg")
|
||||
}
|
||||
after(:create) { |c|
|
||||
c.set_preference(:per_unit, 0.5)
|
||||
c.set_preference(:unit_from_list, "kg")
|
||||
c.save!
|
||||
}
|
||||
end
|
||||
end
|
||||
|
||||
@@ -118,7 +118,7 @@ feature "full-page cart", js: true do
|
||||
describe "admin weight calculated fees" do
|
||||
context "order with 2 line items" do
|
||||
let(:admin_fee) {
|
||||
create(:enterprise_fee, calculator: Calculator::Weight.new(preferred_per_kg: 1),
|
||||
create(:enterprise_fee, calculator: Calculator::Weight.new(preferred_per_unit: 1, preferred_unit_from_list: "kg"),
|
||||
enterprise: order_cycle.coordinator, fee_type: 'admin')
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
require 'spec_helper'
|
||||
|
||||
describe Calculator::Weight do
|
||||
it_behaves_like "a model using the LocalizedNumber module", [:preferred_per_kg]
|
||||
it_behaves_like "a model using the LocalizedNumber module", [:preferred_per_unit]
|
||||
|
||||
it "computes shipping cost for an order by total weight" do
|
||||
variant1 = build_stubbed(:variant, unit_value: 10_000)
|
||||
@@ -14,7 +14,8 @@ describe Calculator::Weight do
|
||||
|
||||
order = double(:order, line_items: [line_item1, line_item2, line_item3])
|
||||
|
||||
subject.set_preference(:per_kg, 5)
|
||||
subject.set_preference(:per_unit, 5)
|
||||
subject.set_preference(:unit_from_list, "kg")
|
||||
expect(subject.compute(order)).to eq(350) # (10 * 1 + 20 * 3) * 5
|
||||
end
|
||||
|
||||
@@ -22,7 +23,10 @@ describe Calculator::Weight do
|
||||
let(:variant) { build_stubbed(:variant, unit_value: 10_000) }
|
||||
let(:line_item) { build_stubbed(:line_item, variant: variant, quantity: 2) }
|
||||
|
||||
before { subject.set_preference(:per_kg, 5) }
|
||||
before {
|
||||
subject.set_preference(:per_unit, 5)
|
||||
subject.set_preference(:unit_from_list, "kg")
|
||||
}
|
||||
|
||||
it "computes shipping cost for a line item" do
|
||||
expect(subject.compute(line_item)).to eq(100) # 10 * 2 * 5
|
||||
@@ -57,15 +61,18 @@ describe Calculator::Weight do
|
||||
order = double(:order, line_items: [line_item1, line_item2])
|
||||
object_with_order = double(:object_with_order, order: order)
|
||||
|
||||
subject.set_preference(:per_kg, 5)
|
||||
subject.set_preference(:per_unit, 5)
|
||||
subject.set_preference(:unit_from_list, "kg")
|
||||
expect(subject.compute(object_with_order)).to eq(250) # (10 * 1 + 20 * 2) * 5
|
||||
subject.set_preference(:unit_from_list, "lb")
|
||||
expect(subject.compute(object_with_order)).to eq(551.15) # (10 * 1 + 20 * 2) * 5 * 2.2
|
||||
end
|
||||
|
||||
context "when line item final_weight_volume is set" do
|
||||
let!(:product) { build_stubbed(:product, product_attributes) }
|
||||
let!(:variant) { build_stubbed(:variant, variant_attributes.merge(product: product)) }
|
||||
|
||||
let(:calculator) { described_class.new(preferred_per_kg: 6) }
|
||||
let(:calculator) { described_class.new(preferred_per_unit: 6, preferred_unit_from_list: "kg") }
|
||||
let(:line_item) do
|
||||
build_stubbed(:line_item, variant: variant, quantity: 2).tap do |object|
|
||||
object.send(:calculate_final_weight_volume)
|
||||
@@ -182,7 +189,10 @@ describe Calculator::Weight do
|
||||
}
|
||||
let(:line_item) { build_stubbed(:line_item, variant: variant, quantity: 1) }
|
||||
|
||||
before { subject.set_preference(:per_kg, 5) }
|
||||
before {
|
||||
subject.set_preference(:per_unit, 5)
|
||||
subject.set_preference(:unit_from_list, "kg")
|
||||
}
|
||||
|
||||
context "when unit_value is zero variant.weight is present" do
|
||||
let(:variant) { build_stubbed(:variant, product: product, unit_value: 0, weight: 10.0) }
|
||||
@@ -224,4 +234,20 @@ describe Calculator::Weight do
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
it "allows a preferred_unit of 'kg' and 'lb'" do
|
||||
subject.calculable = build(:shipping_method)
|
||||
subject.set_preference(:per_unit, 5)
|
||||
subject.set_preference(:unit_from_list, "kg")
|
||||
expect(subject.calculable.errors.count).to eq(0)
|
||||
subject.set_preference(:unit_from_list, "lb")
|
||||
expect(subject.calculable.errors.count).to eq(0)
|
||||
end
|
||||
|
||||
it "does not allow a preferred_unit of anything but 'kg' or 'lb'" do
|
||||
subject.calculable = build(:shipping_method)
|
||||
subject.set_preference(:per_unit, 5)
|
||||
subject.set_preference(:unit_from_list, "kb")
|
||||
expect(subject.calculable.errors.count).to eq(1)
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user