]
+ def transfers
+ raise NotImplementedError
+ end
+
+ def build_transfer(source:, destination:)
+ ::Transfer.new(transfer_params_for(
+ source: source,
+ destination: destination
+ ))
+ end
+
+ def transfer_params_for(source:, destination:)
+ transfer_params.merge(source: source, destination: destination)
+ end
+ end
+ end
+end
diff --git a/app/services/operations/transfers/many_to_one.rb b/app/services/operations/transfers/many_to_one.rb
new file mode 100644
index 000000000..871aa414d
--- /dev/null
+++ b/app/services/operations/transfers/many_to_one.rb
@@ -0,0 +1,24 @@
+module Operations
+ module Transfers
+ class ManyToOne < Base
+ def transfers
+ sources.map do |source|
+ build_transfer(
+ source: source,
+ destination: destination
+ )
+ end
+ end
+
+ private
+
+ def sources
+ from
+ end
+
+ def destination
+ to.first
+ end
+ end
+ end
+end
diff --git a/app/services/operations/transfers/one_to_many.rb b/app/services/operations/transfers/one_to_many.rb
new file mode 100644
index 000000000..f24f4e9ae
--- /dev/null
+++ b/app/services/operations/transfers/one_to_many.rb
@@ -0,0 +1,25 @@
+module Operations
+ module Transfers
+ class OneToMany < Base
+ def transfers
+ destinations.map do |destination|
+ build_transfer(
+ source: source,
+ destination: destination
+ )
+ end
+ end
+
+ private
+
+ def source
+ from.first
+ end
+
+ def destinations
+ to
+ end
+ end
+ end
+end
+
diff --git a/app/services/operations/transfers/one_to_one.rb b/app/services/operations/transfers/one_to_one.rb
new file mode 100644
index 000000000..de9ae5636
--- /dev/null
+++ b/app/services/operations/transfers/one_to_one.rb
@@ -0,0 +1,22 @@
+module Operations
+ module Transfers
+ class OneToOne < Base
+ def transfers
+ [build_transfer(
+ source: source,
+ destination: destination
+ )]
+ end
+
+ private
+
+ def source
+ from.first
+ end
+
+ def destination
+ to.first
+ end
+ end
+ end
+end
diff --git a/app/views/application/menus/_organization_listings_menu.html.erb b/app/views/application/menus/_organization_listings_menu.html.erb
index f44a7043f..62f1dd877 100644
--- a/app/views/application/menus/_organization_listings_menu.html.erb
+++ b/app/views/application/menus/_organization_listings_menu.html.erb
@@ -31,5 +31,14 @@
<%= t "application.navbar.inquiry_public_link" %>
<% end %>
+
+
+ <%= link_to multi_transfers_step_path(step: 1) do %>
+ <%= glyph :transfer %>
+
+ <%= t('multi_transfers.multi_transfers') %>
+
+ <% end %>
+
diff --git a/app/views/multi_transfers/create.html.erb b/app/views/multi_transfers/create.html.erb
new file mode 100644
index 000000000..3c4182b7b
--- /dev/null
+++ b/app/views/multi_transfers/create.html.erb
@@ -0,0 +1,3 @@
+
+ <%= t('multi_transfers.success') %>
+
diff --git a/app/views/multi_transfers/step.html.erb b/app/views/multi_transfers/step.html.erb
new file mode 100644
index 000000000..386f2c455
--- /dev/null
+++ b/app/views/multi_transfers/step.html.erb
@@ -0,0 +1,19 @@
+<%= t('multi_transfers.multi_transfers') %>
+
+<%= form_tag(@form_action, method: :post, id: :multi_transfer) do %>
+ <% @steps.each do |step| %>
+
+ <%= render "multi_transfers/steps/#{step}" %>
+
+ <% end %>
+
+
+<% end %>
diff --git a/app/views/multi_transfers/steps/_confirm.html.erb b/app/views/multi_transfers/steps/_confirm.html.erb
new file mode 100644
index 000000000..3a012c3a0
--- /dev/null
+++ b/app/views/multi_transfers/steps/_confirm.html.erb
@@ -0,0 +1,32 @@
+
+ <%= t('multi_transfers.step.confirm.description') %>
+
+
+
+
+ | <%= t('multi_transfers.step.confirm.type_of_transfer') %> |
+ <%= t("multi_transfers.types.#{@type_of_transfer}") %> |
+
+
+ | <%= t('global.from') %> |
+ <%= @from_names.join(', ') %> |
+
+
+ | <%= t('global.to') %> |
+ <%= @to_names.join(', ') %> |
+
+
+ | <%= t('global.amount') %> |
+ <%= seconds_to_hm(@transfer_amount.to_i) %> |
+
+ <% if @transfer_post_id %>
+
+ | <%= t('activerecord.models.post.one') %> |
+ <%= @post_title %> |
+
+ <% end %>
+
+ | <%= t('global.reason') %> |
+ <%= @transfer_reason %> |
+
+
diff --git a/app/views/multi_transfers/steps/_select_source.html.erb b/app/views/multi_transfers/steps/_select_source.html.erb
new file mode 100644
index 000000000..9a170c097
--- /dev/null
+++ b/app/views/multi_transfers/steps/_select_source.html.erb
@@ -0,0 +1,26 @@
+
+ <%= t('multi_transfers.step.set_source.description') %>
+
+
+
+ <%= select_tag('from[]',
+ options_for_select(
+ TransferSourcesOptions.new(@accounts).to_a,
+ selected: @from,
+ ), id: 'select2-multi-from', multiple: @type_of_transfer == :many_to_one, required: true)
+ %>
+
+
+
diff --git a/app/views/multi_transfers/steps/_select_target.html.erb b/app/views/multi_transfers/steps/_select_target.html.erb
new file mode 100644
index 000000000..24d77c342
--- /dev/null
+++ b/app/views/multi_transfers/steps/_select_target.html.erb
@@ -0,0 +1,29 @@
+
+ <%= t('multi_transfers.step.set_target.description') %>
+
+
+
+ <%= select_tag('to[]',
+ options_for_select(
+ TransferSourcesOptions.new(@accounts).to_a,
+ selected: @to,
+ ), id: 'select2-multi-to', multiple: @type_of_transfer == :one_to_many, required: true)
+ %>
+
+
+
diff --git a/app/views/multi_transfers/steps/_select_type.html.erb b/app/views/multi_transfers/steps/_select_type.html.erb
new file mode 100644
index 000000000..a8d3f20ea
--- /dev/null
+++ b/app/views/multi_transfers/steps/_select_type.html.erb
@@ -0,0 +1,35 @@
+
+ <%= t('multi_transfers.step.select_type.description') %>
+
+
+
+
+
diff --git a/app/views/multi_transfers/steps/_set_params.html.erb b/app/views/multi_transfers/steps/_set_params.html.erb
new file mode 100644
index 000000000..9b79dc876
--- /dev/null
+++ b/app/views/multi_transfers/steps/_set_params.html.erb
@@ -0,0 +1,37 @@
+
+ <%= t('multi_transfers.step.set_params.description') %>
+
+
+
+ <%= simple_fields_for :transfer do |f| %>
+ <%= f.input :hours,
+ required: false,
+ as: :integer,
+ input_html: {
+ value: @transfer_hours,
+ min: 0,
+ max: 20,
+ "data-rule-either-hours-minutes-informed" => "true",
+ "data-rule-range" => "[0,20]"
+ } %>
+ <%= f.input :minutes,
+ required: false,
+ as: :integer,
+ input_html: {
+ value: @transfer_minutes,
+ min: 0,
+ max: 59,
+ step: 15,
+ "data-rule-either-hours-minutes-informed" => "true",
+ "data-rule-range" => "[0,59]"
+ } %>
+ <%= f.input :amount, as: :hidden, input_html: { value: @transfer_amount } %>
+ <%= f.input :reason, required: false, input_html: { value: @transfer_reason } %>
+
+
+
+ <% if @should_render_offer_selector %>
+ <%= render partial: "transfers/#{@target_accountable.model_name.singular}_offer", locals: { form: f, offer: nil, accountable: @target_accountable, value: @transfer_post_id } %>
+ <% end %>
+ <% end %>
+
diff --git a/app/views/shared/_post_form.html.erb b/app/views/shared/_post_form.html.erb
index 59613136a..625ef5d0e 100644
--- a/app/views/shared/_post_form.html.erb
+++ b/app/views/shared/_post_form.html.erb
@@ -52,7 +52,6 @@
class: "form-control",
required: true %>
- <%= f.hidden_field :publisher_id, value: current_user.id %>
<% end %>
<%= t "global.required_field" %>
diff --git a/app/views/statistics/statistics_demographics.html.erb b/app/views/statistics/statistics_demographics.html.erb
index eb5d6a195..a7071ef1d 100644
--- a/app/views/statistics/statistics_demographics.html.erb
+++ b/app/views/statistics/statistics_demographics.html.erb
@@ -3,22 +3,15 @@
diff --git a/app/views/transfers/_member_offer.html.erb b/app/views/transfers/_member_offer.html.erb
index b593bc2db..c5673a8f9 100644
--- a/app/views/transfers/_member_offer.html.erb
+++ b/app/views/transfers/_member_offer.html.erb
@@ -2,5 +2,13 @@
<%= form.input :post, readonly: true %>
<%= form.input :post_id, as: :hidden %>
<% else %>
- <%= form.input :post_id, collection: accountable.offers.active %>
+ <%
+ input_options = { collection: accountable.offers.active }
+
+ if local_assigns[:value]
+ input_options[:selected] = local_assigns[:value]
+ end
+ %>
+
+ <%= form.input :post_id, input_options %>
<% end %>
diff --git a/app/views/transfers/_sources.html.erb b/app/views/transfers/_sources.html.erb
index 271092838..98c6a88b5 100644
--- a/app/views/transfers/_sources.html.erb
+++ b/app/views/transfers/_sources.html.erb
@@ -2,7 +2,7 @@
<%= form.label :source %>
<%= form.select :source,
options_for_select(
- TransferSourcesOptions.new(sources, accountable).to_a,
+ TransferSourcesOptions.new(sources).to_a,
selected: current_user.member(current_organization).account.id
), {}, id: "select2-time", class: "form-control"
%>
diff --git a/app/views/transfers/new.html.erb b/app/views/transfers/new.html.erb
index 3f349f5f9..6de7ee460 100644
--- a/app/views/transfers/new.html.erb
+++ b/app/views/transfers/new.html.erb
@@ -37,7 +37,7 @@
<%= f.button :submit, class: "btn btn-default" %>
-
+
<% end %>
diff --git a/app/views/users/_form.html.erb b/app/views/users/_form.html.erb
index 638b79fa6..f2b32484b 100644
--- a/app/views/users/_form.html.erb
+++ b/app/views/users/_form.html.erb
@@ -14,6 +14,7 @@
<%= f.input :phone %>
<%= f.input :alt_phone %>
+ <%= f.input :gender, collection: genders_collection %>
<%= f.input :date_of_birth,
start_year: Date.today.year - 90,
end_year: Date.today.year - 12,
diff --git a/config/environments/production.rb b/config/environments/production.rb
index fe4480f76..63287d136 100644
--- a/config/environments/production.rb
+++ b/config/environments/production.rb
@@ -22,7 +22,7 @@
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
- config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
+ config.serve_static_files = true
# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier
diff --git a/config/environments/staging.rb b/config/environments/staging.rb
index b451781fe..1ba241c29 100644
--- a/config/environments/staging.rb
+++ b/config/environments/staging.rb
@@ -22,7 +22,7 @@
# Disable serving static files from the `/public` folder by default since
# Apache or NGINX already handles this.
- config.serve_static_files = ENV['RAILS_SERVE_STATIC_FILES'].present?
+ config.serve_static_files = true
# Compress JavaScripts and CSS.
config.assets.js_compressor = :uglifier
diff --git a/config/locales/ca.yml b/config/locales/ca.yml
index ac0405a9b..70a0c7ff9 100644
--- a/config/locales/ca.yml
+++ b/config/locales/ca.yml
@@ -43,8 +43,6 @@ ca:
created_at: Creat
description: Descripció
end_on: Finalitza al
- joinable: Es poden afegir altres
- permanent: Permanent
start_on: Comença al
tag_list: Etiquetes
title: Títol
@@ -294,11 +292,13 @@ ca:
balance: 'Balanç:'
cancel_membership: Esborrar definitivament
contact_details: Dades de contacte
+ create: Crear
date: Data
delete: Esborrar
demote: Convertir en usuari normal
edit: Modificar
filter: Filtre
+ from: Des de
give_time: Transferir temps
home: Inici
information: Informació
@@ -306,6 +306,7 @@ ca:
member_count: 'Nombre d''usuaris:'
more: Veure més
movements: Moviments
+ next: Següent
promote: Convertir en administrador
reason: Raó
required_field: "* Camp obligatori"
@@ -316,6 +317,7 @@ ca:
statistics: Estadístiques
table:
actions: Accions
+ to: a
inquiries:
edit:
submit: Canviar demanda
@@ -347,6 +349,25 @@ ca:
text: "%{organization_name} en"
text_donation: L'associació ADBdT ofereix TimeOverflow de forma gratuïta als Bancs de Temps. Amb una donació de %{href} pots ajudar a mantenir i millorar la plataforma.
text_donation_link: 1€ al mes
+ multi_transfers:
+ multi_transfers: 'Transferències múltiples '
+ step:
+ confirm:
+ description: Confirma els canvis
+ type_of_transfer: Tipus de transferència
+ select_type:
+ description: Selecciona el tipus de transferència
+ set_params:
+ description: 'Especifica el total a transferir, comentaris, i la oferta (si existeix) '
+ set_source:
+ description: Selecciona els comptes on es transferirà el temps
+ set_target:
+ description: Selecciona els comptes on es rebrà el temps
+ success: Transferències realitzades amb éxit
+ types:
+ many_to_one: De moltes a una
+ one_to_many: De una a moltes
+ one_to_one: De una a una
offers:
edit:
submit: Canviar Oferta
@@ -435,6 +456,8 @@ ca:
gender:
female: Dona
male: Home
+ others: Altre
+ prefer_not_to_answer: Prefereixo no contestar
required:
mark: "*"
text: required
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 0bdfdcba8..4772e5ebb 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -43,8 +43,6 @@ en:
created_at: Created
description: Description
end_on: Ends on
- joinable: Can join others
- permanent: Permanent
start_on: Start on
tag_list: Tags
title: Title
@@ -286,6 +284,10 @@ en:
description: The page you were looking for doesn't exist. You may have mistyped the address or the page may have moved.
title: Not found
global:
+ from: From
+ to: To
+ next: Next
+ create: Create
add_new: Create new
all: All
amount: Quantity
@@ -435,6 +437,8 @@ en:
gender:
female: Female
male: Male
+ others: Other
+ prefer_not_to_answer: I prefer not to answer
required:
mark: "*"
text: required
@@ -491,6 +495,25 @@ en:
accept: Accept
show:
accept: Accept
+ multi_transfers:
+ multi_transfers: 'Multi transfers'
+ success: 'Transfers created successfully'
+ types:
+ one_to_many: 'One to many'
+ many_to_one: 'Many to one'
+ one_to_one: 'One to one'
+ step:
+ select_type:
+ description: 'Select the type of transfer'
+ set_source:
+ description: 'Select the account/s that will transfer time'
+ set_target:
+ description: 'Select the account/s that will receive time'
+ set_params:
+ description: 'Specify amount to transfer, reason, and post ( if any ).'
+ confirm:
+ description: 'Confirm the changes'
+ type_of_transfer: 'Type of transfer'
transfers:
computation:
hour:
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 91d5c69fa..5b9aee99c 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -43,8 +43,6 @@ es:
created_at: Creado
description: Descripción
end_on: Termina el
- joinable: Se puede unir otros
- permanent: Permanente
start_on: Empieza el
tag_list: Etiquetas
title: Título
@@ -294,11 +292,13 @@ es:
balance: 'Balance:'
cancel_membership: Borrar definitivamente
contact_details: Datos de contacto
+ create: Crear
date: Fecha
delete: Borrar
demote: Convertir en usuario normal
edit: Modificar
filter: Filtro
+ from: De
give_time: Transferir tiempo
home: Inicio
information: Información
@@ -306,6 +306,7 @@ es:
member_count: 'Número de usuarios:'
more: Ver más
movements: Movimientos
+ next: Siguiente
promote: Convertir en administrador
reason: Razón
required_field: "* Campo obligatorio"
@@ -316,6 +317,7 @@ es:
statistics: Estadísticas
table:
actions: Acciones
+ to: a
inquiries:
edit:
submit: Cambiar demanda
@@ -347,6 +349,25 @@ es:
text: "%{organization_name} en"
text_donation: La asociación ADBdT ofrece TimeOverflow de forma gratuita a los Bancos de Tiempo. Con una donación de %{href} puedes ayudar a mantener y mejorar la plataforma.
text_donation_link: 1€ al mes
+ multi_transfers:
+ multi_transfers: Transferencias múltiples
+ step:
+ confirm:
+ description: Confirma los cambios
+ type_of_transfer: Tipo de transferencia
+ select_type:
+ description: Seleccionar el tipo de transferencia
+ set_params:
+ description: Especifica la cantidad a transferir, comentarios y oferta (si existe)
+ set_source:
+ description: Selecciona las cuentas dónde se va a transferir tiempo
+ set_target:
+ description: Selecciona las cuentas dónde se va recibir tiempo
+ success: Transferencias creadas con éxito
+ types:
+ many_to_one: De muchas a una
+ one_to_many: De una a muchas
+ one_to_one: De una a una
offers:
edit:
submit: Cambiar oferta
@@ -435,6 +456,8 @@ es:
gender:
female: Mujer
male: Hombre
+ others: Otro
+ prefer_not_to_answer: Prefiero no contestar
required:
mark: "*"
text: necesario
diff --git a/config/locales/eu.yml b/config/locales/eu.yml
index b75c4b0d5..950df53d9 100644
--- a/config/locales/eu.yml
+++ b/config/locales/eu.yml
@@ -45,8 +45,6 @@ eu:
created_at: Sortua
description: Deskribapena
end_on: Bukatuko da.
- joinable: Beste batzuk gehi litezke.
- permanent: Iraunkorra
start_on: Hasiko da
tag_list: Etiketa
title: Izenburua
@@ -300,11 +298,13 @@ eu:
balance: Balantzea
cancel_membership: Betirako borratu
contact_details: Kontaktuaren datuak
+ create:
date: Eguna.
delete: Ezabatu
demote: Ohiko erabiltzaile biurtu
edit: Aldaketak egin
filter: Iragazkia
+ from:
give_time: Denbora eman
home:
information: Informazioa
@@ -312,6 +312,7 @@ eu:
member_count: Erabiltzaile zenbakia
more: 'Gehiago ikusi '
movements: Mugimenduak
+ next:
promote: Administratzaile bihurtu
reason: Arrazoia
required_field: Derrigorrez bete beharrekoa
@@ -322,6 +323,7 @@ eu:
statistics: Estatistika
table:
actions: Ekintzak
+ to:
inquiries:
edit:
submit: Eskaera aldatu
@@ -353,6 +355,25 @@ eu:
text: "%{organization_name}"
text_donation: ADBdt elkarteak TimeOverflow zerbitzua , dohainik eskaintzen die denbora bankuei. Plataforma mantendu eta hobetzeko lagun dezakete %{href} ko emari batekin.
text_donation_link: hilean €1ekin
+ multi_transfers:
+ multi_transfers:
+ step:
+ confirm:
+ description:
+ type_of_transfer:
+ select_type:
+ description:
+ set_params:
+ description:
+ set_source:
+ description:
+ set_target:
+ description:
+ success:
+ types:
+ many_to_one:
+ one_to_many:
+ one_to_one:
offers:
edit:
submit: Eskaintza aldatu
@@ -441,6 +462,8 @@ eu:
gender:
female: Emakumea
male: Gizona
+ others: Beste
+ prefer_not_to_answer:
required:
mark: "*"
text: Beharrezkoa
diff --git a/config/locales/gl.yml b/config/locales/gl.yml
index 4fb04e053..1090503ef 100644
--- a/config/locales/gl.yml
+++ b/config/locales/gl.yml
@@ -43,8 +43,6 @@ gl:
created_at: Creado
description: Descrición
end_on: Termina en
- joinable: Poden unirse outros
- permanent: Permanente
start_on: Comezar
tag_list: Etiquetas
title: Título
@@ -294,11 +292,13 @@ gl:
balance: 'Balance:'
cancel_membership: Borrado permanente
contact_details: Datos de contacto
+ create: Crea
date: Datos
delete: Eliminar
demote: Degradar a usuario/a normal
edit: Actualización
filter: Filtro
+ from: Desde
give_time: Transferencia de tempo
home: Inicio
information: Información
@@ -306,6 +306,7 @@ gl:
member_count: 'Número de persoas usuarias:'
more: Máis
movements: Transaccións
+ next: Próximo
promote: Promover á persoa administradora
reason: Razón
required_field: "* Campo obrigatorio"
@@ -316,6 +317,7 @@ gl:
statistics: Estatísticas
table:
actions: Accións
+ to: A
inquiries:
edit:
submit: Cambiar consulta
@@ -347,6 +349,25 @@ gl:
text: "%{organization_name} de"
text_donation: A asociación ADBdT ofrece TimeOverflow gratuitamente aos bancos de tempo. Cunha doazón de %{href} podes axudar a manter e mellorar a plataforma.
text_donation_link: 1€ ao mes
+ multi_transfers:
+ multi_transfers: Multi Transferencias
+ step:
+ confirm:
+ description: Confirma os cambios
+ type_of_transfer: Tipo de transferencia
+ select_type:
+ description: Selecciona o tipo de transferencia
+ set_params:
+ description: Especifica a cantidade a transferir, razón, e correo ( se hai ).
+ set_source:
+ description: Selecciona a conta (ou contas) que transferirá o tempo
+ set_target:
+ description: Selecciona a conta (ou contas) que recibirá tempo
+ success: As transferencias fixéronse con éxito
+ types:
+ many_to_one: De moitas persoas a unha
+ one_to_many: De unha a moitas persoas
+ one_to_one: De unha persoa a unha
offers:
edit:
submit: Cambiar oferta
@@ -435,6 +456,8 @@ gl:
gender:
female: Muller
male: Home
+ others: Outros
+ prefer_not_to_answer: Prefiro non contestar
required:
mark: "*"
text: esixido
diff --git a/config/locales/pt-BR.yml b/config/locales/pt-BR.yml
index be2496192..8de7fc794 100644
--- a/config/locales/pt-BR.yml
+++ b/config/locales/pt-BR.yml
@@ -22,9 +22,9 @@ pt-BR:
created_at: Criado
updated_at: Atualizado
inquiry:
- is_group:
+ is_group: Consulta de grupo
offer:
- is_group:
+ is_group: Oferta de grupo
organization:
address: Endereço
city: Cidade
@@ -43,8 +43,6 @@ pt-BR:
created_at: Criado
description: Descrição
end_on: 'Terminar o '
- joinable: É possível unir outros
- permanent: Permanente
start_on: Começa o
tag_list: Etiquetas
title: Título
@@ -69,9 +67,9 @@ pt-BR:
identity_document: Documento
last_sign_in_at: Data do último login
notifications: Receber notificações
- organization:
+ organization: Organização
phone: Telefone
- push_notifications:
+ push_notifications: Receber notificações pelo celular
registration_date: Data de ingresso
registration_number: Código do usuário
superadmin: Administrador do sistema
@@ -132,7 +130,7 @@ pt-BR:
sing_out: Desconectar
menus:
offers_by_tag_link:
- tags:
+ tags: Etiqueta
navbar:
admin: Administrar
administration: Administração
@@ -251,8 +249,8 @@ pt-BR:
remember_me: Lembrar de mim
sign_in: Entrar
user:
- signed_in:
- signed_out:
+ signed_in: Entrar
+ signed_out: Sair
shared:
links:
didnt_receive_confirmation_instructions: Enviar e-mail de confirmação novamente
@@ -271,8 +269,8 @@ pt-BR:
unlocked: Tua conta foi desbloqueada. Por favor, entre para continuar.
errors:
internal_server_error:
- description:
- title:
+ description: Desculpem, parece que há um erro com esta solicitação. Uma notificação nos foi enviada automaticamente, e nós resolveremos isso assim que possível.
+ title: Erro do servidor interno
messages:
already_confirmed: já foi confirmado, por favor tente entrar
confirmation_period_expired: É necessário se confirmar em %{period}, por favor, solicite um novo
@@ -283,8 +281,8 @@ pt-BR:
one: '1 erro impede que este %{resource} se guarde:'
other: "%{count} erros impedem que este %{resource} se guarde:"
not_found:
- description:
- title:
+ description: A página que você estava procurando não existe. Você deve ter digitado o endereço errado ou a página mudou.
+ title: Não encontrado
global:
add_new: Criar novo
all: Tudo
@@ -294,18 +292,21 @@ pt-BR:
balance: 'Balanço:'
cancel_membership: Apagar definitivamente
contact_details: Dados de contato
+ create: Criar
date: Data
delete: Apagar
demote: Converter em usuário normal
edit: Modificar
filter: Filtro
+ from: De
give_time: Transferir tempo
- home:
+ home: Início
information: Informação
locales_header: Trocar idioma
member_count: 'Número de usuários:'
more: Ver mais
movements: Movimentos
+ next: Próximo
promote: Converter em administrador
reason: Razão
required_field: "* Campo obrigatório"
@@ -316,6 +317,7 @@ pt-BR:
statistics: Estatísticas
table:
actions: Ações
+ to: Para
inquiries:
edit:
submit: Trocar demanda
@@ -345,8 +347,27 @@ pt-BR:
mailers_globals:
footer:
text: "%{organization_name} em"
- text_donation:
- text_donation_link:
+ text_donation: A associação ADBdT oferece TimeOverflow gratuitamente para Bancos de Tempo. Com a doação de %{href} você pode ajudar a manter e aprimorar a plataforma.
+ text_donation_link: 1€ ao mês
+ multi_transfers:
+ multi_transfers: Multi transferidores
+ step:
+ confirm:
+ description: Confirmar as mudanças
+ type_of_transfer: Tipo de transferência
+ select_type:
+ description: Selecione o tipo de transferência
+ set_params:
+ description: Especificar a quantia para transferir, razão, e cargo (se algum)
+ set_source:
+ description: Selecione a(s) conta(s) que transferirá tempo
+ set_target:
+ description: Selecione a(s) conta(s) que receberá tempo
+ success: Transferência feita com sucesso
+ types:
+ many_to_one: Muitos para um
+ one_to_many: Um para muitos
+ one_to_one: Um para um
offers:
edit:
submit: Trocar oferta
@@ -377,14 +398,14 @@ pt-BR:
contact_information: Informação de contato
pages:
about:
- app-mobile:
- app-mobile-text:
+ app-mobile: Aplicativo móvel
+ app-mobile-text: O aplicativo móvel TimeOverflow está disponível.
Este aplicativo foi possível graças aos colaboradores da municipalidade de Barcelona, programa %{impulsem_link} (Barcelona Ativa) 2017-2018
banner-button: Pedir acesso
banner-subtitle: Vamos ajudá-los a começar ou obter uma demonstração
banner-title: Você é um Banco de Tempo?
- donate-link:
- donate-text:
- donate-title:
+ donate-link: Doe 1€ ao mês
+ donate-text: A fim de apoiar muitas comunidades a associação ADBdT oferece a plataforma TimeOverflow para todos os Bancos de Tempo. Doe %{donate_link} para contribuir com os custos de manutenção e inovação.
+ donate-title: Participe com uma doação
empower-adbdt: ADBdt
empower-adbdt-title: Associação para o Desenvolvimento dos Bancos de Tempo
empower-coopdevs: CoopDevs
@@ -405,13 +426,13 @@ pt-BR:
feature-text-4: Os membros de um Banco de Tempo podem acessar o sistema e entrar em contato com outros membros
feature-text-5: Publicar ofertas e demandas
feature-text-6: Pagar horas a outros membros
- impulsem-link:
+ impulsem-link: Incentive quem faz
subtitle: TimeOverflow é livre, gratuito e colaborativo
title: O software desenhado por e para
title2: os Bancos de Tempo
posts:
show:
- info:
+ info: Este %{type} pertence a %{organization}.
reports:
cat_with_users:
title: Serviços oferecidos
@@ -423,8 +444,8 @@ pt-BR:
delete_reason: Tem certeza de que quer apagar este comentário?
movements: Movimentos
post_form:
- group_inquiry:
- group_offer:
+ group_inquiry: É uma requisição de grupo?
+ group_offer: É uma oferta de grupo?
you_can_use: Você pode usar
simple_form:
error_notification:
@@ -435,6 +456,8 @@ pt-BR:
gender:
female: Mulher
male: Homem
+ others: Outro
+ prefer_not_to_answer: Prefiro não responder
required:
mark: "*"
text: Necessário
@@ -490,7 +513,7 @@ pt-BR:
terms:
accept: Aceitar
show:
- accept:
+ accept: Aceitar
transfers:
computation:
hour:
@@ -501,7 +524,7 @@ pt-BR:
one: "%{count} minuto"
other: "%{count} minutos"
new:
- error_amount:
+ error_amount: O tempo deve ser maior que 0
users:
edit:
edit_user: Trocar usuário
@@ -511,18 +534,18 @@ pt-BR:
give_time: Dar Tempo a
index:
actions: Ações
- active_warning:
+ active_warning: Você mudará o status da conta para %{username}
active_warning_angular: Mudará o estado da conta do usuário {{username}}
- cancel_warning:
+ cancel_warning: Você deletará a conta do Banco de Tempo %{username}
cancel_warning_angular: Eliminará o usuário do banco {{username}}
create: Criar novo usuário
- manage_warning:
+ manage_warning: Você mudará privilégios para o usuário %{username}
manage_warning_angular: Mudará os privilégios do usuário {{username}}
- members:
+ members: Membros
user_created: Usuário %{uid} %{name} guardado
member_card:
- active_ago:
- no_activity:
+ active_ago: Ativo %{time} anterior
+ no_activity: Inativo
new:
cancel: Cancelar
create_more_users_button: Criar e introduzir outro usuário
@@ -533,7 +556,7 @@ pt-BR:
accounts: Contas
balance: 'Balanço:'
categories: Serviços Oferecidos
- change_your_image:
+ change_your_image: Mudar sua imagem
created_at: 'Inscrição:'
data: Dados do usuário
date: Data
diff --git a/config/routes.rb b/config/routes.rb
index f28c54013..2179e4f83 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -46,6 +46,9 @@
end
end
+ match "multi/step/:step", to: "multi_transfers#step", via: [:get, :post], as: :multi_transfers_step
+ post "multi/create", to: "multi_transfers#create", as: :multi_transfers_create
+
resources :documents
resources :members, only: [:destroy] do
diff --git a/db/migrate/20190322180602_remove_publisher_id_from_posts.rb b/db/migrate/20190322180602_remove_publisher_id_from_posts.rb
new file mode 100644
index 000000000..2190912db
--- /dev/null
+++ b/db/migrate/20190322180602_remove_publisher_id_from_posts.rb
@@ -0,0 +1,5 @@
+class RemovePublisherIdFromPosts < ActiveRecord::Migration
+ def change
+ remove_column :posts, :publisher_id
+ end
+end
diff --git a/db/migrate/20190411192828_remove_deprecated_attributes_from_posts.rb b/db/migrate/20190411192828_remove_deprecated_attributes_from_posts.rb
new file mode 100644
index 000000000..221ce356e
--- /dev/null
+++ b/db/migrate/20190411192828_remove_deprecated_attributes_from_posts.rb
@@ -0,0 +1,7 @@
+class RemoveDeprecatedAttributesFromPosts < ActiveRecord::Migration
+ def change
+ remove_column :posts, :permanent
+ remove_column :posts, :joinable
+ remove_column :posts, :global
+ end
+end
diff --git a/db/migrate/20190412163011_remove_user_joined_post_table.rb b/db/migrate/20190412163011_remove_user_joined_post_table.rb
new file mode 100644
index 000000000..ea55d47bc
--- /dev/null
+++ b/db/migrate/20190412163011_remove_user_joined_post_table.rb
@@ -0,0 +1,5 @@
+class RemoveUserJoinedPostTable < ActiveRecord::Migration
+ def change
+ drop_table :user_joined_post
+ end
+end
diff --git a/db/schema.rb b/db/schema.rb
index bcb4a05c1..bfcd9e381 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
#
# It's strongly recommended that you check this file into your version control system.
-ActiveRecord::Schema.define(version: 20190319121401) do
+ActiveRecord::Schema.define(version: 20190412163011) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -141,13 +141,9 @@
t.text "description"
t.date "start_on"
t.date "end_on"
- t.boolean "permanent"
- t.boolean "joinable"
- t.boolean "global"
t.datetime "created_at"
t.datetime "updated_at"
t.text "tags", array: true
- t.integer "publisher_id"
t.integer "organization_id"
t.boolean "active", default: true
t.boolean "is_group", default: false
@@ -155,7 +151,6 @@
add_index "posts", ["category_id"], name: "index_posts_on_category_id", using: :btree
add_index "posts", ["organization_id"], name: "index_posts_on_organization_id", using: :btree
- add_index "posts", ["publisher_id"], name: "index_posts_on_publisher_id", using: :btree
add_index "posts", ["tags"], name: "index_posts_on_tags", using: :gin
add_index "posts", ["user_id"], name: "index_posts_on_user_id", using: :btree
@@ -181,11 +176,6 @@
add_index "transfers", ["operator_id"], name: "index_transfers_on_operator_id", using: :btree
add_index "transfers", ["post_id"], name: "index_transfers_on_post_id", using: :btree
- create_table "user_joined_post", force: :cascade do |t|
- t.integer "user_id"
- t.integer "post_id"
- end
-
create_table "users", force: :cascade do |t|
t.string "username", null: false
t.string "email", null: false
diff --git a/docs/ofertas.png b/docs/ofertas.png
index 53b077b6f..ce64f5afa 100644
Binary files a/docs/ofertas.png and b/docs/ofertas.png differ
diff --git a/docs/usuarios.png b/docs/usuarios.png
index 922a1602c..0b8b82e2d 100644
Binary files a/docs/usuarios.png and b/docs/usuarios.png differ
diff --git a/spec/controllers/multi_transfers_controller_spec.rb b/spec/controllers/multi_transfers_controller_spec.rb
new file mode 100644
index 000000000..bab67003e
--- /dev/null
+++ b/spec/controllers/multi_transfers_controller_spec.rb
@@ -0,0 +1,137 @@
+require "spec_helper"
+
+RSpec.describe MultiTransfersController, type: :controller do
+ let(:organization) { Fabricate(:organization) }
+ let(:admin) { Fabricate(:member, organization: organization, manager: true) }
+ let(:member) { Fabricate(:member, organization: organization) }
+ let(:another_member) { Fabricate(:member, organization: organization) }
+ let(:yet_another_member) { Fabricate(:member) }
+ let(:test_category) { Fabricate(:category) }
+ let!(:offer) do
+ Fabricate(:offer,
+ user: member.user,
+ organization: organization,
+ category: test_category)
+ end
+
+ include_context "stub browser locale"
+
+ before { set_browser_locale("en") }
+
+ it 'creates one to many transfers' do
+ expect do
+ login(admin.user)
+
+ get :step, step: 1
+
+ params = {}
+
+ post :step, params.merge!(
+ step: 2,
+ type_of_transfer: :one_to_many
+ )
+
+ post :step, params.merge!(
+ step: 3,
+ from: [member.account].map(&:id)
+ )
+
+ post :step, params.merge!(
+ step: 4,
+ to: [another_member.account, yet_another_member.account].map(&:id)
+ )
+
+ post :step, params.merge!(
+ step: 5,
+ transfer: {amount: 3600, reason: 'because of reasons'}
+ )
+
+ post :create, params
+ end.to change { Transfer.count }.by(2)
+ end
+
+ it 'creates many to one transfers' do
+ expect do
+ login(admin.user)
+
+ get :step, step: 1
+
+ params = {}
+
+ post :step, params.merge!(
+ step: 2,
+ type_of_transfer: :many_to_one
+ )
+
+ post :step, params.merge!(
+ step: 3,
+ to: [another_member.account, yet_another_member.account].map(&:id)
+ )
+
+ post :step, params.merge!(
+ step: 4,
+ from: [member.account].map(&:id)
+ )
+
+ post :step, params.merge!(
+ step: 5,
+ transfer: {amount: 3600, reason: 'because of reasons'}
+ )
+
+ post :create, params
+ end.to change { Transfer.count }.by(2)
+ end
+
+ context 'when only one source and one target is selected' do
+ it 'creates one to one transfers' do
+ expect do
+ login(admin.user)
+
+ get :step, step: 1
+
+ params = {}
+
+ post :step, params.merge!(
+ step: 2,
+ type_of_transfer: :many_to_one
+ )
+
+ post :step, params.merge!(
+ step: 3,
+ to: [member.account].map(&:id)
+ )
+
+ post :step, params.merge!(
+ step: 4,
+ from: [another_member.account].map(&:id)
+ )
+
+ post :step, params.merge!(
+ step: 5,
+ transfer: {amount: 3600, reason: 'because of reasons'}
+ )
+
+ post :create, params
+ end.to change { Transfer.count }.by(1)
+ end
+ end
+
+ context 'non admins' do
+ it 'cannot access step route' do
+ login(member.user)
+
+ get :step, step: 1
+
+ expect(response).not_to have_http_status(:success)
+ end
+
+ it 'cannot access create route' do
+ login(member.user)
+
+ post :create, {}
+
+ response.should redirect_to('/')
+ end
+ end
+end
+
diff --git a/spec/controllers/transfers_controller_spec.rb b/spec/controllers/transfers_controller_spec.rb
index c075091ec..96b4bd05f 100644
--- a/spec/controllers/transfers_controller_spec.rb
+++ b/spec/controllers/transfers_controller_spec.rb
@@ -71,7 +71,7 @@
get :new, params
expect(response.body).to include("")
+ expect(response.body).to include("")
end
end
diff --git a/spec/controllers/users_controller_spec.rb b/spec/controllers/users_controller_spec.rb
index 552c5f4c5..698af6d57 100644
--- a/spec/controllers/users_controller_spec.rb
+++ b/spec/controllers/users_controller_spec.rb
@@ -40,12 +40,14 @@
describe "GET #index" do
before { login(user) }
- it 'sorts the users by their member_uid asc by default' do
- member.increment!(:member_uid, Member.maximum(:member_uid) + 1)
+ it 'sorts the users by their last_sign_in_at desc by default' do
+ member.user.update_column(:last_sign_in_at, DateTime.now)
+ another_member.user.update_column(:last_sign_in_at, nil)
get :index
- expect(assigns(:members).last).to eq(member)
+ expect(assigns(:members).first).to eq(member)
+ expect(assigns(:members).last).to eq(another_member)
end
it 'allows to sort by member_uid' do
@@ -81,8 +83,6 @@
context "with an normal logged user" do
it "populates and array of users" do
- login(user)
-
get "index"
expect(assigns(:members).map(&:user))
@@ -101,9 +101,31 @@
end
end
+ context 'when searching' do
+ it 'allows to search by member_uid' do
+ user = Fabricate(:user, username: 'foo', email: 'foo@email.com')
+ member = Fabricate(:member, user: user, organization: test_organization, member_uid: 1000)
+
+ get :index, q: { user_username_or_user_email_or_member_uid_search_contains: 1000 }
+
+ expect(assigns(:members)).to include(member)
+ end
+ end
+ end
+
+ describe "GET #manage" do
+ before { login(user) }
+
+ it 'sorts the users by their member_uid asc by default' do
+ member.increment!(:member_uid, Member.maximum(:member_uid) + 1)
+
+ get :manage
+
+ expect(assigns(:members).last).to eq(member)
+ end
+
context 'when sorting by balance' do
before do
- login(user)
member_admin.account.update_attribute(:balance, 3600)
end
@@ -111,7 +133,7 @@
let(:direction) { 'desc' }
it 'orders the rows by their balance' do
- get :index, q: { s: "account_balance #{direction}" }
+ get :manage, q: { s: "account_balance #{direction}" }
expect(assigns(:members).pluck(:user_id).first).to eq(admin_user.id)
end
@@ -121,23 +143,12 @@
let(:direction) { 'asc' }
it 'orders the rows by their balance' do
- get :index, q: { s: "account_balance #{direction}" }
+ get :manage, q: { s: "account_balance #{direction}" }
expect(assigns(:members).pluck(:user_id).last).to eq(admin_user.id)
end
end
end
-
- context 'when searching' do
- it 'allows to search by member_uid' do
- user = Fabricate(:user, username: 'foo', email: 'foo@email.com')
- member = Fabricate(:member, user: user, organization: test_organization, member_uid: 1000)
-
- get :index, q: { user_username_or_user_email_or_member_uid_search_contains: 1000 }
-
- expect(assigns(:members)).to include(member)
- end
- end
end
describe "GET #show" do
@@ -151,7 +162,7 @@
end
it 'links to new_transfer_path for his individual offers' do
- offer = Fabricate(:offer, user: user, publisher: user, organization: test_organization)
+ offer = Fabricate(:offer, user: user, organization: test_organization)
get "show", id: user.id
expect(response.body).to include(
@@ -176,7 +187,7 @@
end
it 'links to new_transfer_path for his individual offers' do
- offer = Fabricate(:offer, user: user, publisher: user, organization: test_organization)
+ offer = Fabricate(:offer, user: user, organization: test_organization)
get "show", id: user.id
expect(response.body).to include(
diff --git a/spec/fabricators/post_fabricator.rb b/spec/fabricators/post_fabricator.rb
index 367e6bc6f..919bd56b4 100644
--- a/spec/fabricators/post_fabricator.rb
+++ b/spec/fabricators/post_fabricator.rb
@@ -4,9 +4,6 @@
user { Fabricate(:user) }
description { Faker::Lorem.paragraph }
category { Fabricate(:category) }
- permanent { false }
- joinable { false }
- global { false }
active { true }
end
@@ -19,9 +16,6 @@
user { Fabricate(:user) }
description { Faker::Lorem.paragraph }
category { Fabricate(:category) }
- permanent { false }
- joinable { false }
- global { false }
active { true }
end
@@ -34,9 +28,6 @@
user { Fabricate(:user) }
description { Faker::Lorem.paragraph }
category { Fabricate(:category) }
- permanent { false }
- joinable { false }
- global { false }
active { true }
end
diff --git a/spec/models/member_spec.rb b/spec/models/member_spec.rb
index 817b3d1c1..5196f52c5 100644
--- a/spec/models/member_spec.rb
+++ b/spec/models/member_spec.rb
@@ -36,7 +36,7 @@
end
describe '#display_id' do
- subject { member.display_id(nil) }
+ subject { member.display_id }
it { is_expected.to eq(member.member_uid) }
end
diff --git a/spec/models/organization_spec.rb b/spec/models/organization_spec.rb
index a7cc30296..8b0a9710e 100644
--- a/spec/models/organization_spec.rb
+++ b/spec/models/organization_spec.rb
@@ -4,17 +4,9 @@
let(:organization) { Fabricate(:organization) }
describe '#display_id' do
- subject { organization.display_id(destination_accountable) }
+ subject { organization.display_id }
- context 'when the destination_accountable is an organization' do
- let(:destination_accountable) { Fabricate(:organization) }
- it { is_expected.to eq(organization.account.accountable_id) }
- end
-
- context 'when the destination_accountable is not an organization' do
- let(:destination_accountable) { Fabricate(:member) }
- it { is_expected.to eq('') }
- end
+ it { is_expected.to eq(organization.account.accountable_id) }
end
describe 'ensure_url validation' do
diff --git a/spec/models/post_spec.rb b/spec/models/post_spec.rb
index 3dee6f67b..54d87727f 100644
--- a/spec/models/post_spec.rb
+++ b/spec/models/post_spec.rb
@@ -4,7 +4,6 @@
describe 'Relations' do
it { is_expected.to belong_to(:category) }
it { is_expected.to belong_to(:user) }
- it { is_expected.to belong_to(:publisher) }
it { is_expected.to have_many(:transfers) }
it { is_expected.to have_many(:movements) }
it { is_expected.to have_many(:events) }
diff --git a/spec/models/transfer_sources_options_spec.rb b/spec/models/transfer_sources_options_spec.rb
index 90ee6f9bf..2827f8777 100644
--- a/spec/models/transfer_sources_options_spec.rb
+++ b/spec/models/transfer_sources_options_spec.rb
@@ -2,7 +2,7 @@
RSpec.describe TransferSourcesOptions do
let(:transfer_sources_options) do
- described_class.new(sources, destination_accountable)
+ described_class.new(sources)
end
describe '#to_a' do
@@ -14,8 +14,6 @@
Fabricate(:member, organization: organization, member_uid: 1)
end
- let(:destination_accountable) { Fabricate(:organization) }
-
let(:sources) do
[organization.account, member.account, newer_member.account]
end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
index c37a39111..1ecab1cfc 100644
--- a/spec/models/user_spec.rb
+++ b/spec/models/user_spec.rb
@@ -17,6 +17,8 @@
it { is_expected.to validate_presence_of :username }
+ it { is_expected.to validate_inclusion_of(:gender).in_array(User::GENDERS) }
+
describe "#setup_and_save_user" do
it "sets a fake email before attempting to save user" do
user = Fabricate.build(:user, email: "")
diff --git a/spec/services/operations/many_to_one_spec.rb b/spec/services/operations/many_to_one_spec.rb
new file mode 100644
index 000000000..e17828d6e
--- /dev/null
+++ b/spec/services/operations/many_to_one_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+RSpec.describe Operations::Transfers::ManyToOne do
+ let(:source_accounts) { 5.times.map { Fabricate(:account) } }
+ let(:destination_account) { Fabricate(:account) }
+
+ let(:operation) do
+ Operations::Transfers::ManyToOne.new(
+ from: source_accounts.map(&:id),
+ to: [destination_account.id],
+ transfer_params: { amount: 3600, reason: 'why not' }
+ )
+ end
+
+ describe '#perform' do
+ it 'creates multiple transfers' do
+ expect { operation.perform }.to change { Transfer.count }.by(5)
+ end
+
+ it 'creates many movements towards destination account' do
+ expect { operation.perform }.to change { Movement.where(account_id: destination_account.id).count }.by(5)
+ end
+
+ it 'creates one movement from each source account' do
+ expect { operation.perform }.to change { Movement.where(account_id: source_accounts.map(&:id)).map(&:account_id).uniq.count }.by(5)
+ end
+ end
+end
diff --git a/spec/services/operations/one_to_many_spec.rb b/spec/services/operations/one_to_many_spec.rb
new file mode 100644
index 000000000..40c26a54b
--- /dev/null
+++ b/spec/services/operations/one_to_many_spec.rb
@@ -0,0 +1,28 @@
+require 'spec_helper'
+
+RSpec.describe Operations::Transfers::OneToMany do
+ let(:source_account) { Fabricate(:account) }
+ let(:destination_accounts) { 5.times.map { Fabricate(:account) } }
+
+ let(:operation) do
+ Operations::Transfers::OneToMany.new(
+ from: [source_account.id],
+ to: destination_accounts.map(&:id),
+ transfer_params: { amount: 3600, reason: 'why not' }
+ )
+ end
+
+ describe '#perform' do
+ it 'creates multiple transfers' do
+ expect { operation.perform }.to change { Transfer.count }.by(5)
+ end
+
+ it 'creates many movements from source account' do
+ expect { operation.perform }.to change { Movement.where(account_id: source_account.id).count }.by(5)
+ end
+
+ it 'creates one movement towards each target account' do
+ expect { operation.perform }.to change { Movement.where(account_id: destination_accounts.map(&:id)).map(&:account_id).uniq.count }.by(5)
+ end
+ end
+end
diff --git a/spec/services/operations/one_to_one_spec.rb b/spec/services/operations/one_to_one_spec.rb
new file mode 100644
index 000000000..b088a7ade
--- /dev/null
+++ b/spec/services/operations/one_to_one_spec.rb
@@ -0,0 +1,29 @@
+require 'spec_helper'
+
+RSpec.describe Operations::Transfers::OneToMany do
+ let(:source_account) { Fabricate(:account) }
+ let(:destination_account) { Fabricate(:account) }
+
+ let(:operation) do
+ Operations::Transfers::OneToOne.new(
+ from: [source_account.id],
+ to: [destination_account.id],
+ transfer_params: { amount: 3600, reason: 'why not' }
+ )
+ end
+
+ describe '#perform' do
+ it 'creates multiple transfers' do
+ expect { operation.perform }.to change { Transfer.count }.by(1)
+ end
+
+ it 'creates one movement towards destination account' do
+ expect { operation.perform }.to change { Movement.where(account_id: source_account.id).count }.by(1)
+ end
+
+ it 'creates one movement from each source account' do
+ expect { operation.perform }.to change { Movement.where(account_id: destination_account.id).count }.by(1)
+ end
+ end
+end
+
diff --git a/spec/services/operations/transfers_spec.rb b/spec/services/operations/transfers_spec.rb
new file mode 100644
index 000000000..c77c3740d
--- /dev/null
+++ b/spec/services/operations/transfers_spec.rb
@@ -0,0 +1,42 @@
+require 'spec_helper'
+
+RSpec.describe Operations::Transfers do
+ describe 'create' do
+ let(:operation) {
+ Operations::Transfers.create(
+ from: from,
+ to: to,
+ transfer_params: {}
+ )
+ }
+
+ context 'when there is one source and many targets' do
+ let(:from) { [1] }
+ let(:to) { [2, 3] }
+
+ it 'instantiates a OneToMany operation' do
+ expect(operation).to be_a(Operations::Transfers::OneToMany)
+ end
+ end
+
+
+ context 'when there many sources and one target' do
+ let(:from) { [1, 2] }
+ let(:to) { [3] }
+
+ it 'instantiates a ManyToOne operation' do
+ expect(operation).to be_a(Operations::Transfers::ManyToOne)
+ end
+ end
+
+ context 'when weird shit is passed' do
+ let(:from) { [] }
+ let(:to) { [] }
+
+ it do
+ expect { operation }.to raise_error(ArgumentError)
+ end
+ end
+ end
+end
+
diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb
index 7a75c877f..44cd48558 100644
--- a/spec/spec_helper.rb
+++ b/spec/spec_helper.rb
@@ -10,7 +10,7 @@
require 'capybara/rspec'
require 'database_cleaner'
require 'fabrication'
-require 'chromedriver-helper'
+require 'webdrivers'
require 'selenium/webdriver'
require 'faker'
require 'shoulda/matchers'