diff --git a/app/models/spree/calculator/flat_percent_item_total_decorator.rb b/app/models/spree/calculator/flat_percent_item_total_decorator.rb new file mode 100644 index 0000000000..3df011fcaf --- /dev/null +++ b/app/models/spree/calculator/flat_percent_item_total_decorator.rb @@ -0,0 +1,18 @@ +Spree::Calculator::FlatPercentItemTotal.class_eval do + # Spree's calculator sums all amounts, and then calculates a percentage on them. + # In the cart, we display line item individual amounts rounded, so to have consistent + # calculations we do the same internally. Here, we round adjustments at the individual + # item level first, then multiply by the item quantity. + + + def compute(object) + line_items_for(object).sum do |li| + unless li.price.present? && li.quantity.present? + raise ArgumentError, "object must respond to #price and #quantity" + end + + value = (li.price * BigDecimal(self.preferred_flat_percent.to_s) / 100.0).round(2) + value * li.quantity + end + end +end diff --git a/spec/features/consumer/shopping/cart_spec.rb b/spec/features/consumer/shopping/cart_spec.rb index ab2b5e5a7c..ac34d9246b 100644 --- a/spec/features/consumer/shopping/cart_spec.rb +++ b/spec/features/consumer/shopping/cart_spec.rb @@ -13,16 +13,35 @@ feature "full-page cart", js: true do let!(:order_cycle) { create(:simple_order_cycle, suppliers: [supplier], distributors: [distributor], coordinator: create(:distributor_enterprise), variants: [product_tax.variants.first, product_fee.variants.first]) } let(:enterprise_fee) { create(:enterprise_fee, amount: 11.00, tax_category: product_tax.tax_category) } let(:product_tax) { create(:taxed_product, supplier: supplier, zone: zone, price: 110.00, tax_rate_amount: 0.1) } - let(:product_fee) { create(:simple_product, supplier: supplier, price: 0.86) } + let(:product_fee) { create(:simple_product, supplier: supplier, price: 0.86, on_hand: 100) } let(:order) { create(:order, order_cycle: order_cycle, distributor: distributor) } before do - add_enterprise_fee enterprise_fee set_order order end + describe "fees" do + let(:percentage_fee) { create(:enterprise_fee, calculator: Spree::Calculator::FlatPercentItemTotal.new(preferred_flat_percent: 20)) } + + before do + add_enterprise_fee percentage_fee + add_product_to_cart order, product_fee, quantity: 8 + visit spree.cart_path + end + + it "rounds fee calculations correctly" do + # $0.86 + 20% = $1.032 + # Fractional cents should be immediately rounded down and not carried through + expect(page).to have_selector '.cart-item-price', text: '$1.03' + expect(page).to have_selector '.cart-item-total', text: '$8.24' + expect(page).to have_selector '.order-total.item-total', text: '$8.24' + expect(page).to have_selector '.order-total.grand-total', text: '$8.24' + end + end + describe "tax" do before do + add_enterprise_fee enterprise_fee add_product_to_cart order, product_tax visit spree.cart_path end diff --git a/spec/models/spree/calculator/flat_percent_item_total_spec.rb b/spec/models/spree/calculator/flat_percent_item_total_spec.rb new file mode 100644 index 0000000000..b4b5e21d62 --- /dev/null +++ b/spec/models/spree/calculator/flat_percent_item_total_spec.rb @@ -0,0 +1,15 @@ +module Spree + describe Calculator::FlatPercentItemTotal do + let(:calculator) { Calculator::FlatPercentItemTotal.new preferred_flat_percent: 20 } + + it "calculates for a simple line item" do + line_item = LineItem.new price: 50, quantity: 2 + expect(calculator.compute(line_item)).to eq 20 + end + + it "rounds fractional cents before summing" do + line_item = LineItem.new price: 0.86, quantity: 8 + expect(calculator.compute(line_item)).to eq 1.36 + end + end +end diff --git a/spec/support/request/shop_workflow.rb b/spec/support/request/shop_workflow.rb index 5dc07961d8..3fc7ac53a6 100644 --- a/spec/support/request/shop_workflow.rb +++ b/spec/support/request/shop_workflow.rb @@ -16,9 +16,9 @@ module ShopWorkflow ApplicationController.any_instance.stub(:session).and_return({order_id: order.id, access_token: order.token}) end - def add_product_to_cart(order, product) + def add_product_to_cart(order, product, quantity: 1) populator = Spree::OrderPopulator.new(order, order.currency) - populator.populate(variants: {product.variants.first.id => 1}) + populator.populate(variants: {product.variants.first.id => quantity}) # Recalculate fee totals order.update_distribution_charge!