Merge pull request #6956 from andrewpbrett/unit-price-backend

Provide actual unit price values in front end shop
This commit is contained in:
Andy Brett
2021-03-25 12:50:59 -07:00
committed by GitHub
7 changed files with 297 additions and 39 deletions

View File

@@ -220,9 +220,9 @@ module Spree
end
def unit_price_price_and_unit
price = Spree::Money.new((rand * 10).round(2), currency: currency)
unit = ["item", "kg"].sample
price.to_html + " / " + unit
unit_price = UnitPrice.new(variant)
Spree::Money.new(price_with_adjustments / unit_price.denominator).to_html +
" / " + unit_price.unit
end
def scoper

View File

@@ -41,10 +41,16 @@ class Api::VariantSerializer < ActiveModel::Serializer
end
def unit_price_price
(rand * 10).round(2)
price_with_fees / unit_price.denominator
end
def unit_price_unit
rand > 0.5 ? "item" : "kg"
unit_price.unit
end
private
def unit_price
@unit_price ||= UnitPrice.new(object)
end
end

View File

@@ -0,0 +1,35 @@
# frozen_string_literal: true
class UnitPrice
def initialize(variant)
@variant = variant
@product = variant.product
end
def denominator
# catches any case where unit is not kg, lb, or L.
return @variant.unit_value if @product&.variant_unit == "items"
case unit
when "lb"
@variant.unit_value / 453.6
when "kg"
@variant.unit_value / 1000
else # Liters
@variant.unit_value
end
end
def unit
return "lb" if WeightsAndMeasures.new(@variant).system == "imperial"
case @product&.variant_unit
when "weight"
"kg"
when "volume"
"L"
else
@product.variant_unit_name.presence || I18n.t("item")
end
end
end

View File

@@ -62,40 +62,7 @@ module VariantUnits
end
def scale_for_unit_value
units = {
'weight' => {
1.0 => { 'name' => 'g', 'system' => 'metric' },
28.35 => { 'name' => 'oz', 'system' => 'imperial' },
453.6 => { 'name' => 'lb', 'system' => 'imperial' },
1000.0 => { 'name' => 'kg', 'system' => 'metric' },
1_000_000.0 => { 'name' => 'T', 'system' => 'metric' }
},
'volume' => {
0.001 => { 'name' => 'mL', 'system' => 'metric' },
1.0 => { 'name' => 'L', 'system' => 'metric' },
1000.0 => { 'name' => 'kL', 'system' => 'metric' }
}
}
scales = units[@variant.product.variant_unit]
product_scale = @variant.product.variant_unit_scale
product_scale_system = scales[product_scale.to_f]['system']
largest_unit = find_largest_unit(scales, product_scale_system)
[largest_unit[0], largest_unit[1]["name"]]
end
# Find the largest available and compatible unit where unit_value comes
# to >= 1 when expressed in it.
# If there is none available where this is true, use the smallest available unit.
def find_largest_unit(scales, product_scale_system)
largest_unit = scales.select { |scale, unit_info|
unit_info['system'] == product_scale_system &&
@variant.unit_value / scale >= 1
}.max
return scales.first if largest_unit.nil?
largest_unit
WeightsAndMeasures.new(@variant).scale_for_unit_value
end
def pluralize(unit_name, count)

View File

@@ -0,0 +1,58 @@
# frozen_string_literal: true
class WeightsAndMeasures
def initialize(variant)
@variant = variant
@units = UNITS
end
def scale_for_unit_value
largest_unit = find_largest_unit(scales_for_variant_unit, system)
return [nil, nil] unless largest_unit
[largest_unit[0], largest_unit[1]["name"]]
end
def system
scales = scales_for_variant_unit
return "custom" unless product_scale = @variant.product.variant_unit_scale
scales[product_scale.to_f]['system']
end
private
UNITS = {
'weight' => {
1.0 => { 'name' => 'g', 'system' => 'metric' },
28.35 => { 'name' => 'oz', 'system' => 'imperial' },
453.6 => { 'name' => 'lb', 'system' => 'imperial' },
1000.0 => { 'name' => 'kg', 'system' => 'metric' },
1_000_000.0 => { 'name' => 'T', 'system' => 'metric' }
},
'volume' => {
0.001 => { 'name' => 'mL', 'system' => 'metric' },
1.0 => { 'name' => 'L', 'system' => 'metric' },
1000.0 => { 'name' => 'kL', 'system' => 'metric' }
}
}.freeze
def scales_for_variant_unit
@units[@variant.product.variant_unit]
end
# Find the largest available and compatible unit where unit_value comes
# to >= 1 when expressed in it.
# If there is none available where this is true, use the smallest available unit.
def find_largest_unit(scales, product_scale_system)
return nil unless scales
largest_unit = scales.select { |scale, unit_info|
unit_info['system'] == product_scale_system &&
@variant.unit_value / scale >= 1
}.max
return scales.first if largest_unit.nil?
largest_unit
end
end

View File

@@ -0,0 +1,105 @@
# frozen_string_literal: true
require 'spec_helper'
describe UnitPrice do
subject { UnitPrice.new(variant) }
let(:variant) { Spree::Variant.new }
let(:product) { instance_double(Spree::Product) }
before do
allow(variant).to receive(:product) { product }
end
describe "#unit" do
context "metric" do
before do
allow(product).to receive(:variant_unit_scale) { 1.0 }
end
it "returns kg for weight" do
allow(product).to receive(:variant_unit) { "weight" }
expect(subject.unit).to eq("kg")
end
it "returns L for volume" do
allow(product).to receive(:variant_unit) { "volume" }
expect(subject.unit).to eq("L")
end
end
context "imperial" do
it "returns lbs" do
allow(product).to receive(:variant_unit_scale) { 453.6 }
allow(product).to receive(:variant_unit) { "weight" }
expect(subject.unit).to eq("lb")
end
end
context "items" do
it "returns items if no unit is specified" do
allow(product).to receive(:variant_unit_name) { nil }
allow(product).to receive(:variant_unit_scale) { nil }
allow(product).to receive(:variant_unit) { "items" }
expect(subject.unit).to eq("Item")
end
it "returns the unit if a unit is specified" do
allow(product).to receive(:variant_unit_name) { "bunch" }
allow(product).to receive(:variant_unit_scale) { nil }
allow(product).to receive(:variant_unit) { "items" }
expect(subject.unit).to eq("bunch")
end
end
end
describe "#denominator" do
context "metric" do
it "returns 0.5 for a 500g variant" do
allow(product).to receive(:variant_unit_scale) { 1.0 }
allow(product).to receive(:variant_unit) { "weight" }
variant.unit_value = 500
expect(subject.denominator).to eq(0.5)
end
it "returns 2 for a 2kg variant" do
allow(product).to receive(:variant_unit_scale) { 1000 }
allow(product).to receive(:variant_unit) { "weight" }
variant.unit_value = 2000
expect(subject.denominator).to eq(2)
end
it "returns 0.5 for a 500mL variant" do
allow(product).to receive(:variant_unit_scale) { 0.001 }
allow(product).to receive(:variant_unit) { "volume" }
variant.unit_value = 0.5
expect(subject.denominator).to eq(0.5)
end
end
context "imperial" do
it "returns 2 for a 2 pound variant" do
allow(product).to receive(:variant_unit_scale) { 453.6 }
allow(product).to receive(:variant_unit) { "weight" }
variant.unit_value = 2*453.6
expect(subject.denominator).to eq(2)
end
end
context "items" do
it "returns 1 if no unit is specified" do
allow(product).to receive(:variant_unit_scale) { nil }
allow(product).to receive(:variant_unit) { "items" }
variant.unit_value = 1
expect(subject.denominator).to eq(1)
end
it "returns 2 for multi-item units" do
allow(product).to receive(:variant_unit_scale) { nil }
allow(product).to receive(:variant_unit) { "items" }
variant.unit_value = 2
expect(subject.denominator).to eq(2)
end
end
end
end

View File

@@ -0,0 +1,87 @@
# frozen_string_literal: true
require 'spec_helper'
describe WeightsAndMeasures do
subject { WeightsAndMeasures.new(variant) }
let(:variant) { Spree::Variant.new }
let(:product) { instance_double(Spree::Product) }
before do
allow(variant).to receive(:product) { product }
end
describe "#system" do
context "weight" do
before do
allow(product).to receive(:variant_unit) { "weight" }
end
it "when scale is for a metric unit" do
allow(product).to receive(:variant_unit_scale) { 1.0 }
expect(subject.system).to eq("metric")
end
it "when scale is for an imperial unit" do
allow(product).to receive(:variant_unit_scale) { 28.35 }
expect(subject.system).to eq("imperial")
end
end
context "volume" do
it "when scale is for a metric unit" do
allow(product).to receive(:variant_unit) { "volume" }
allow(product).to receive(:variant_unit_scale) { 1.0 }
expect(subject.system).to eq("metric")
end
end
context "items" do
it "when scale is for items" do
allow(product).to receive(:variant_unit) { "items" }
allow(product).to receive(:variant_unit_scale) { nil }
expect(subject.system).to eq("custom")
end
end
end
describe "#scale_for_unit_value" do
context "weight" do
before do
allow(product).to receive(:variant_unit) { "weight" }
end
context "metric" do
it "for a unit value that should display in grams" do
allow(product).to receive(:variant_unit_scale) { 1.0 }
allow(variant).to receive(:unit_value) { 500 }
expect(subject.scale_for_unit_value).to eq([1.0, "g"])
end
it "for a unit value that should display in kg" do
allow(product).to receive(:variant_unit_scale) { 1.0 }
allow(variant).to receive(:unit_value) { 1500 }
expect(subject.scale_for_unit_value).to eq([1000.0, "kg"])
end
end
end
context "volume" do
it "for a unit value that should display in L" do
allow(product).to receive(:variant_unit) { "volume" }
allow(product).to receive(:variant_unit_scale) { 1.0 }
allow(variant).to receive(:unit_value) { 1500 }
expect(subject.scale_for_unit_value).to eq([1000, "kL"])
end
end
context "items" do
it "when scale is for items" do
allow(product).to receive(:variant_unit) { "items" }
allow(product).to receive(:variant_unit_scale) { nil }
allow(variant).to receive(:unit_value) { 4 }
expect(subject.scale_for_unit_value).to eq([nil, nil])
end
end
end
end