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 fefe30e5..9a1bcab4 100644 --- a/app/admin/petition.rb +++ b/app/admin/petition.rb @@ -11,6 +11,8 @@ end end + 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 new file mode 100644 index 00000000..7857e142 --- /dev/null +++ b/app/admin/transfer.rb @@ -0,0 +1,42 @@ +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 + + 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 "From - To" do |transfer| + accounts_from_movements(transfer, with_links: true).join(" #{glyph(:arrow_right)} ").html_safe + end + column :amount do |transfer| + seconds_to_hm(transfer.movements.first.amount.abs) + 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 + + 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 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