From de95e00f63fe3f99ac5eb60a1b88c355b3e23a32 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Francisco=20Jos=C3=A9=20Perej=C3=B3n=20Barrios?=
<70280187+franpb14@users.noreply.github.com>
Date: Sat, 30 Dec 2023 20:09:22 +0100
Subject: [PATCH 1/4] [Feature] Transfers added to admin & import from csv
(#723)
---
app/admin/transfer.rb | 40 ++++++++++++++++
app/services/transfer_importer.rb | 78 +++++++++++++++++++++++++++++++
2 files changed, 118 insertions(+)
create mode 100644 app/admin/transfer.rb
create mode 100644 app/services/transfer_importer.rb
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
From 580333c4f5fc13e2d141c9b22af16765d2f765ee Mon Sep 17 00:00:00 2001
From: Marc Anguera Insa
Date: Sat, 30 Dec 2023 20:10:29 +0100
Subject: [PATCH 2/4] [admin] refactor transfers section: avoid N+1 and DRY
---
app/admin/transfer.rb | 15 +++++++--------
1 file changed, 7 insertions(+), 8 deletions(-)
diff --git a/app/admin/transfer.rb b/app/admin/transfer.rb
index 86007874..95dd902f 100644
--- a/app/admin/transfer.rb
+++ b/app/admin/transfer.rb
@@ -1,4 +1,8 @@
ActiveAdmin.register Transfer do
+ includes :post, movements: { account: [:accountable, :organization] }
+
+ actions :index, :destroy
+
action_item :upload_csv, only: :index do
link_to I18n.t("active_admin.users.upload_from_csv"), action: "upload_csv"
end
@@ -18,16 +22,11 @@
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
+ column "From - To" do |transfer|
+ accounts_from_movements(transfer, with_links: true).join(" #{glyph(:arrow_right)} ").html_safe
end
column :amount do |transfer|
- transfer.movements.find_by('amount > 0').amount
+ seconds_to_hm(transfer.movements.first.amount.abs)
end
column :created_at do |transfer|
l transfer.created_at.to_date, format: :long
From 65f8b75bd18bba1ac899d5631af601ee3f378bf0 Mon Sep 17 00:00:00 2001
From: Marc Anguera Insa
Date: Sat, 30 Dec 2023 20:27:05 +0100
Subject: [PATCH 3/4] [admin] better filters for transfers and petitions
---
app/admin/petition.rb | 1 +
app/admin/transfer.rb | 4 ++++
2 files changed, 5 insertions(+)
diff --git a/app/admin/petition.rb b/app/admin/petition.rb
index fefe30e5..f1bd7716 100644
--- a/app/admin/petition.rb
+++ b/app/admin/petition.rb
@@ -11,6 +11,7 @@
end
end
+ filter :organization
filter :status, as: :select, collection: -> { Petition.statuses }
filter :created_at
end
diff --git a/app/admin/transfer.rb b/app/admin/transfer.rb
index 95dd902f..7075d574 100644
--- a/app/admin/transfer.rb
+++ b/app/admin/transfer.rb
@@ -36,4 +36,8 @@
end
actions
end
+
+ filter :post
+ filter :reason
+ filter :created_at
end
From 9e5c05f1fd4acebab36aa48e88730fc61eb36fff Mon Sep 17 00:00:00 2001
From: Marc Anguera Insa
Date: Sat, 30 Dec 2023 20:45:08 +0100
Subject: [PATCH 4/4] [admin] upgrade activeadmin gem + more filtering
adjustments
---
Gemfile | 2 +-
Gemfile.lock | 52 +++++++++++++++++++--------------------
app/admin/category.rb | 3 +++
app/admin/organization.rb | 2 ++
app/admin/petition.rb | 1 +
app/admin/post.rb | 1 +
app/admin/transfer.rb | 1 -
app/admin/user.rb | 1 +
8 files changed, 35 insertions(+), 28 deletions(-)
diff --git a/Gemfile b/Gemfile
index 386447c9..ff1ac20d 100644
--- a/Gemfile
+++ b/Gemfile
@@ -4,7 +4,7 @@ gem 'rails', '~> 6.1.1'
gem 'rails-i18n', '~> 7.0'
gem 'rdiscount', '~> 2.2.7'
gem 'rubyzip', '~> 2.3.0'
-gem 'activeadmin', '~> 2.9.0'
+gem 'activeadmin', '~> 2.14'
gem 'bootsnap', '~> 1.12.0', require: false
gem 'has_scope', '~> 0.7.2'
gem 'pundit', '~> 2.1.0'
diff --git a/Gemfile.lock b/Gemfile.lock
index 2fb28811..7aaf022b 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -44,15 +44,15 @@ GEM
activemodel (>= 5.2.0)
activestorage (>= 5.2.0)
activesupport (>= 5.2.0)
- activeadmin (2.9.0)
+ activeadmin (2.14.0)
arbre (~> 1.2, >= 1.2.1)
formtastic (>= 3.1, < 5.0)
formtastic_i18n (~> 0.4)
inherited_resources (~> 1.7)
jquery-rails (~> 4.2)
kaminari (~> 1.0, >= 1.2.1)
- railties (>= 5.2, < 6.2)
- ransack (~> 2.1, >= 2.1.1)
+ railties (>= 6.1, < 7.1)
+ ransack (>= 2.1.1, < 4)
activejob (6.1.7.6)
activesupport (= 6.1.7.6)
globalid (>= 0.3.6)
@@ -78,8 +78,8 @@ GEM
public_suffix (>= 2.0.2, < 6.0)
airbrussh (1.4.0)
sshkit (>= 1.6.1, != 1.7.0)
- arbre (1.4.0)
- activesupport (>= 3.0.0, < 6.2)
+ arbre (1.5.0)
+ activesupport (>= 3.0.0, < 7.1)
ruby2_keywords (>= 0.0.2, < 1.0)
ast (2.4.2)
autoprefixer-rails (10.4.13.0)
@@ -163,7 +163,7 @@ GEM
ffi (1.15.5)
formtastic (4.0.0)
actionpack (>= 5.2.0)
- formtastic_i18n (0.6.0)
+ formtastic_i18n (0.7.0)
fugit (1.9.0)
et-orbi (~> 1, >= 1.2.7)
raabro (~> 1.4)
@@ -182,10 +182,10 @@ GEM
image_processing (1.12.2)
mini_magick (>= 4.9.5, < 5)
ruby-vips (>= 2.0.17, < 3)
- inherited_resources (1.12.0)
- actionpack (>= 5.2, < 6.2)
+ inherited_resources (1.13.1)
+ actionpack (>= 5.2, < 7.1)
has_scope (~> 0.6)
- railties (>= 5.2, < 6.2)
+ railties (>= 5.2, < 7.1)
responders (>= 2, < 4)
jmespath (1.6.1)
jquery-rails (4.4.0)
@@ -195,18 +195,18 @@ GEM
json (2.6.3)
json_translate (4.0.1)
activerecord (>= 4.2.0)
- kaminari (1.2.1)
+ kaminari (1.2.2)
activesupport (>= 4.1.0)
- kaminari-actionview (= 1.2.1)
- kaminari-activerecord (= 1.2.1)
- kaminari-core (= 1.2.1)
- kaminari-actionview (1.2.1)
+ kaminari-actionview (= 1.2.2)
+ kaminari-activerecord (= 1.2.2)
+ kaminari-core (= 1.2.2)
+ kaminari-actionview (1.2.2)
actionview
- kaminari-core (= 1.2.1)
- kaminari-activerecord (1.2.1)
+ kaminari-core (= 1.2.2)
+ kaminari-activerecord (1.2.2)
activerecord
- kaminari-core (= 1.2.1)
- kaminari-core (1.2.1)
+ kaminari-core (= 1.2.2)
+ kaminari-core (1.2.2)
kgio (2.11.3)
launchy (2.5.0)
addressable (~> 2.7)
@@ -315,9 +315,9 @@ GEM
rainbow (3.0.0)
raindrops (0.19.1)
rake (13.1.0)
- ransack (2.4.2)
- activerecord (>= 5.2.4)
- activesupport (>= 5.2.4)
+ ransack (3.0.1)
+ activerecord (>= 6.0.4)
+ activesupport (>= 6.0.4)
i18n
rb-fsevent (0.10.4)
rb-inotify (0.10.1)
@@ -325,9 +325,9 @@ GEM
rdiscount (2.2.7)
redis (4.8.1)
regexp_parser (2.8.2)
- responders (3.0.1)
- actionpack (>= 5.0)
- railties (>= 5.0)
+ responders (3.1.1)
+ actionpack (>= 5.2)
+ railties (>= 5.2)
rest-client (2.1.0)
http-accept (>= 1.7.0, < 2.0)
http-cookie (>= 1.0.2, < 2.0)
@@ -370,7 +370,7 @@ GEM
ruby-progressbar (1.11.0)
ruby-vips (2.1.4)
ffi (~> 1.12)
- ruby2_keywords (0.0.4)
+ ruby2_keywords (0.0.5)
rubyzip (2.3.2)
sassc (2.4.0)
ffi (~> 1.9)
@@ -453,7 +453,7 @@ PLATFORMS
DEPENDENCIES
active_storage_validations (~> 1.1.3)
- activeadmin (~> 2.9.0)
+ activeadmin (~> 2.14)
aws-sdk-s3 (~> 1.94)
bootsnap (~> 1.12.0)
bootstrap-sass (~> 3.4)
diff --git a/app/admin/category.rb b/app/admin/category.rb
index aff17356..009b14c1 100644
--- a/app/admin/category.rb
+++ b/app/admin/category.rb
@@ -7,6 +7,9 @@
actions
end
+ filter :created_at
+ filter :updated_at
+
form do |f|
f.inputs do
f.input :name
diff --git a/app/admin/organization.rb b/app/admin/organization.rb
index c54442bc..62f85c35 100644
--- a/app/admin/organization.rb
+++ b/app/admin/organization.rb
@@ -69,6 +69,8 @@ def destroy
filter :phone
filter :city, as: :select, collection: -> { Organization.pluck(:city).uniq }
filter :neighborhood
+ filter :created_at
+ filter :updated_at
permit_params :name, :email, :web, :phone, :city, :neighborhood,
:address, :description, :public_opening_times, :logo
diff --git a/app/admin/petition.rb b/app/admin/petition.rb
index f1bd7716..9a1bcab4 100644
--- a/app/admin/petition.rb
+++ b/app/admin/petition.rb
@@ -14,4 +14,5 @@
filter :organization
filter :status, as: :select, collection: -> { Petition.statuses }
filter :created_at
+ filter :updated_at
end
diff --git a/app/admin/post.rb b/app/admin/post.rb
index 39ad8d60..c0d0e505 100644
--- a/app/admin/post.rb
+++ b/app/admin/post.rb
@@ -57,4 +57,5 @@
filter :is_group
filter :active
filter :created_at
+ filter :updated_at
end
diff --git a/app/admin/transfer.rb b/app/admin/transfer.rb
index 7075d574..7857e142 100644
--- a/app/admin/transfer.rb
+++ b/app/admin/transfer.rb
@@ -37,7 +37,6 @@
actions
end
- filter :post
filter :reason
filter :created_at
end
diff --git a/app/admin/user.rb b/app/admin/user.rb
index 032375bc..060f9e84 100644
--- a/app/admin/user.rb
+++ b/app/admin/user.rb
@@ -35,6 +35,7 @@
filter :phone
filter :postcode
filter :locale
+ filter :created_at
form do |f|
f.semantic_errors *f.object.errors.keys