From 39fc0707c34cbf7785a776386caaf4a3827669da Mon Sep 17 00:00:00 2001 From: Andy Brett Date: Wed, 24 Feb 2021 21:26:14 -0800 Subject: [PATCH] provide unit price values in front end shop --- app/serializers/api/variant_serializer.rb | 4 +- .../variant_units/option_value_namer.rb | 35 +----- app/services/variant_units/unit_prices.rb | 37 ++++++ .../variant_units/weights_and_measures.rb | 60 ++++++++++ .../variant_units/unit_prices_spec.rb | 107 ++++++++++++++++++ .../weights_and_measures_spec.rb | 89 +++++++++++++++ 6 files changed, 296 insertions(+), 36 deletions(-) create mode 100644 app/services/variant_units/unit_prices.rb create mode 100644 app/services/variant_units/weights_and_measures.rb create mode 100644 spec/services/variant_units/unit_prices_spec.rb create mode 100644 spec/services/variant_units/weights_and_measures_spec.rb diff --git a/app/serializers/api/variant_serializer.rb b/app/serializers/api/variant_serializer.rb index 5c99fc1730..b701d709f0 100644 --- a/app/serializers/api/variant_serializer.rb +++ b/app/serializers/api/variant_serializer.rb @@ -41,10 +41,10 @@ class Api::VariantSerializer < ActiveModel::Serializer end def unit_price_price - (rand * 10).round(2) + price_with_fees / VariantUnits::UnitPrices.new(object).unit_price_denominator end def unit_price_unit - rand > 0.5 ? "item" : "kg" + VariantUnits::UnitPrices.new(object).unit_price_unit end end diff --git a/app/services/variant_units/option_value_namer.rb b/app/services/variant_units/option_value_namer.rb index 7cb8fc8547..934a873b78 100644 --- a/app/services/variant_units/option_value_namer.rb +++ b/app/services/variant_units/option_value_namer.rb @@ -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 + VariantUnits::WeightsAndMeasures.new(@variant).scale_for_unit_value end def pluralize(unit_name, count) diff --git a/app/services/variant_units/unit_prices.rb b/app/services/variant_units/unit_prices.rb new file mode 100644 index 0000000000..3672cb2830 --- /dev/null +++ b/app/services/variant_units/unit_prices.rb @@ -0,0 +1,37 @@ +# frozen_string_literal: true + +module VariantUnits + class UnitPrices + def initialize(variant) + @variant = variant + @product = variant.product + end + + def unit_price_denominator + return @variant.unit_value if @product&.variant_unit == "items" + + case unit_price_unit + when "lb" + @variant.unit_value / 453.6 + when "kg" + @variant.unit_value / 1000 + else # Liters + @variant.unit_value + end + end + + def unit_price_unit + return "lb" if VariantUnits::WeightsAndMeasures.new(@variant). + system_of_measurement == "imperial" + + case @product&.variant_unit + when "weight" + "kg" + when "volume" + "L" + else + @product.variant_unit_name.presence || "item" + end + end + end +end diff --git a/app/services/variant_units/weights_and_measures.rb b/app/services/variant_units/weights_and_measures.rb new file mode 100644 index 0000000000..37b2b17368 --- /dev/null +++ b/app/services/variant_units/weights_and_measures.rb @@ -0,0 +1,60 @@ +# frozen_string_literal: true + +module VariantUnits + 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_of_measurement) + return [nil, nil] unless largest_unit + + [largest_unit[0], largest_unit[1]["name"]] + end + + def system_of_measurement + 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 +end diff --git a/spec/services/variant_units/unit_prices_spec.rb b/spec/services/variant_units/unit_prices_spec.rb new file mode 100644 index 0000000000..116cfcdf12 --- /dev/null +++ b/spec/services/variant_units/unit_prices_spec.rb @@ -0,0 +1,107 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module VariantUnits + describe UnitPrices do + subject { UnitPrices.new(variant) } + let(:variant) { Spree::Variant.new } + let(:product) { double } + + before do + allow(variant).to receive(:product) { product } + end + + describe "#unit_price_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_price_unit).to eq("kg") + end + + it "returns L for volume" do + allow(product).to receive(:variant_unit) { "volume" } + expect(subject.unit_price_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_price_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_price_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_price_unit).to eq("bunch") + end + end + end + + describe "#unit_price_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.unit_price_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.unit_price_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.unit_price_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.unit_price_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.unit_price_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.unit_price_denominator).to eq(2) + end + end + end + end +end diff --git a/spec/services/variant_units/weights_and_measures_spec.rb b/spec/services/variant_units/weights_and_measures_spec.rb new file mode 100644 index 0000000000..f178aea09e --- /dev/null +++ b/spec/services/variant_units/weights_and_measures_spec.rb @@ -0,0 +1,89 @@ +# frozen_string_literal: true + +require 'spec_helper' + +module VariantUnits + describe WeightsAndMeasures do + subject { WeightsAndMeasures.new(variant) } + let(:variant) { Spree::Variant.new } + let(:product) { double } + + before do + allow(variant).to receive(:product) { product } + end + + describe "#system_of_measurement" 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_of_measurement).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_of_measurement).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_of_measurement).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_of_measurement).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 +end