From 39fc0707c34cbf7785a776386caaf4a3827669da Mon Sep 17 00:00:00 2001 From: Andy Brett Date: Wed, 24 Feb 2021 21:26:14 -0800 Subject: [PATCH 01/10] 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 From 0afc2d281a1afed6ccf123719b4189a79f140f2a Mon Sep 17 00:00:00 2001 From: Andy Brett Date: Wed, 3 Mar 2021 08:12:08 -0800 Subject: [PATCH 02/10] shorten UnitPrices method names --- app/services/variant_units/unit_prices.rb | 7 ++--- .../variant_units/unit_prices_spec.rb | 26 +++++++++---------- 2 files changed, 17 insertions(+), 16 deletions(-) diff --git a/app/services/variant_units/unit_prices.rb b/app/services/variant_units/unit_prices.rb index 3672cb2830..3bd5023c52 100644 --- a/app/services/variant_units/unit_prices.rb +++ b/app/services/variant_units/unit_prices.rb @@ -7,10 +7,11 @@ module VariantUnits @product = variant.product end - def unit_price_denominator + def denominator + # catches any case where unit is not kg, lb, or L. return @variant.unit_value if @product&.variant_unit == "items" - case unit_price_unit + case unit when "lb" @variant.unit_value / 453.6 when "kg" @@ -20,7 +21,7 @@ module VariantUnits end end - def unit_price_unit + def unit return "lb" if VariantUnits::WeightsAndMeasures.new(@variant). system_of_measurement == "imperial" diff --git a/spec/services/variant_units/unit_prices_spec.rb b/spec/services/variant_units/unit_prices_spec.rb index 116cfcdf12..089116ae3c 100644 --- a/spec/services/variant_units/unit_prices_spec.rb +++ b/spec/services/variant_units/unit_prices_spec.rb @@ -12,7 +12,7 @@ module VariantUnits allow(variant).to receive(:product) { product } end - describe "#unit_price_unit" do + describe "#unit" do context "metric" do before do allow(product).to receive(:variant_unit_scale) { 1.0 } @@ -20,12 +20,12 @@ module VariantUnits it "returns kg for weight" do allow(product).to receive(:variant_unit) { "weight" } - expect(subject.unit_price_unit).to eq("kg") + expect(subject.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") + expect(subject.unit).to eq("L") end end @@ -33,7 +33,7 @@ module VariantUnits 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") + expect(subject.unit).to eq("lb") end end @@ -42,39 +42,39 @@ module VariantUnits 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") + 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_price_unit).to eq("bunch") + expect(subject.unit).to eq("bunch") end end end - describe "#unit_price_denominator" do + 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.unit_price_denominator).to eq(0.5) + 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.unit_price_denominator).to eq(2) + 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.unit_price_denominator).to eq(0.5) + expect(subject.denominator).to eq(0.5) end end @@ -83,7 +83,7 @@ module VariantUnits 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) + expect(subject.denominator).to eq(2) end end @@ -92,14 +92,14 @@ module VariantUnits 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) + 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.unit_price_denominator).to eq(2) + expect(subject.denominator).to eq(2) end end end From 89c734289289d1bdc766e207ca3695008b7aace1 Mon Sep 17 00:00:00 2001 From: Andy Brett Date: Wed, 3 Mar 2021 08:12:19 -0800 Subject: [PATCH 03/10] memoize UnitPrices object --- app/serializers/api/variant_serializer.rb | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/serializers/api/variant_serializer.rb b/app/serializers/api/variant_serializer.rb index b701d709f0..60ae1f441d 100644 --- a/app/serializers/api/variant_serializer.rb +++ b/app/serializers/api/variant_serializer.rb @@ -41,10 +41,16 @@ class Api::VariantSerializer < ActiveModel::Serializer end def unit_price_price - price_with_fees / VariantUnits::UnitPrices.new(object).unit_price_denominator + price_with_fees / unit_prices.denominator end def unit_price_unit - VariantUnits::UnitPrices.new(object).unit_price_unit + unit_prices.unit + end + + private + + def unit_prices + @unit_prices ||= VariantUnits::UnitPrices.new(object) end end From 330839012e277026c3cb579f61a7b926258d70ef Mon Sep 17 00:00:00 2001 From: Andy Brett Date: Wed, 3 Mar 2021 12:14:42 -0800 Subject: [PATCH 04/10] add translation --- app/services/variant_units/unit_prices.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/services/variant_units/unit_prices.rb b/app/services/variant_units/unit_prices.rb index 3bd5023c52..23ac3a079b 100644 --- a/app/services/variant_units/unit_prices.rb +++ b/app/services/variant_units/unit_prices.rb @@ -31,7 +31,7 @@ module VariantUnits when "volume" "L" else - @product.variant_unit_name.presence || "item" + @product.variant_unit_name.presence || I18n.t("item") end end end From 7319ef73451282b291f1acd81b4781d027a321b4 Mon Sep 17 00:00:00 2001 From: Andy Brett Date: Mon, 8 Mar 2021 09:23:23 -0800 Subject: [PATCH 05/10] use better method and class names --- app/serializers/api/variant_serializer.rb | 8 +- app/services/unit_prices.rb | 35 ++++++ .../variant_units/option_value_namer.rb | 2 +- app/services/variant_units/unit_prices.rb | 38 ------- .../variant_units/weights_and_measures.rb | 60 ---------- app/services/weights_and_measures.rb | 58 ++++++++++ spec/services/unit_prices_spec.rb | 105 +++++++++++++++++ .../variant_units/unit_prices_spec.rb | 107 ------------------ .../weights_and_measures_spec.rb | 89 --------------- spec/services/weights_and_measures_spec.rb | 87 ++++++++++++++ 10 files changed, 290 insertions(+), 299 deletions(-) create mode 100644 app/services/unit_prices.rb delete mode 100644 app/services/variant_units/unit_prices.rb delete mode 100644 app/services/variant_units/weights_and_measures.rb create mode 100644 app/services/weights_and_measures.rb create mode 100644 spec/services/unit_prices_spec.rb delete mode 100644 spec/services/variant_units/unit_prices_spec.rb delete mode 100644 spec/services/variant_units/weights_and_measures_spec.rb create mode 100644 spec/services/weights_and_measures_spec.rb diff --git a/app/serializers/api/variant_serializer.rb b/app/serializers/api/variant_serializer.rb index 60ae1f441d..ecbfc66dc7 100644 --- a/app/serializers/api/variant_serializer.rb +++ b/app/serializers/api/variant_serializer.rb @@ -41,16 +41,16 @@ class Api::VariantSerializer < ActiveModel::Serializer end def unit_price_price - price_with_fees / unit_prices.denominator + price_with_fees / unit_price.denominator end def unit_price_unit - unit_prices.unit + unit_price.unit end private - def unit_prices - @unit_prices ||= VariantUnits::UnitPrices.new(object) + def unit_price + @unit_price ||= UnitPrice.new(object) end end diff --git a/app/services/unit_prices.rb b/app/services/unit_prices.rb new file mode 100644 index 0000000000..0b28c973d5 --- /dev/null +++ b/app/services/unit_prices.rb @@ -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 diff --git a/app/services/variant_units/option_value_namer.rb b/app/services/variant_units/option_value_namer.rb index 934a873b78..9d95f58f2e 100644 --- a/app/services/variant_units/option_value_namer.rb +++ b/app/services/variant_units/option_value_namer.rb @@ -62,7 +62,7 @@ module VariantUnits end def scale_for_unit_value - VariantUnits::WeightsAndMeasures.new(@variant).scale_for_unit_value + 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 deleted file mode 100644 index 23ac3a079b..0000000000 --- a/app/services/variant_units/unit_prices.rb +++ /dev/null @@ -1,38 +0,0 @@ -# frozen_string_literal: true - -module VariantUnits - class UnitPrices - 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 VariantUnits::WeightsAndMeasures.new(@variant). - system_of_measurement == "imperial" - - case @product&.variant_unit - when "weight" - "kg" - when "volume" - "L" - else - @product.variant_unit_name.presence || I18n.t("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 deleted file mode 100644 index 37b2b17368..0000000000 --- a/app/services/variant_units/weights_and_measures.rb +++ /dev/null @@ -1,60 +0,0 @@ -# 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/app/services/weights_and_measures.rb b/app/services/weights_and_measures.rb new file mode 100644 index 0000000000..eaf66b3ec4 --- /dev/null +++ b/app/services/weights_and_measures.rb @@ -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 diff --git a/spec/services/unit_prices_spec.rb b/spec/services/unit_prices_spec.rb new file mode 100644 index 0000000000..2ea2d04b52 --- /dev/null +++ b/spec/services/unit_prices_spec.rb @@ -0,0 +1,105 @@ +# frozen_string_literal: true + +require 'spec_helper' + +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" 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 diff --git a/spec/services/variant_units/unit_prices_spec.rb b/spec/services/variant_units/unit_prices_spec.rb deleted file mode 100644 index 089116ae3c..0000000000 --- a/spec/services/variant_units/unit_prices_spec.rb +++ /dev/null @@ -1,107 +0,0 @@ -# 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" 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 -end diff --git a/spec/services/variant_units/weights_and_measures_spec.rb b/spec/services/variant_units/weights_and_measures_spec.rb deleted file mode 100644 index f178aea09e..0000000000 --- a/spec/services/variant_units/weights_and_measures_spec.rb +++ /dev/null @@ -1,89 +0,0 @@ -# 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 diff --git a/spec/services/weights_and_measures_spec.rb b/spec/services/weights_and_measures_spec.rb new file mode 100644 index 0000000000..616b6a6be8 --- /dev/null +++ b/spec/services/weights_and_measures_spec.rb @@ -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) { double } + + 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 From 6ebf45610daab3a62dc1b2e085e98fc34af7990f Mon Sep 17 00:00:00 2001 From: Andy Brett Date: Mon, 8 Mar 2021 09:24:39 -0800 Subject: [PATCH 06/10] use instance_double in specs --- spec/services/unit_prices_spec.rb | 2 +- spec/services/weights_and_measures_spec.rb | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/services/unit_prices_spec.rb b/spec/services/unit_prices_spec.rb index 2ea2d04b52..28b3ebfaba 100644 --- a/spec/services/unit_prices_spec.rb +++ b/spec/services/unit_prices_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' describe UnitPrices do subject { UnitPrices.new(variant) } let(:variant) { Spree::Variant.new } - let(:product) { double } + let(:product) { instance_double(Spree::Product) } before do allow(variant).to receive(:product) { product } diff --git a/spec/services/weights_and_measures_spec.rb b/spec/services/weights_and_measures_spec.rb index 616b6a6be8..435ac0f5e4 100644 --- a/spec/services/weights_and_measures_spec.rb +++ b/spec/services/weights_and_measures_spec.rb @@ -5,7 +5,7 @@ require 'spec_helper' describe WeightsAndMeasures do subject { WeightsAndMeasures.new(variant) } let(:variant) { Spree::Variant.new } - let(:product) { double } + let(:product) { instance_double(Spree::Product) } before do allow(variant).to receive(:product) { product } From d2828585eb15ea3382388983dbf865bb080b560f Mon Sep 17 00:00:00 2001 From: Andy Brett Date: Mon, 8 Mar 2021 09:24:59 -0800 Subject: [PATCH 07/10] fix typo in spec --- spec/services/unit_prices_spec.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/services/unit_prices_spec.rb b/spec/services/unit_prices_spec.rb index 28b3ebfaba..66a54ffed7 100644 --- a/spec/services/unit_prices_spec.rb +++ b/spec/services/unit_prices_spec.rb @@ -41,7 +41,7 @@ describe UnitPrices 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") + expect(subject.unit).to eq("Item") end it "returns the unit if a unit is specified" do From 907c0d3e8c1da8fcda35202954fb87cfb6bfbe11 Mon Sep 17 00:00:00 2001 From: Andy Brett Date: Thu, 18 Mar 2021 10:34:49 -0700 Subject: [PATCH 08/10] rename unit_price.rb file --- app/services/{unit_prices.rb => unit_price.rb} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename app/services/{unit_prices.rb => unit_price.rb} (100%) diff --git a/app/services/unit_prices.rb b/app/services/unit_price.rb similarity index 100% rename from app/services/unit_prices.rb rename to app/services/unit_price.rb From be60adbcb3d1e4f7429ad0b957fa6378ca0dd179 Mon Sep 17 00:00:00 2001 From: Andy Brett Date: Thu, 18 Mar 2021 14:39:59 -0700 Subject: [PATCH 09/10] update class name to singular in spec --- spec/services/unit_prices_spec.rb | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/services/unit_prices_spec.rb b/spec/services/unit_prices_spec.rb index 66a54ffed7..0e836f2c29 100644 --- a/spec/services/unit_prices_spec.rb +++ b/spec/services/unit_prices_spec.rb @@ -2,8 +2,8 @@ require 'spec_helper' -describe UnitPrices do - subject { UnitPrices.new(variant) } +describe UnitPrice do + subject { UnitPrice.new(variant) } let(:variant) { Spree::Variant.new } let(:product) { instance_double(Spree::Product) } From 24908616393614ce37fe1aabfbd9d121fed5e223 Mon Sep 17 00:00:00 2001 From: Andy Brett Date: Thu, 25 Mar 2021 09:59:13 -0700 Subject: [PATCH 10/10] show correct values in line_item.rb --- app/models/spree/line_item.rb | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/models/spree/line_item.rb b/app/models/spree/line_item.rb index df88535a2a..5813511577 100644 --- a/app/models/spree/line_item.rb +++ b/app/models/spree/line_item.rb @@ -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