Add available credit on the admin customer page

This commit is contained in:
Gaetan Craig-Riou
2026-01-23 14:28:24 +11:00
parent c58a65a52b
commit 6aa4bf7a33
7 changed files with 71 additions and 4 deletions

View File

@@ -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

View File

@@ -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"

View File

@@ -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" }

View File

@@ -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"

View File

@@ -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

View File

@@ -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

View File

@@ -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) }