diff --git a/app/queries/customers_with_balance_query.rb b/app/queries/customers_with_balance_query.rb index 47d0fabca9..25e9f533b6 100644 --- a/app/queries/customers_with_balance_query.rb +++ b/app/queries/customers_with_balance_query.rb @@ -12,7 +12,8 @@ class CustomersWithBalanceQuery joins(left_join_complete_orders). group("customers.id"). select("customers.*"). - select("#{outstanding_balance_sum} AS balance_value") + select("#{outstanding_balance_sum} AS balance_value"). + select("#{available_credit} AS credit_value") end private @@ -34,4 +35,21 @@ class CustomersWithBalanceQuery def outstanding_balance_sum "SUM(#{OutstandingBalanceQuery.new.statement})::float" end + + def available_credit + <<~SQL.squish + CASE WHEN EXISTS (#{available_credit_subquery}) THEN (#{available_credit_subquery})#{' '} + ELSE 0.00 END + SQL + end + + def available_credit_subquery + <<~SQL.squish + SELECT balance + FROM customer_account_transactions + WHERE customer_account_transactions.customer_id = customers.id + ORDER BY id desc + LIMIT 1 + SQL + end end diff --git a/app/serializers/api/admin/customer_with_balance_serializer.rb b/app/serializers/api/admin/customer_with_balance_serializer.rb index 6115d9efbe..49fe02d37d 100644 --- a/app/serializers/api/admin/customer_with_balance_serializer.rb +++ b/app/serializers/api/admin/customer_with_balance_serializer.rb @@ -7,14 +7,18 @@ module Api # columns to instance methods. This way, the `balance_value` alias on that class ends up being # `object.balance_value` here. class CustomerWithBalanceSerializer < CustomerSerializer - attributes :balance, :balance_status + attributes :balance, :balance_status, :available_credit - delegate :balance_value, to: :object + delegate :balance_value, :credit_value, to: :object def balance Spree::Money.new(balance_value, currency: CurrentConfig.get(:currency)).to_s end + def available_credit + Spree::Money.new(object.credit_value).to_s + end + def balance_status if balance_value.positive? "credit_owed" diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index a61fc61cf9..e6deb6eeea 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -73,6 +73,7 @@ %th.bill_address{ 'ng-show' => 'columns.bill_address.visible' }=t('admin.customers.index.bill_address') %th.ship_address{ 'ng-show' => 'columns.ship_address.visible' }=t('admin.customers.index.ship_address') %th.balance{ 'ng-show' => 'columns.balance.visible' }=t('admin.customers.index.balance') + %th.credit{ 'ng-show' => 'columns.credit.visible' }=t('admin.customers.index.credit') %tbody %tr.customer{ 'ng-repeat' => "customer in filteredCustomers = ( customers | filter:quickSearch | orderBy: sorting.predicate:sorting.reverse ) | limitTo:customerLimit track by customer.id", 'ng-class-even' => "'even'", 'ng-class-odd' => "'odd'", :id => "c_{{customer.id}}" } -# %td.bulk @@ -98,6 +99,8 @@ %td.balance.align-center{ 'ng-show' => 'columns.balance.visible'} %span.state.white-space-nowrap{ 'ng-class' => 'customer.balance_status', 'ng-bind' => 'displayBalanceStatus(customer)' } %span{ 'ng-bind' => '::customer.balance' } + %td.balance.align-center{ 'ng-show' => 'columns.credit.visible'} + %span{ 'ng-bind' => '::customer.available_credit' } %td.actions %a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" } diff --git a/config/locales/en.yml b/config/locales/en.yml index e1cba89aff..4ac54325ea 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -877,6 +877,7 @@ en: bill_address: "Billing Address" ship_address: "Shipping Address" balance: "Balance" + credit: "Available Credit" update_address_success: "Address updated successfully." update_address_error: "Sorry! Please input all of the required fields!" edit_bill_address: "Edit Billing Address" diff --git a/lib/open_food_network/column_preference_defaults.rb b/lib/open_food_network/column_preference_defaults.rb index ae651ec171..3c2300700d 100644 --- a/lib/open_food_network/column_preference_defaults.rb +++ b/lib/open_food_network/column_preference_defaults.rb @@ -36,7 +36,8 @@ module OpenFoodNetwork tags: { name: I18n.t("admin.tags"), visible: true }, bill_address: { name: I18n.t("#{node}.bill_address"), visible: true }, ship_address: { name: I18n.t("#{node}.ship_address"), visible: true }, - balance: { name: I18n.t("#{node}.balance"), visible: true } + balance: { name: I18n.t("#{node}.balance"), visible: true }, + credit: { name: I18n.t("#{node}.credit"), visible: true } } end diff --git a/spec/queries/customers_with_balance_query_spec.rb b/spec/queries/customers_with_balance_query_spec.rb index 9077a09a33..f87d878d3b 100644 --- a/spec/queries/customers_with_balance_query_spec.rb +++ b/spec/queries/customers_with_balance_query_spec.rb @@ -210,5 +210,32 @@ RSpec.describe CustomersWithBalanceQuery do expect(customer.balance_value).to eq(0) end end + + context "with customer payments" do + # TODO should not be needed, need to add this to seed somehow + let!(:payment_method) { + create( + :payment_method, + name: CustomerAccountTransaction::DEFAULT_PAYMENT_METHOD_NAME, + distributors: [customer.enterprise] + ) + } + + it 'returns the customer available credit' do + create(:customer_account_transaction, customer:, amount: 10.00) + create(:customer_account_transaction, customer:, amount: -2.00) + create(:customer_account_transaction, customer:, amount: 5.00) + + customer_result = result.first + expect(customer_result.credit_value).to eq(13.00) + end + end + + context "with no customer payments" do + it 'returns 0 for the customer available credit' do + customer_result = result.first + expect(customer_result.credit_value).to eq(0.00) + end + end end end diff --git a/spec/serializers/api/admin/customer_with_balance_serializer_spec.rb b/spec/serializers/api/admin/customer_with_balance_serializer_spec.rb index fe49d26d22..6f82bb0444 100644 --- a/spec/serializers/api/admin/customer_with_balance_serializer_spec.rb +++ b/spec/serializers/api/admin/customer_with_balance_serializer_spec.rb @@ -16,6 +16,19 @@ RSpec.describe Api::Admin::CustomerWithBalanceSerializer do end end + describe '#available_credit' do + let(:customer) { double(Customer, credit_value: 5.3) } + let(:money) { instance_double(Spree::Money, to_s: "$5.30") } + + before do + allow(Spree::Money).to receive(:new).with(5.3) { money } + end + + it 'returns the available_credit as a money amount' do + expect(serialized_customer.available_credit).to eq("$5.30") + end + end + describe '#balance_status' do context 'when the balance_value is positive' do let(:customer) { double(Customer, balance_value: 1) }