diff --git a/app/controllers/admin/customer_account_transaction_controller.rb b/app/controllers/admin/customer_account_transaction_controller.rb new file mode 100644 index 0000000000..799608e18d --- /dev/null +++ b/app/controllers/admin/customer_account_transaction_controller.rb @@ -0,0 +1,26 @@ +# frozen_string_literal: true + +module Admin + class CustomerAccountTransactionController < Admin::ResourceController + def index + @available_credit = @collection.first.balance + + respond_with do |format| + format.turbo_stream { + render :index + } + end + end + + # We are using an old version of CanCanCan so I could not get `accessible_by` to work properly, + # so we are doing our own authorization before calling 'accessible_by' + def collection + allowed_customers = OpenFoodNetwork::Permissions.new(spree_current_user) + .managed_enterprises.joins(:customers).select("customers.id").map(&:id) + raise CanCan::AccessDenied unless allowed_customers.include?(params[:customer_id].to_i) + + CustomerAccountTransaction.accessible_by(current_ability, action) + .where(customer_id: params[:customer_id]).order(id: :desc) + end + end +end diff --git a/app/models/spree/ability.rb b/app/models/spree/ability.rb index 458cee2b44..cc4aeb007a 100644 --- a/app/models/spree/ability.rb +++ b/app/models/spree/ability.rb @@ -61,7 +61,7 @@ module Spree add_manage_line_items_abilities user end add_relationship_management_abilities user if can_manage_relationships? user - add_customer_payment_abilities user if can_manage_enterprises? user + add_customer_account_transaction_abilities user if can_manage_enterprises? user end # New users have no enterprises. @@ -459,8 +459,8 @@ module Spree end end - def add_customer_payment_abilities(_user) - can [:create], CustomerAccountTransaction + def add_customer_account_transaction_abilities(_user) + can [:admin, :create, :index], CustomerAccountTransaction end 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 49fe02d37d..550059c208 100644 --- a/app/serializers/api/admin/customer_with_balance_serializer.rb +++ b/app/serializers/api/admin/customer_with_balance_serializer.rb @@ -7,7 +7,7 @@ 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, :available_credit + attributes :balance, :balance_status, :available_credit, :available_credit_url delegate :balance_value, :credit_value, to: :object @@ -15,10 +15,6 @@ module Api 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" @@ -28,6 +24,14 @@ module Api "" end end + + def available_credit + Spree::Money.new(object.credit_value).to_s + end + + def available_credit_url + admin_customer_customer_account_transaction_index_path(object.id) + end end end end diff --git a/app/views/admin/customer_account_transaction/index.turbo_stream.haml b/app/views/admin/customer_account_transaction/index.turbo_stream.haml new file mode 100644 index 0000000000..124a113690 --- /dev/null +++ b/app/views/admin/customer_account_transaction/index.turbo_stream.haml @@ -0,0 +1,31 @@ += turbo_stream.update "customer-account-transactions-modal-container" do + = render ModalComponent.new(id: "customer-account-transactions-modal", instant: true, modal_class: "big") do + %h3 + = t(".available_credit", available_credit: Spree::Money.new(@available_credit)) + %table.index + %thead + %tr + %th.transaction-date + = t(".transaction_date") + %th.description + = t(".description") + %th.payment-method + = t(".payment_method") + %th.amount + = t(".amount") + %th.running-balance + = t(".running_balance") + + %tbody + - @collection.each do |transaction| + %tr.transaction + %td.transaction-date + = transaction.updated_at.strftime("%Y-%m-%d") + %td.description + = transaction.description + %td.payment-method + = t(transaction.payment_method.name.to_s) + %td.amount + = Spree::Money.new(transaction.amount) + %td.running_balance + = Spree::Money.new(transaction.balance) diff --git a/app/views/admin/customers/index.html.haml b/app/views/admin/customers/index.html.haml index e6deb6eeea..df4a0ea617 100644 --- a/app/views/admin/customers/index.html.haml +++ b/app/views/admin/customers/index.html.haml @@ -99,11 +99,14 @@ %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.balance.align-center{ 'ng-show' => 'columns.credit.visible', "data-turbo": true} + %a{ "ng-href": "{{customer.available_credit_url}}", "data-turbo-stream": "" } + %span{ 'ng-bind' => '::customer.available_credit' } %td.actions %a{ 'ng-click' => "deleteCustomer(customer)", :class => "delete-customer icon-trash no-text" } .text-center{ "ng-show": "filteredCustomers.length > customerLimit" } %input{ type: 'button', value: t(:show_more), "ng-click": 'customerLimit = customerLimit + 20' } %input{ type: 'button', value: t(:show_all_with_more, num: '{{ filteredCustomers.length - customerLimit }}'), "ng-click": 'customerLimit = filteredCustomers.length' } + + #customer-account-transactions-modal-container diff --git a/config/locales/en.yml b/config/locales/en.yml index 4ac54325ea..add3c89573 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -894,6 +894,14 @@ en: balance_due: "Balance Due" destroy: has_associated_subscriptions: "Delete failed: This customer has active subscriptions. Cancel them first." + customer_account_transaction: + index: + available_credit: "Available credit: %{available_credit}" + transaction_date: Transaction Date + description: Description + payment_method: Payment method + amount: Amount + running_balance: Running balance column_preferences: bulk_update: success: "Column preferences saved" diff --git a/config/routes/admin.rb b/config/routes/admin.rb index 081aacfc35..e36ae10545 100644 --- a/config/routes/admin.rb +++ b/config/routes/admin.rb @@ -91,7 +91,9 @@ Openfoodnetwork::Application.routes.draw do resources :inventory_items, only: [:create, :update] - resources :customers, only: [:index, :create, :update, :destroy, :show] + resources :customers, only: [:index, :create, :update, :destroy, :show] do + resources :customer_account_transaction, only: [:index] + end resources :tag_rules, only: [] do get :map_by_tag, on: :collection, format: :json diff --git a/spec/requests/admin/customer_account_transaction_spec.rb b/spec/requests/admin/customer_account_transaction_spec.rb new file mode 100644 index 0000000000..5a3211f487 --- /dev/null +++ b/spec/requests/admin/customer_account_transaction_spec.rb @@ -0,0 +1,44 @@ +# frozen_string_literal: true + +require 'spec_helper' + +RSpec.describe Admin::CustomerAccountTransactionController do + describe "GET /index" do + let(:enterprise_user) { create(:user, enterprises: [enterprise]) } + let(:enterprise) { create(:enterprise) } + let(:customer) { create(:customer, enterprise:) } + let!(:payment_method) { + create( + :payment_method, + name: CustomerAccountTransaction::DEFAULT_PAYMENT_METHOD_NAME, + distributors: [enterprise] + ) + } + + before do + login_as enterprise_user + end + + it "returns a list of customer transactions" do + customer_account_transaction = create(:customer_account_transaction, customer:) + + get admin_customer_customer_account_transaction_index_path(customer), + params: { format: :turbo_stream } + + expect(response).to render_template("admin/customer_account_transaction/index") + end + + context "with a non authorized customer" do + let(:customer) { create(:customer) } + + it "returns unauthorized" do + customer_account_transaction = create(:customer_account_transaction, customer:) + + get admin_customer_customer_account_transaction_index_path(customer), + params: { format: :turbo_stream } + + expect(response).to redirect_to(unauthorized_path) + end + end + end +end