Skip to content

multi transfers #460

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
May 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions app/assets/javascripts/application.js.coffee
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#= require give_time
#= require tags
#= require mobile_app_libs
#= require multi_select2

$(document).on 'click', 'a[data-popup]', (event) ->
window.open($(this).attr('href'), 'popup', 'width=600,height=600')
Expand Down
36 changes: 36 additions & 0 deletions app/assets/javascripts/give_time.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
$( document ).ready(function () {
jQuery.validator.addMethod(
"either-hours-minutes-informed",
function (value, element) {
return Boolean($("#transfer_hours").val() || $("#transfer_minutes").val());
},
"Hours or minutes should be informed"
);

function submitHandler(form) {
// just submit when fields are not visible, in order to not break the multi
// transfer wizard
if (!$("#transfer_hours").is(":visible")) {
form.submit();
return;
}

var amount = $("#transfer_hours").val() * 3600 + $("#transfer_minutes").val() * 60;
$("#transfer_amount").val(amount);

if (amount == 0) {
$(form).find('.js-error-amount').removeClass('invisible').show();
return;
}

if ($(form).valid()) {
form.submit();
}
}

var config = {
submitHandler: submitHandler
};

$("#multi_transfer, #new_transfer").validate(config);
})
18 changes: 0 additions & 18 deletions app/assets/javascripts/give_time.js.coffee

This file was deleted.

32 changes: 32 additions & 0 deletions app/assets/javascripts/multi_select2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/**
* A setup function to make select2 jquery plugin play as an ordinary form when
* using the "multiple" flag.
*
* Also allows options to be disabled.
*
*
* @param {String} selector - CSS selector pointing to a <select /> element
* @param {Object} options
* @param {Array<String>} options.value - initial value
* @param {Array<String>} options.disabledOptions - disabled option values to disable
* @param {Array<String>} options.noMatchesFormat - i18n'd string to show when no matches found when filtering
*/
window.initializeSelect2 = function(selector, options) {
var $select = $(selector);

var optionsToDisableSelector = (options.disabledOptions || []).map(function (optionId) {
return "option[value=" + optionId + "]";
}).join(',');

var $options = $select.find(optionsToDisableSelector);
$options.attr('disabled', 'disabled');

$select.select2({
formatNoMatches: function () {
return options.noMatchesFormat
}
});

$select.val(options.value.length === 0 ? null : options.value);
$select.trigger('change');
}
16 changes: 16 additions & 0 deletions app/controllers/concerns/with_transfer_params.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
module WithTransferParams
def transfer_params
permitted_transfer_params = [
:destination,
:amount,
:reason,
:post_id,
]

permitted_transfer_params.push(:source) if admin?

params.
require(:transfer).
permit(*permitted_transfer_params)
end
end
89 changes: 89 additions & 0 deletions app/controllers/multi_transfers_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
class MultiTransfersController < ApplicationController
include WithTransferParams

STEPS = [
'select_type',
'select_source',
'select_target',
'set_params',
'confirm'
]

def step
authorize :multi_transfer

set_steps_info
propagate_params
prepare_template_vars
end

def create
authorize :multi_transfer

operation = Operations::Transfers.create(
from: params[:from],
to: params[:to],
transfer_params: params[:transfer].to_hash
)
operation.perform
end

private

def set_steps_info
@steps = STEPS
@step_name = step_name
@form_action = if @step_name == @steps.last
multi_transfers_create_path
else
multi_transfers_step_path(step: next_step)
end
@is_last_step = is_last_step
end

def propagate_params
@type_of_transfer = (params[:type_of_transfer] || :many_to_one).to_sym
@from = params[:from] || []
@to = params[:to] || []

@transfer_amount = params[:transfer] && params[:transfer][:amount]
@transfer_post_id = params[:transfer] && params[:transfer][:post_id]
@transfer_reason = params[:transfer] && params[:transfer][:reason]
@transfer_minutes = params[:transfer] && params[:transfer][:minutes]
@transfer_hours = params[:transfer] && params[:transfer][:hours]
end

def prepare_template_vars
@accounts = [current_organization.account] + current_organization.member_accounts.merge(Member.active)

if @type_of_transfer == :many_to_one && @to.length == 1
@target_accountable = Account.find(@to.first).accountable
end

@should_render_offer_selector = (
@type_of_transfer.to_sym == :many_to_one &&
@target_accountable &&
@target_accountable.offers.length > 0
)

@from_names = Account.find(@from).map(&:accountable).map(&:to_s)
@to_names = Account.find(@to).map(&:accountable).map(&:to_s)
@post_title = @transfer_post_id && Post.find(@transfer_post_id).title
end

def step_index
params[:step].to_i - 1
end

def next_step
params[:step].to_i + 1
end

def step_name
STEPS[step_index]
end

def is_last_step
step_index == STEPS.length - 1
end
end
12 changes: 2 additions & 10 deletions app/controllers/transfers_controller.rb
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
class TransfersController < ApplicationController
include WithTransferParams

def create
@source = find_source
@account = Account.find(transfer_params[:destination])
Expand Down Expand Up @@ -64,14 +66,4 @@ def redirect_target
raise ArgumentError
end
end

def transfer_params
params.
require(:transfer).
permit(:destination,
:amount,
:reason,
:post_id,
*[*(:source if admin?)])
end
end
2 changes: 1 addition & 1 deletion app/models/transfer.rb
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# A time transfer between accounts.
#
# When an amount of time is to be transferred between two accounts, a
# Transfer object should be created with the followinf attributes:
# Transfer object should be created with the following attributes:
#
# source: id or instance of the source account
# destination: id or instance of the destination account
Expand Down
9 changes: 9 additions & 0 deletions app/policies/multi_transfer_policy.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
class MultiTransferPolicy < ApplicationPolicy
def step?
user.admins?(organization)
end

def create?
user.admins?(organization)
end
end
29 changes: 29 additions & 0 deletions app/services/operations/transfers.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
module Operations
module Transfers
# Transfer operation factory. Creates either a OneToOne/ManyToOne/OneToMany
# depending on the length of the "from" and "to" arrays
def create(from:, to:, transfer_params:)
transfer_klass(from: from, to: to).new(
from: from,
to: to,
transfer_params: transfer_params
)
end

def transfer_klass(from:, to:)
case
when from.length == 1 && to.length == 1
OneToOne
when from.length > 1 && to.length == 1
ManyToOne
when from.length == 1 && to.length > 1
OneToMany
else
raise ArgumentError, "Unknown type of transfer"
end
end

module_function :transfer_klass, :create
private_class_method :transfer_klass
end
end
50 changes: 50 additions & 0 deletions app/services/operations/transfers/base.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
module Operations
module Transfers
# Base class for the transfer operations. Each subclass must implement the
# "transfers" method, and call "build_transfer" any number of times
# depending on the desired behavior.
class Base
def initialize(transfer_params:, from:, to:)
self.transfer_params = transfer_params
self.from = from
self.to = to
end

def perform
::ActiveRecord::Base.transaction do
transfers.each do |transfer|
persister = ::Persister::TransferPersister.new(transfer)
raise ActiveRecord::Rollback unless persister.save
end
end
end

protected

attr_reader :transfer_params, :from, :to

private

attr_writer :from, :to, :transfer_params

# Template method, use "build_transfer" method to ease the instantiation
# of Transfers with the correct params.
#
# @return [Array<Transfer>]
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
24 changes: 24 additions & 0 deletions app/services/operations/transfers/many_to_one.rb
Original file line number Diff line number Diff line change
@@ -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
25 changes: 25 additions & 0 deletions app/services/operations/transfers/one_to_many.rb
Original file line number Diff line number Diff line change
@@ -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

22 changes: 22 additions & 0 deletions app/services/operations/transfers/one_to_one.rb
Original file line number Diff line number Diff line change
@@ -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
Loading