Skip to content

Direct Transfers Between Allied Organizations #796

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

Open
wants to merge 9 commits into
base: interorg-user-contact
Choose a base branch
from
Open
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
58 changes: 58 additions & 0 deletions app/controllers/organization_transfers_controller.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
class OrganizationTransfersController < ApplicationController
before_action :authenticate_user!
before_action :check_manager_role
before_action :set_organizations, only: [:new]
before_action :validate_alliance, only: [:new, :create]

def new
@transfer = Transfer.new
end

def create
@transfer = Transfer.new(transfer_params)
@transfer.source = current_organization.account
@transfer.destination = destination_organization.account
@transfer.post = nil
persister = ::Persister::TransferPersister.new(@transfer)

if persister.save
redirect_to organization_path(destination_organization),
notice: t('organizations.transfers.create.success')
else
set_organizations
flash.now[:error] = t('organizations.transfers.create.error', error: @transfer.errors.full_messages.to_sentence)
render :new
end
end

private

def transfer_params
params.require(:transfer).permit(:amount, :hours, :minutes, :reason)
end

def check_manager_role
unless current_user.manages?(current_organization)
redirect_to root_path, alert: t('organization_alliances.not_authorized')
end
end

def set_organizations
@source_organization = current_organization
@destination_organization = destination_organization
end

def destination_organization
@destination_organization ||= Organization.find(params[:destination_organization_id])
rescue ActiveRecord::RecordNotFound
redirect_to organizations_path, alert: t('application.tips.user_not_found')
end

def validate_alliance
alliance = current_organization.alliance_with(destination_organization)
unless alliance && alliance.accepted?
redirect_to organizations_path,
alert: t('transfers.cross_bank.no_alliance')
end
end
end
12 changes: 10 additions & 2 deletions app/controllers/transfers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,12 @@ def create_cross_bank_transfer(post)

destination_organization = transfer_factory.destination_organization

unless current_organization.alliance_with(destination_organization)&.accepted?
redirect_back fallback_location: post,
alert: t('transfers.cross_bank.no_alliance')
return
end

@persisters = []

user_account = current_user.members.find_by(organization: current_organization).account
Expand All @@ -93,8 +99,7 @@ def create_cross_bank_transfer(post)
destination: destination_organization.account,
amount: transfer_params[:amount],
reason: post.description,
post: post,
is_cross_bank: true
post: post
)
@persisters << ::Persister::TransferPersister.new(org_to_org_transfer)

Expand All @@ -108,6 +113,9 @@ def create_cross_bank_transfer(post)
post: post
)
@persisters << ::Persister::TransferPersister.new(org_to_user_transfer)
else
redirect_back fallback_location: post, alert: t('transfers.cross_bank.error')
return
end

if persisters_saved?
Expand Down
11 changes: 11 additions & 0 deletions app/helpers/transfers_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,15 @@ def accounts_from_movements(transfer, with_links: false)
end
end
end

def is_bank_to_bank_transfer?(transfer)
return false unless transfer

source_account = transfer.movements.find_by('amount < 0')&.account
destination_account = transfer.movements.find_by('amount > 0')&.account

source_account&.accountable_type == 'Organization' &&
destination_account&.accountable_type == 'Organization' &&
transfer.post.nil?
end
end
66 changes: 4 additions & 62 deletions app/models/transfer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,45 +11,20 @@
# account, so the total sum of the system is zero
#
class Transfer < ApplicationRecord
attr_accessor :source, :destination, :amount, :hours, :minutes, :is_cross_bank, :meta
attr_accessor :source, :destination, :amount, :hours, :minutes

belongs_to :post, optional: true
has_many :movements, dependent: :destroy
has_many :events, dependent: :destroy

validates :amount, numericality: { greater_than: 0 }
validate :different_source_and_destination
validate :validate_organizations_alliance, if: -> { is_cross_bank && meta.present? }

after_create :make_movements

def make_movements
if is_cross_bank && meta.present?
make_cross_bank_movements
else
movements.create(account: Account.find(source_id), amount: -amount.to_i, created_at: created_at)
movements.create(account: Account.find(destination_id), amount: amount.to_i, created_at: created_at)
end
end

def make_cross_bank_movements
source_organization_id = meta[:source_organization_id]
destination_organization_id = meta[:destination_organization_id]
final_destination_user_id = meta[:final_destination_user_id]

source_organization = Organization.find(source_organization_id)
destination_organization = Organization.find(destination_organization_id)
final_user = User.find(final_destination_user_id)
final_member = final_user.members.find_by(organization: destination_organization)

movements.create(account: Account.find(source_id), amount: -amount.to_i, created_at: created_at)
movements.create(account: source_organization.account, amount: amount.to_i, created_at: created_at)

movements.create(account: source_organization.account, amount: -amount.to_i, created_at: created_at)
movements.create(account: destination_organization.account, amount: amount.to_i, created_at: created_at)

movements.create(account: destination_organization.account, amount: -amount.to_i, created_at: created_at)
movements.create(account: final_member.account, amount: amount.to_i, created_at: created_at)
movements.create(account: Account.find(destination_id), amount: amount.to_i, created_at: created_at)
end

def source_id
Expand All @@ -60,43 +35,10 @@ def destination_id
destination.respond_to?(:id) ? destination.id : destination
end

private

def different_source_and_destination
return unless source == destination
errors.add(:base, :same_account)
end

def cross_bank?
movements.count > 2
end

def related_account_for(movement)
return nil unless movement.transfer == self

movements_in_order = movements.order(:id)
current_index = movements_in_order.index(movement)
return nil unless current_index

if movement.amount > 0 && current_index > 0
movements_in_order[current_index - 1].account
elsif movement.amount < 0 && current_index < movements_in_order.length - 1
movements_in_order[current_index + 1].account
end
end

private

def validate_organizations_alliance
return unless meta[:source_organization_id] && meta[:destination_organization_id]

source_org = Organization.find_by(id: meta[:source_organization_id])
dest_org = Organization.find_by(id: meta[:destination_organization_id])

return unless source_org && dest_org

alliance = source_org.alliance_with(dest_org)

unless alliance && alliance.accepted?
errors.add(:base, :no_alliance_between_organizations)
end
end
end
18 changes: 5 additions & 13 deletions app/models/transfer_factory.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,14 @@ def initialize(current_organization, current_user, offer_id, destination_account
end

def offer
if offer_id.present?
Offer.find_by_id(offer_id)
end
@offer ||= Offer.find_by_id(offer_id) if offer_id.present?
end

def build_transfer
transfer = Transfer.new(source: source)
transfer = Transfer.new(source: source, destination: destination_account.id)

if cross_bank && offer && offer.organization != current_organization
transfer.destination = destination_account.id
transfer.post = offer
transfer.is_cross_bank = true
else
transfer.destination = destination_account.id
transfer.post = offer unless for_organization?
end
transfer.post = offer if (cross_bank && offer && offer.organization != current_organization) ||
(offer && !for_organization?)

transfer
end
Expand Down Expand Up @@ -63,7 +55,7 @@ def source
end

def for_organization?
destination_account.try(:accountable).class == Organization
destination_account&.accountable.is_a?(Organization)
end

def admin?
Expand Down
33 changes: 33 additions & 0 deletions app/views/organization_transfers/new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<h1>
<%= t 'organizations.transfers.new.title' %>
</h1>
<div class="alert alert-info">
<%= t 'organizations.transfers.new.description',
source_organization: @source_organization.name,
destination_organization: @destination_organization.name %>
</div>
<%= simple_form_for @transfer, url: organization_to_organization_transfers_path(destination_organization_id: @destination_organization.id) do |f| %>
<div class="form-inputs">
<%= f.input :hours,
as: :integer,
input_html: {
min: 0,
"data-rule-either-hours-minutes-informed" => "true"
} %>
<%= f.input :minutes,
as: :integer,
input_html: {
min: 0,
max: 59,
step: 15,
"data-rule-either-hours-minutes-informed" => "true",
"data-rule-range" => "[0,59]"
} %>
<%= f.input :amount, as: :hidden %>
<%= f.input :reason %>
</div>
<div class="form-actions">
<%= f.button :submit, t('organizations.transfers.new.submit'), class: "btn btn-primary" %>
<label class="js-error-amount form-label error invisible"><%= t "transfers.new.error_amount" %></label>
</div>
<% end %>
17 changes: 16 additions & 1 deletion app/views/organizations/show.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -100,8 +100,23 @@
<% end %>
</li>
<% end %>
<% if current_user&.manages?(current_organization) &&
@organization != current_organization &&
current_organization.alliance_with(@organization)&.accepted? %>
<li class="nav-item">
<%= link_to new_organization_to_organization_transfer_path(destination_organization_id: @organization.id), class: 'nav-link' do %>
<%= glyph :time %>
<%= t "organizations.transfers.bank_to_bank_transfer" %>
<% end %>
</li>
<% end %>
</ul>
<%= render "organizations/petition_button", organization: @organization %>
<div class="d-flex align-items-center mt-2">
<%= render "organizations/petition_button", organization: @organization %>
<div class="ms-2">
<%= render "organizations/alliance_button", organization: @organization %>
</div>
</div>
</div>
</div>

Expand Down
11 changes: 3 additions & 8 deletions app/views/shared/_movements.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,7 @@
</td>
<td>
<%
display_account = nil

if mv.transfer&.cross_bank?
display_account = mv.transfer.related_account_for(mv)
display_account ||= mv.other_side.account
else
display_account = mv.other_side.account
end
display_account = mv.other_side.account
%>

<% if display_account.accountable.present? %>
Expand All @@ -48,6 +41,8 @@
<td>
<% if mv.transfer&.post&.active? %>
<%= link_to mv.transfer.post, offer_path(mv.transfer.post) %>
<% elsif is_bank_to_bank_transfer?(mv.transfer) %>
<span class="badge bg-info"><%= t("organizations.transfers.bank_transfer") %></span>
<% else %>
<%= mv.transfer.post %>
<% end %>
Expand Down
19 changes: 16 additions & 3 deletions config/locales/en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -412,7 +412,7 @@ en:
contact_request:
subject: "Contact request for your %{post}"
greeting: "Hello %{name},"
message: "%{requester} from %{organization} time bank is interested in your %{post}."
message: "%{requester} from %{organization} organization is interested in your %{post}."
requester_info: "Here is their contact information"
closing: "If you are interested, please contact them directly using the provided information."
organizations:
Expand All @@ -427,6 +427,17 @@ en:
show:
contact_information: Contact information
join_timebank: Don't hesitate to contact the time bank to join it or ask any questions.
transfers:
bank_to_bank_transfer: "Transfer time between organizations"
bank_transfer: "Organization to Organization transfer"
new:
title: "Transfer between Organizations"
submit: "Execute transfer"
description: "Transfer time from %{source_organization} to %{destination_organization}"
reason_hint: "Optional: Describe the reason for this bank-to-bank transfer"
create:
success: "Organization to Organization transfer completed successfully"
error: "Error processing the transfer: %{error}"
pages:
about:
app-mobile: Mobile App
Expand Down Expand Up @@ -569,8 +580,10 @@ en:
new:
error_amount: Time must be greater than 0
cross_bank:
success: "Cross-organization transfer completed successfully"
error: "Error creating cross-bank transfer"
no_alliance: "Cannot perform cross-bank transfers: no active alliance exists between organizations"
info: "This is a time transfer to a member who belongs to %{organization}. The time will be transferred through both organizations."
success: "Cross-organization transfer completed successfully."
users:
avatar:
change_your_image: Change your image
Expand Down Expand Up @@ -637,4 +650,4 @@ en:
last: Last
next: Next
previous: Previous
truncate: Truncate
truncate: Truncate
12 changes: 12 additions & 0 deletions config/locales/es.yml
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,17 @@ es:
show:
contact_information: Información de contacto
join_timebank: No dudes en contactar con el Banco de Tiempo para unirte o para resolver dudas.
transfers:
bank_to_bank_transfer: "Transferir tiempo entre organizaciones"
bank_transfer: "Transferencia entre organizaciones"
new:
title: "Transferencia entre organizaciones"
submit: "Crear transferencia"
description: "Transferir tiempo desde %{source_organization} a %{destination_organization}"
reason_hint: "Opcional: Describe el motivo de esta transferencia organizaciones"
create:
success: "Transferencia entre organizaciones realizada con éxito"
error: "Error al realizar la transferencia: %{error}"
pages:
about:
app-mobile: App Móvil
Expand Down Expand Up @@ -572,6 +583,7 @@ es:
info: "Esta es una transferencia de tiempo a un miembro perteneciente a %{organization}. El tiempo se transferirá a través de ambas organizaciones."
success: "Transferencia entre organizaciones completada con éxito."
error: "Ha ocurrido un error al procesar la transferencia entre organizaciones."
no_alliance: "No se pueden realizar transferencias entre organizaciones: no existe una alianza activa entre ellas."

users:
avatar:
Expand Down
Loading