diff --git a/Gemfile b/Gemfile
index 0317203bb..173ad6a3b 100644
--- a/Gemfile
+++ b/Gemfile
@@ -5,6 +5,7 @@ ruby File.read('.ruby-version').strip
gem 'rails', '~> 6.1.1'
gem 'rails-i18n', '~> 6.0.0'
gem 'rdiscount', '~> 2.2.0.1'
+gem 'rubyzip', '~> 2.3.0'
gem 'activeadmin', '~> 2.9.0'
gem 'bootsnap', '~> 1.7.3', require: false
gem 'has_scope', '~> 0.7.2'
diff --git a/Gemfile.lock b/Gemfile.lock
index 62465c48e..d9eb8b504 100644
--- a/Gemfile.lock
+++ b/Gemfile.lock
@@ -442,6 +442,7 @@ DEPENDENCIES
rspec-rails (~> 4.0.0)
rubocop (~> 1.6)
rubocop-rails (~> 2.9)
+ rubyzip (~> 2.3.0)
sassc-rails (~> 2.1.2)
select2-rails (~> 4.0.13)
selenium-webdriver (~> 3.142)
diff --git a/app/admin/post.rb b/app/admin/post.rb
index 976788ef9..5aef7fe3c 100644
--- a/app/admin/post.rb
+++ b/app/admin/post.rb
@@ -2,6 +2,7 @@
index do
id_column
column :class
+ column :is_group
column :title
column :created_at do |post|
l post.created_at.to_date, format: :long
@@ -19,10 +20,11 @@
f.input :type, as: :radio, collection: %w[Offer Inquiry]
f.input :title
f.input :organization
- f.input :user, hint: "* should be member of the selected organization"
+ f.input :user, hint: "Should be member of the selected organization"
f.input :category
f.input :description
- f.input :tag_list
+ f.input :tag_list, hint: "Accepts comma separated values"
+ f.input :is_group
f.input :active
end
f.actions
@@ -36,6 +38,7 @@
filter :organization
filter :user
filter :category
+ filter :is_group
filter :active
filter :created_at
end
diff --git a/app/admin/user.rb b/app/admin/user.rb
index 8d273e89e..7de9e123b 100644
--- a/app/admin/user.rb
+++ b/app/admin/user.rb
@@ -29,12 +29,14 @@
filter :organizations
filter :email
filter :username
+ filter :phone
form do |f|
f.semantic_errors *f.object.errors.keys
f.inputs do
f.input :username
f.input :email
+ f.input :phone
f.input :gender, as: :select, collection: User::GENDERS
f.input :identity_document
end
diff --git a/app/assets/stylesheets/application.scss b/app/assets/stylesheets/application.scss
index 8df55ae59..b6047b93d 100644
--- a/app/assets/stylesheets/application.scss
+++ b/app/assets/stylesheets/application.scss
@@ -221,7 +221,7 @@ html {
}
.row.exports {
- padding: 10px;
+ padding: 20px 0;
}
.table-responsive {
diff --git a/app/controllers/organizations_controller.rb b/app/controllers/organizations_controller.rb
index 0c747804a..42eae556c 100644
--- a/app/controllers/organizations_controller.rb
+++ b/app/controllers/organizations_controller.rb
@@ -2,7 +2,9 @@ class OrganizationsController < ApplicationController
before_action :load_resource, only: [:show, :edit, :update, :set_current]
def index
- @organizations = Organization.all.page(params[:page]).per(25)
+ organizations = Organization.all
+ organizations = organizations.search_by_query(params[:q]) if params[:q].present?
+ @organizations = organizations.page(params[:page]).per(25)
end
def show
diff --git a/app/controllers/reports_controller.rb b/app/controllers/reports_controller.rb
index b757574e3..5b2b271fa 100644
--- a/app/controllers/reports_controller.rb
+++ b/app/controllers/reports_controller.rb
@@ -1,48 +1,98 @@
+require 'zip'
+
class ReportsController < ApplicationController
before_action :authenticate_user!
layout "report"
def user_list
- @members = current_organization.members.active.
- includes(:user).
- order("members.member_uid")
+ @members = report_collection("Member")
report_responder('Member', current_organization, @members)
end
def post_list
@post_type = (params[:type] || "offer").capitalize.constantize
- @posts = current_organization.posts.
- of_active_members.
- active.
- merge(@post_type.all).
- includes(:user, :category).
- group_by(&:category).
- to_a.
- sort_by { |category, _| category.try(:name).to_s }
+ @posts = report_collection(@post_type)
report_responder('Post', current_organization, @posts, @post_type)
end
def transfer_list
- @transfers = current_organization.all_transfers_with_accounts
+ @transfers = report_collection('Transfer')
report_responder('Transfer', current_organization, @transfers)
end
+ def download_all
+ filename = "#{current_organization.name.parameterize}_#{Date.today}.zip"
+ temp_file = Tempfile.new(filename)
+
+ begin
+ Zip::File.open(temp_file.path, Zip::File::CREATE) do |zipfile|
+ %w(Member Transfer Inquiry Offer).each do |report_class|
+ add_csv_to_zip(report_class, zipfile)
+ end
+ end
+ zip_data = File.read(temp_file.path)
+ send_data(zip_data, type: 'application/zip', disposition: 'attachment', filename: filename)
+ rescue Errno::ENOENT
+ redirect_to download_all_report_path
+ ensure
+ temp_file.close
+ temp_file.unlink
+ end
+ end
+
private
def report_responder(report_class, *args)
respond_to do |format|
format.html
- format.csv { download_report("Report::Csv::#{report_class}", *args) }
- format.pdf { download_report("Report::Pdf::#{report_class}", *args) }
+ format.csv { download_report("Csv::#{report_class}", *args) }
+ format.pdf { download_report("Pdf::#{report_class}", *args) }
end
end
def download_report(report_class, *args)
- report = report_class.constantize.new(*args)
+ report = get_report(report_class, *args)
send_data report.run, filename: report.name, type: report.mime_type
end
+
+ def get_report(report_class, *args)
+ "Report::#{report_class}".constantize.new(*args)
+ end
+
+ def report_collection(report_class)
+ case report_class.to_s
+ when 'Member'
+ current_organization.members.active.includes(:user).order('members.member_uid')
+ when 'Transfer'
+ current_organization.all_transfers_with_accounts
+ when 'Inquiry', 'Offer'
+ report_class = report_class.constantize if report_class.is_a?(String)
+
+ current_organization.posts.of_active_members.active.
+ merge(report_class.all).
+ includes(:user, :category).
+ group_by(&:category).
+ sort_by { |category, _| category.try(:name).to_s }
+ end
+ end
+
+ def add_csv_to_zip(report_class, zip)
+ collection = report_collection(report_class)
+
+ report = if report_class.in? %w(Inquiry Offer)
+ get_report("Csv::Post", current_organization, collection, report_class.constantize)
+ else
+ get_report("Csv::#{report_class}", current_organization, collection)
+ end
+
+ file = Tempfile.new
+ file.write(report.run)
+ file.rewind
+
+ zip.add("#{report_class.pluralize}_#{Date.today}.csv", file.path)
+ end
end
diff --git a/app/models/concerns/taggable.rb b/app/models/concerns/taggable.rb
index 0a499bbf1..61e3d6f78 100644
--- a/app/models/concerns/taggable.rb
+++ b/app/models/concerns/taggable.rb
@@ -13,8 +13,10 @@ def tag_list
tags && tags.join(", ")
end
- def tag_list=(tag_list)
- self.tags = tag_list.reject(&:empty?)
+ def tag_list=(new_tags)
+ new_tags = new_tags.split(",").map(&:strip) if new_tags.is_a?(String)
+
+ self.tags = new_tags.reject(&:empty?)
end
module ClassMethods
diff --git a/app/models/organization.rb b/app/models/organization.rb
index 1fc189017..dfc7a4b00 100644
--- a/app/models/organization.rb
+++ b/app/models/organization.rb
@@ -1,4 +1,15 @@
class Organization < ApplicationRecord
+ include PgSearch::Model
+
+ pg_search_scope :search_by_query,
+ against: %i[city neighborhood address name],
+ ignoring: :accents,
+ using: {
+ tsearch: {
+ prefix: true
+ }
+ }
+
has_many :members, dependent: :destroy
has_many :users, -> { order "members.created_at DESC" }, through: :members
has_many :all_accounts, class_name: "Account", inverse_of: :organization, dependent: :destroy
diff --git a/app/views/application/menus/_organization_reports_menu.html.erb b/app/views/application/menus/_organization_reports_menu.html.erb
index d87571675..9b590189b 100644
--- a/app/views/application/menus/_organization_reports_menu.html.erb
+++ b/app/views/application/menus/_organization_reports_menu.html.erb
@@ -29,5 +29,12 @@
<%= Transfer.model_name.human(count: :many) %>
<% end %>
+
+
+ <%= link_to download_all_report_path do %>
+ <%= glyph :download %>
+ <%= t 'reports.download_all' %>
+ <% end %>
+
diff --git a/app/views/application/menus/_visitor_menu.html.erb b/app/views/application/menus/_visitor_menu.html.erb
index 513a63e63..ee04b5d1f 100644
--- a/app/views/application/menus/_visitor_menu.html.erb
+++ b/app/views/application/menus/_visitor_menu.html.erb
@@ -1,2 +1,3 @@
<%= link_to t("layouts.application.login"), new_user_session_path %>
<%= link_to t("layouts.application.about"), page_path("about") %>
+<%= link_to t("layouts.application.bdtnear"), organizations_path %>
diff --git a/app/views/organizations/index.html.erb b/app/views/organizations/index.html.erb
index 20090cac9..1f3ccad94 100644
--- a/app/views/organizations/index.html.erb
+++ b/app/views/organizations/index.html.erb
@@ -2,6 +2,21 @@
<%= Organization.model_name.human(count: :many) %>
+
+
diff --git a/app/views/organizations/show.html.erb b/app/views/organizations/show.html.erb
index d14dca13d..fb5eaee7c 100644
--- a/app/views/organizations/show.html.erb
+++ b/app/views/organizations/show.html.erb
@@ -106,4 +106,8 @@
<% if current_user %>
<%= render "shared/movements" %>
+<% else %>
+
+ <%= t ".join_timebank" %>
+
<% end %>
diff --git a/config/locales/ca.yml b/config/locales/ca.yml
index 6d7aa4838..6087868ca 100644
--- a/config/locales/ca.yml
+++ b/config/locales/ca.yml
@@ -293,6 +293,7 @@ ca:
required_field: "* Camp obligatori"
save: Desar
search: Cercar
+ search_location: Cerca per localitat
show: Mostrar
source_destination: Origen/Destí
statistics: Estadístiques
@@ -309,6 +310,7 @@ ca:
layouts:
application:
about: Sobre TimeOverflow
+ bdtnear: Cerca Banc de Temps
edit_org: Modificar %{organization}
edit_profile: Modificar perfil
help: Ajuda
@@ -377,6 +379,7 @@ ca:
new: Nou banc
show:
contact_information: Informació de contacte
+ join_timebank: No dubtis en contactar amb el Banc de Temps per unir-te o per preguntar qualsevol dubte.
pages:
about:
app-mobile: Aplicació Mòbil
@@ -412,11 +415,8 @@ ca:
show:
info: Aquesta %{type} pertany a %{organization}.
reports:
- cat_with_users:
- title: Serveis ofertats
download: Descarregar
- user_list:
- title: Llistat d'usuaris
+ download_all: Descarregar tot
shared:
movements:
delete_reason: Esteu segur d'esborrar aquest comentari?
diff --git a/config/locales/en.yml b/config/locales/en.yml
index e7419738b..2a65f5c3e 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -293,6 +293,7 @@ en:
required_field: "* Required field"
save: Save
search: Search
+ search_location: Search by location
show: Show
source_destination: From/to
statistics: Statistics
@@ -309,6 +310,7 @@ en:
layouts:
application:
about: About TimeOverflow
+ bdtnear: Search Timebank
edit_org: Update %{organization}
edit_profile: Update my profile
help: Help
@@ -377,6 +379,7 @@ en:
new: New bank
show:
contact_information: Contact information
+ join_timebank: Don't hesitate to contact the time bank to join it or ask any questions.
pages:
about:
app-mobile: Mobile App
@@ -412,11 +415,8 @@ en:
show:
info: This %{type} belongs to %{organization}.
reports:
- cat_with_users:
- title: Offered Services
download: Download
- user_list:
- title: User List
+ download_all: Download all
shared:
movements:
delete_reason: Are you sure to delete this comment?
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 617a1a733..91d7203b4 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -293,6 +293,7 @@ es:
required_field: "* Campo obligatorio"
save: Guardar
search: Buscar
+ search_location: Buscar por localidad
show: Mostrar
source_destination: Origen/Destino
statistics: Estadísticas
@@ -309,6 +310,7 @@ es:
layouts:
application:
about: Sobre TimeOverflow
+ bdtnear: Busca Banco de Tiempo
edit_org: Modificar %{organization}
edit_profile: Modificar mi perfil
help: Ayuda
@@ -377,6 +379,7 @@ es:
new: Nuevo banco
show:
contact_information: Información de contacto
+ join_timebank: No dudes en contactar con el Banco de Tiempo para unirte o para resolver dudas.
pages:
about:
app-mobile: App Móvil
@@ -412,11 +415,8 @@ es:
show:
info: Esta %{type} pertenece a %{organization}.
reports:
- cat_with_users:
- title: Servicios ofrecidos
download: Descargar
- user_list:
- title: Listado usuarios
+ download_all: Descargar todo
shared:
movements:
delete_reason: "¿Está seguro de borrar este comentario?"
diff --git a/config/locales/eu.yml b/config/locales/eu.yml
index 9b8eea255..aff3fbb19 100644
--- a/config/locales/eu.yml
+++ b/config/locales/eu.yml
@@ -293,6 +293,7 @@ eu:
required_field: Derrigorrez bete beharrekoa
save: Gorde
search: Bilatu
+ search_location:
show: Erakutsi
source_destination: Jatorria/ helmuga
statistics: Estatistika
@@ -309,6 +310,7 @@ eu:
layouts:
application:
about: TimeOverflowri buruz
+ bdtnear:
edit_org: " %{organization} aldaketak egin"
edit_profile: Nire profila aldatu
help: Laguntza
@@ -377,6 +379,7 @@ eu:
new: Banku berria
show:
contact_information: kontaktuaren informazioa
+ join_timebank:
pages:
about:
app-mobile: Mugikorrerako Appa
@@ -412,11 +415,8 @@ eu:
show:
info: "%{type} hau %{organization}(e)ri dagokio."
reports:
- cat_with_users:
- title: Eskaintzen diren zerbitzuak
download: Jaitsi
- user_list:
- title: Erabiltzaile zerrenda
+ download_all:
shared:
movements:
delete_reason: Zihur zaude azalpen hau ezabatu nahi duzula?
diff --git a/config/locales/gl.yml b/config/locales/gl.yml
index 5316ba0a6..e24ed1004 100644
--- a/config/locales/gl.yml
+++ b/config/locales/gl.yml
@@ -293,6 +293,7 @@ gl:
required_field: "* Campo obrigatorio"
save: Gardar
search: Busca
+ search_location:
show: Amosar
source_destination: Dende/ata
statistics: Estatísticas
@@ -309,6 +310,7 @@ gl:
layouts:
application:
about: Sobre TimeOverflow
+ bdtnear:
edit_org: Actualización %{organization}
edit_profile: Actualiza o meu perfil
help: Axuda
@@ -377,6 +379,7 @@ gl:
new: Novo banco
show:
contact_information: Información de contacto
+ join_timebank:
pages:
about:
app-mobile: Aplicación móbil
@@ -412,11 +415,8 @@ gl:
show:
info: Este %{type} pertence a %{organization}.
reports:
- cat_with_users:
- title: Servizos ofrecidos
download: Descarga
- user_list:
- title: Lista de persoas usuarias
+ download_all:
shared:
movements:
delete_reason: Estás seguro/a de borrar este comentario?
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index 2cd7b0755..50d54dfa6 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -293,6 +293,7 @@ pt-BR:
required_field: "* Campo obrigatório"
save: Guardar
search: Pesquisar
+ search_location:
show: Mostrar
source_destination: Origem/Destino
statistics: Estatísticas
@@ -309,6 +310,7 @@ pt-BR:
layouts:
application:
about: Sobre TimeOverflow
+ bdtnear:
edit_org: Modificar %{organization}
edit_profile: Modificar meu perfil
help: Ajuda
@@ -377,6 +379,7 @@ pt-BR:
new: Novo banco
show:
contact_information: Informação de contato
+ join_timebank:
pages:
about:
app-mobile: Aplicativo móvel
@@ -412,11 +415,8 @@ pt-BR:
show:
info: Este %{type} pertence a %{organization}.
reports:
- cat_with_users:
- title: Serviços oferecidos
download: Baixar
- user_list:
- title: Lista de usuários
+ download_all:
shared:
movements:
delete_reason: Tem certeza de que quer apagar este comentário?
diff --git a/config/routes.rb b/config/routes.rb
index cc8fd4c7e..a88d0ee40 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -64,6 +64,7 @@
get "offer_list" => :post_list, type: "offer"
get "inquiry_list" => :post_list, type: "inquiry"
get "transfer_list"
+ get "download_all"
end
end
diff --git a/spec/controllers/organizations_controller_spec.rb b/spec/controllers/organizations_controller_spec.rb
index e0ae5a717..20d71361f 100644
--- a/spec/controllers/organizations_controller_spec.rb
+++ b/spec/controllers/organizations_controller_spec.rb
@@ -2,12 +2,64 @@
let!(:organization) { Fabricate(:organization) }
let(:member) { Fabricate(:member, organization: organization) }
let(:user) { member.user }
+ let!(:second_organization) { Fabricate(:organization) }
describe 'GET #index' do
- it 'populates and array of organizations' do
- get :index
+ context 'without parameters' do
+ it 'populates and array of organizations' do
+ get :index
- expect(assigns(:organizations)).to eq([organization])
+ expect(assigns(:organizations)).to eq([organization, second_organization])
+ end
+ end
+
+ context 'a search is made' do
+ before do
+ second_organization.name = "Banco del tiempo Doe"
+ second_organization.city = "Sevilla"
+ second_organization.address = "Calle gloria"
+ second_organization.neighborhood = "La paz"
+ second_organization.save!
+
+ organization.neighborhood = "La paz"
+ organization.save!
+ end
+
+ it 'populates an array of organizations searching by city' do
+ get :index, params: { q: 'Sevilla' }
+
+ expect(assigns(:organizations)).to eq([second_organization])
+ end
+
+ it 'populates an array of organizations searching by name' do
+ get :index, params: { q: 'Doe' }
+
+ expect(assigns(:organizations)).to eq([second_organization])
+ end
+
+ it 'populates an array of organizations searching by address' do
+ get :index, params: { q: 'gloria' }
+
+ expect(assigns(:organizations)).to eq([second_organization])
+ end
+
+ it 'populates an array of organizations searching by neighborhood' do
+ get :index, params: { q: 'Paz' }
+
+ expect(assigns(:organizations)).to eq([organization, second_organization])
+ end
+
+ it 'allows to search by partial word' do
+ get :index, params: { q: 'Sev' }
+
+ expect(assigns(:organizations)).to eq([second_organization])
+ end
+
+ it 'populates an array of organizations ignoring accents' do
+ get :index, params: { q: 'Sevillá' }
+
+ expect(assigns(:organizations)).to eq([second_organization])
+ end
end
end
diff --git a/spec/controllers/reports_controller_spec.rb b/spec/controllers/reports_controller_spec.rb
index fc6a1688d..369402369 100644
--- a/spec/controllers/reports_controller_spec.rb
+++ b/spec/controllers/reports_controller_spec.rb
@@ -89,5 +89,24 @@
expect(response.media_type).to eq("application/pdf")
end
end
+
+ describe 'GET #download_all' do
+ it 'downloads a zip' do
+ get :download_all
+
+ expect(response.media_type).to eq('application/zip')
+ expect(response.body).to include('Inquiries')
+ expect(response.body).to include('Offers')
+ expect(response.body).to include('Members')
+ expect(response.body).to include('Transfers')
+ end
+
+ it 'redirects to download_all_report_path (retry) if zip is not ready' do
+ allow(subject).to receive(:add_csv_to_zip).and_raise(Errno::ENOENT)
+ get :download_all
+
+ expect(response).to redirect_to(download_all_report_path)
+ end
+ end
end
end
diff --git a/spec/fabricators/member_fabricator.rb b/spec/fabricators/member_fabricator.rb
index 2ccb0e8e1..82e63d64b 100644
--- a/spec/fabricators/member_fabricator.rb
+++ b/spec/fabricators/member_fabricator.rb
@@ -1,8 +1,6 @@
Fabricator(:member) do
-
user { Fabricate(:user) }
organization { Fabricate(:organization) }
manager false
active true
-
end
diff --git a/spec/fabricators/post_fabricator.rb b/spec/fabricators/post_fabricator.rb
index 919bd56b4..c5871867c 100644
--- a/spec/fabricators/post_fabricator.rb
+++ b/spec/fabricators/post_fabricator.rb
@@ -1,15 +1,12 @@
Fabricator(:post) do
-
title { Faker::Lorem.sentence }
user { Fabricate(:user) }
description { Faker::Lorem.paragraph }
category { Fabricate(:category) }
active { true }
-
end
Fabricator(:inquiry) do
-
type "Inquiry"
title { Faker::Lorem.sentence }
@@ -21,7 +18,6 @@
end
Fabricator(:offer) do
-
type "Offer"
title { Faker::Lorem.sentence }
@@ -29,5 +25,4 @@
description { Faker::Lorem.paragraph }
category { Fabricate(:category) }
active { true }
-
end
diff --git a/spec/models/taggable_spec.rb b/spec/models/taggable_spec.rb
index 80f420051..542c1a3dd 100644
--- a/spec/models/taggable_spec.rb
+++ b/spec/models/taggable_spec.rb
@@ -1,6 +1,7 @@
RSpec.describe Taggable do
let(:organization) { Fabricate(:organization) }
-
+ let(:tags) { %w(foo bar baz) }
+ let(:more_tags) { %w(foo baz qux) }
let!(:offer) do
Fabricate(
:offer,
@@ -17,9 +18,6 @@
end
context "class methods and scopes" do
- let(:tags) { %w(foo bar baz) }
- let(:more_tags) { %w(foo baz qux) }
-
it "tagged_with" do
expect(Offer.tagged_with("bar")).to eq [offer]
end
@@ -33,18 +31,28 @@
expect(Offer.find_like_tag("Foo")).to eq ["foo"]
expect(Offer.find_like_tag("none")).to eq []
end
- end
- describe '.alphabetical_grouped_tags' do
- let(:tags) { %w(foo bar baz Boo) }
- let(:more_tags) { %w(foo baz qux) }
+ describe '.alphabetical_grouped_tags' do
+ let(:tags) { %w(foo bar baz Boo) }
+ let(:more_tags) { %w(foo baz qux) }
- it 'sorts them by alphabetical order case insensitive' do
- expect(Offer.alphabetical_grouped_tags).to eq({
- 'B' => [['bar', 1], ['baz', 2], ['Boo', 1]],
- 'F' => [['foo', 2]],
- 'Q' => [['qux', 1]]
- })
+ it 'sorts them by alphabetical order case insensitive' do
+ expect(Offer.alphabetical_grouped_tags).to eq({
+ 'B' => [['bar', 1], ['baz', 2], ['Boo', 1]],
+ 'F' => [['foo', 2]],
+ 'Q' => [['qux', 1]]
+ })
+ end
end
end
+
+ it "#tag_list= writter accepts string and array" do
+ offer = Offer.new
+
+ offer.tag_list = ["a", "b"]
+ expect(offer.tag_list).to eq "a, b"
+
+ offer.tag_list = "c, d"
+ expect(offer.tag_list).to eq "c, d"
+ end
end