mirror of
https://github.com/openfoodfoundation/openfoodnetwork
synced 2026-01-24 20:36:49 +00:00
Reuse outstanding balance statement across queries
Instead of relying on Spree::Order#outstanding_balance we make us of the result set `balance_value` computed column. So, we ask PostgreSQL to compute it instead of Ruby and then serialize it from that computed column. That's a bit faster to compute that way and let's reuse logic. We hide this new implementation under this features' toggle so it's only used when enabled. We want hit the old behaviour by default.
This commit is contained in:
@@ -17,9 +17,10 @@ module Spree
|
||||
@orders = @user.orders
|
||||
.where.not(Spree::Order.in_incomplete_state.where_values_hash)
|
||||
.select('spree_orders.*')
|
||||
.select(OutstandingBalance.new.query)
|
||||
.order('completed_at desc')
|
||||
|
||||
@orders = OutstandingBalance.new(@orders).query
|
||||
|
||||
customers = spree_current_user.customers
|
||||
@shops = Enterprise
|
||||
.where(id: @orders.pluck(:distributor_id).uniq | customers.pluck(:enterprise_id))
|
||||
|
||||
@@ -132,8 +132,8 @@ module Spree
|
||||
|
||||
# All the states an order can be in before completing the checkout
|
||||
PRIOR_TO_COMPLETION_STATES = %w(cart address delivery payment).freeze
|
||||
# All the states of a complete order but that shouldn't count towards the balance. Those that the
|
||||
# customer won't enjoy.
|
||||
# All the states of a complete order but that shouldn't count towards the balance. Those that
|
||||
# the customer won't enjoy.
|
||||
NON_FULFILLED_STATES = %w(canceled returned).freeze
|
||||
|
||||
scope :in_incomplete_state, -> { where(state: PRIOR_TO_COMPLETION_STATES) }
|
||||
|
||||
@@ -9,17 +9,30 @@
|
||||
#
|
||||
# See CompleteOrdersWithBalance or CustomersWithBalance as examples.
|
||||
class OutstandingBalance
|
||||
# The relation must be an ActiveRecord::Relation object with `spree_orders` in the SQL statement
|
||||
# FROM for #statement to work.
|
||||
def initialize(relation = nil)
|
||||
@relation = relation
|
||||
end
|
||||
|
||||
def query
|
||||
relation.select("#{statement} AS balance_value")
|
||||
end
|
||||
|
||||
# Arel doesn't support CASE statements until v7.1.0 so we'll have to wait with SQL literals
|
||||
# a little longer. See https://github.com/rails/arel/pull/400 for details.
|
||||
def statement
|
||||
<<-SQL.strip_heredoc
|
||||
CASE WHEN state IN #{non_fulfilled_states_group.to_sql} THEN payment_total
|
||||
WHEN state IS NOT NULL THEN payment_total - total
|
||||
WHEN state IS NOT NULL THEN payment_total - total
|
||||
ELSE 0 END
|
||||
AS balance_value
|
||||
SQL
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :relation
|
||||
|
||||
def non_fulfilled_states_group
|
||||
states = Spree::Order::NON_FULFILLED_STATES.map { |state| Arel::Nodes.build_quoted(state) }
|
||||
Arel::Nodes::Grouping.new(states)
|
||||
|
||||
@@ -7,6 +7,14 @@ module Api
|
||||
|
||||
has_many :payments, serializer: Api::PaymentSerializer
|
||||
|
||||
def outstanding_balance
|
||||
if OpenFoodNetwork::FeatureToggle.enabled?(:customer_balance, object.user)
|
||||
-object.balance_value
|
||||
else
|
||||
object.outstanding_balance
|
||||
end
|
||||
end
|
||||
|
||||
def payments
|
||||
object.payments.joins(:payment_method).completed
|
||||
end
|
||||
|
||||
@@ -10,23 +10,15 @@ class CustomersWithBalance
|
||||
joins(left_join_complete_orders).
|
||||
group("customers.id").
|
||||
select("customers.*").
|
||||
select(outstanding_balance)
|
||||
select(outstanding_balance_sum)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
attr_reader :enterprise
|
||||
|
||||
# Arel doesn't support CASE statements until v7.1.0 so we'll have to wait with SQL literals
|
||||
# a little longer. See https://github.com/rails/arel/pull/400 for details.
|
||||
def outstanding_balance
|
||||
<<-SQL.strip_heredoc
|
||||
SUM(
|
||||
CASE WHEN state IN #{non_fulfilled_states_group.to_sql} THEN payment_total
|
||||
WHEN state IS NOT NULL THEN payment_total - total
|
||||
ELSE 0 END
|
||||
) AS balance_value
|
||||
SQL
|
||||
def outstanding_balance_sum
|
||||
"SUM(#{OutstandingBalance.new.statement}) AS balance_value"
|
||||
end
|
||||
|
||||
# The resulting orders are in states that belong after the checkout. Only these can be considered
|
||||
@@ -42,20 +34,4 @@ class CustomersWithBalance
|
||||
states = Spree::Order::PRIOR_TO_COMPLETION_STATES.map { |state| Arel::Nodes.build_quoted(state) }
|
||||
Arel::Nodes::NotIn.new(Spree::Order.arel_table[:state], states)
|
||||
end
|
||||
|
||||
def non_fulfilled_states_group
|
||||
states_group = Spree::Order::NON_FULFILLED_STATES.map { |state| Arel::Nodes.build_quoted(state) }
|
||||
Arel::Nodes::Grouping.new(states_group)
|
||||
end
|
||||
|
||||
# All the states an order can be in before completing the checkout
|
||||
def prior_to_completion_states
|
||||
%w(cart address delivery payment)
|
||||
end
|
||||
|
||||
# All the states of a complete order but that shouldn't count towards the balance. Those that the
|
||||
# customer won't enjoy.
|
||||
def non_fulfilled_states
|
||||
%w(canceled returned)
|
||||
end
|
||||
end
|
||||
|
||||
@@ -23,4 +23,30 @@ describe Api::OrderSerializer do
|
||||
expect(serializer.to_json).to match completed_payment.amount.to_s
|
||||
expect(serializer.to_json).to_not match payment.amount.to_s
|
||||
end
|
||||
|
||||
describe '#outstanding_balance' do
|
||||
context 'when the customer_balance feature is enabled' do
|
||||
before do
|
||||
allow(OpenFoodNetwork::FeatureToggle)
|
||||
.to receive(:enabled?).with(:customer_balance, order.user) { true }
|
||||
|
||||
allow(order).to receive(:balance_value).and_return(-1.23)
|
||||
end
|
||||
|
||||
it "returns the object's balance_value from the users perspective" do
|
||||
expect(serializer.serializable_hash[:outstanding_balance]).to eq(1.23)
|
||||
end
|
||||
end
|
||||
|
||||
context 'when the customer_balance is not enabled' do
|
||||
before do
|
||||
allow(OpenFoodNetwork::FeatureToggle)
|
||||
.to receive(:enabled?).with(:customer_balance, order.user) { false }
|
||||
end
|
||||
|
||||
it 'calls #outstanding_balance on the object' do
|
||||
expect(serializer.serializable_hash[:outstanding_balance]).to eq(1.0)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
Reference in New Issue
Block a user