diff --git a/app/admin/transfer.rb b/app/admin/transfer.rb new file mode 100644 index 00000000..86007874 --- /dev/null +++ b/app/admin/transfer.rb @@ -0,0 +1,40 @@ +ActiveAdmin.register Transfer do + action_item :upload_csv, only: :index do + link_to I18n.t("active_admin.users.upload_from_csv"), action: "upload_csv" + end + + collection_action :upload_csv do + render "admin/csv/upload_csv" + end + + collection_action :import_csv, method: :post do + errors = TransferImporter.call(params[:dump][:organization_id], params[:dump][:file]) + flash[:error] = errors.join("
").html_safe if errors.present? + + redirect_to action: :index + end + + index do + id_column + column :post + column :reason + column :source do |transfer| + acc = transfer.movements.find_by('amount < 0').account.accountable + acc.class.name == "Member" ? acc.user : acc + end + column :destination do |transfer| + acc = transfer.movements.find_by('amount > 0').account.accountable + acc.class.name == "Member" ? acc.user : acc + end + column :amount do |transfer| + transfer.movements.find_by('amount > 0').amount + end + column :created_at do |transfer| + l transfer.created_at.to_date, format: :long + end + column :organization do |transfer| + transfer.movements.first.account.organization + end + actions + end +end diff --git a/app/services/transfer_importer.rb b/app/services/transfer_importer.rb new file mode 100644 index 00000000..93acbc13 --- /dev/null +++ b/app/services/transfer_importer.rb @@ -0,0 +1,78 @@ +# Used in the Admin section to import transfers +# to a specific organization, from a CSV file. + +require "csv" + +class TransferImporter + Row = Struct.new( + :source_id, + :source_type, + :destination_id, + :destination_type, + :amount, + :created_at, + :reason, + :post_id + ) do + def transfer_from_row(organization_id, errors) + source = find_account(source_id, source_type, organization_id, errors, 'source') + destination = find_account(destination_id, destination_type, organization_id, errors, 'destination') + return unless source && destination + + Transfer.new( + source: source, + destination: destination, + amount: amount, + created_at: created_at, + reason: reason, + post_id: post_id, + ) + end + + private + + def find_account(id, type, organization_id, errors, direction) + acc = if type.downcase == 'organization' + Organization.find(organization_id).account + else + Member.find_by(member_uid: id, organization_id: organization_id)&.account + end + + unless acc + errors.push(account_id: id, errors: "#{direction}_id #{id} not found in organization #{organization_id}") + return false + end + acc + end + end + + class << self + def call(organization_id, csv_data) + data = csv_data.read + errors = [] + + CSV.parse(data, headers: false) do |data_row| + row = Row.new( + data_row[0], + data_row[1], + data_row[2], + data_row[3], + data_row[4], + data_row[5], + data_row[6], + data_row[7] + ) + process_row(row, organization_id, errors) + end + + errors + end + + def process_row(row, organization_id, errors) + transfer = row.transfer_from_row(organization_id, errors) + return if !transfer || transfer.save + + errors.push(account_id: row.source_id, errors: transfer.errors.full_messages) + end + end +end