Skip to content

Inter-organizational Contact and Time Transfers #792

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 17 commits into
base: interbank-offer-visibility
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
59 changes: 27 additions & 32 deletions app/assets/javascripts/application/organizations_filter.js
Original file line number Diff line number Diff line change
@@ -1,33 +1,28 @@
// app/assets/javascripts/application/organization_filter.js
$(function() {
// Manejar cambios en las casillas de organizaciones
$(document).on('change', '.organization-checkbox', function() {
// Obtener valores actuales de la URL
var searchParams = new URLSearchParams(window.location.search);
var cat = searchParams.get('cat');
var q = searchParams.get('q');
var tag = searchParams.get('tag');

var form = $(this).closest('form');

// Mantener parámetros actuales
if (cat) {
if (form.find('input[name="cat"]').length === 0) {
form.append('<input type="hidden" name="cat" value="' + cat + '">');
}
}

if (q) {
form.find('input[name="q"]').val(q);
}

if (tag) {
if (form.find('input[name="tag"]').length === 0) {
form.append('<input type="hidden" name="tag" value="' + tag + '">');
}
}

// Enviar el formulario
form.submit();
});
});
$(document).on('change', '.organization-checkbox', function() {
var searchParams = new URLSearchParams(window.location.search);
var cat = searchParams.get('cat');
var q = searchParams.get('q');
var tag = searchParams.get('tag');

var form = $(this).closest('form');

if (cat) {
if (form.find('input[name="cat"]').length === 0) {
form.append('<input type="hidden" name="cat" value="' + cat + '">');
}
}

if (q) {
form.find('input[name="q"]').val(q);
}

if (tag) {
if (form.find('input[name="tag"]').length === 0) {
form.append('<input type="hidden" name="tag" value="' + tag + '">');
}
}

form.submit();
});
});
6 changes: 4 additions & 2 deletions app/controllers/offers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,9 @@ def model
def show
super

member = @offer.user.members.find_by(organization: current_organization)
@destination_account = member.account if member
if @offer.user
member = @offer.user.members.find_by(organization: current_organization)
@destination_account = member.account if member
end
end
end
23 changes: 23 additions & 0 deletions app/controllers/posts_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,16 @@ class PostsController < ApplicationController
def index
context = model.active.of_active_members

if current_user.present? && current_organization.present?
if params[:show_allied].present?
allied_org_ids = current_organization.allied_organizations.pluck(:id)
org_ids = [current_organization.id] + allied_org_ids
context = context.by_organizations(org_ids)
elsif !params[:org].present?
context = context.by_organization(current_organization.id)
end
end

posts = apply_scopes(context)
posts = posts.search_by_query(params[:q]) if params[:q].present?
posts = posts.page(params[:page]).per(25)
Expand Down Expand Up @@ -65,6 +75,19 @@ def destroy
redirect_to send("#{resources}_path") if post.update!(active: false)
end

def contact
@post = Post.find(params[:id])

if current_user && current_organization != @post.organization && current_user.active?(current_organization)
OrganizationNotifier.contact_request(@post, current_user, current_organization).deliver_later
flash[:notice] = t('posts.contact.success')
else
flash[:error] = t('posts.contact.error')
end

redirect_to @post
end

private

def resource
Expand Down
88 changes: 86 additions & 2 deletions app/controllers/transfers_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,13 @@ class TransfersController < ApplicationController

def create
@source = find_source

if params[:cross_bank] == "true" && params[:post_id].present?
Copy link
Preview

Copilot AI Apr 18, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nitpick] Relying on a string comparison for the 'cross_bank' parameter could be error-prone if the parameter type changes. Consider normalizing the parameter to a boolean value before performing the comparison.

Suggested change
if params[:cross_bank] == "true" && params[:post_id].present?
if normalize_to_boolean(params[:cross_bank]) && params[:post_id].present?

Copilot uses AI. Check for mistakes.

post = Post.find(params[:post_id])
create_cross_bank_transfer(post)
return
end

@account = Account.find(transfer_params[:destination])

transfer = Transfer.new(
Expand All @@ -23,16 +30,21 @@ def new
current_organization,
current_user,
params[:offer],
params[:destination_account_id]
params[:destination_account_id],
params[:cross_bank] == "true"
)

@cross_bank = params[:cross_bank] == "true"
@offer = transfer_factory.offer

render(
:new,
locals: {
accountable: transfer_factory.accountable,
transfer: transfer_factory.build_transfer,
offer: transfer_factory.offer,
sources: transfer_factory.transfer_sources
sources: transfer_factory.transfer_sources,
cross_bank: @cross_bank
}
)
end
Expand All @@ -49,6 +61,78 @@ def delete_reason

private

def create_cross_bank_transfer(post)
transfer_factory = TransferFactory.new(
current_organization,
current_user,
post.id,
nil,
true
)

destination_organization = transfer_factory.destination_organization

@persisters = []

user_account = current_user.members.find_by(organization: current_organization).account
org_account = current_organization.account

if user_account.id != org_account.id
user_to_org_transfer = Transfer.new(
source: user_account,
destination: org_account,
amount: transfer_params[:amount],
reason: post.description,
post: post
)
@persisters << ::Persister::TransferPersister.new(user_to_org_transfer)
end

org_to_org_transfer = Transfer.new(
source: org_account,
destination: destination_organization.account,
amount: transfer_params[:amount],
reason: post.description,
post: post,
is_cross_bank: true
)
@persisters << ::Persister::TransferPersister.new(org_to_org_transfer)

member = post.user.members.find_by(organization: destination_organization)
if member && member.account
org_to_user_transfer = Transfer.new(
source: destination_organization.account,
destination: member.account,
amount: transfer_params[:amount],
reason: post.description,
post: post
)
@persisters << ::Persister::TransferPersister.new(org_to_user_transfer)
end

if persisters_saved?
redirect_to post, notice: t('transfers.cross_bank.success')
else
@persisters.each do |persister|
persister.transfer.destroy if persister.transfer.persisted?
end
redirect_back fallback_location: post, alert: @error_messages || t('transfers.cross_bank.error')
end
end

def persisters_saved?
@error_messages = []

@persisters.each do |persister|
unless persister.save
@error_messages << persister.transfer.errors.full_messages.to_sentence
return false
end
end

true
end

def find_source
if admin?
Account.find(transfer_params[:source])
Expand Down
14 changes: 14 additions & 0 deletions app/mailers/organization_notifier.rb
Original file line number Diff line number Diff line change
Expand Up @@ -56,4 +56,18 @@ def no_membership_warning(user)
)
end
end

def contact_request(post, requester, requester_organization)
@post = post
@requester = requester
@requester_organization = requester_organization
@offerer = post.user

I18n.with_locale(@offerer.locale) do
mail(
to: @offerer.email,
subject: t('organization_notifier.contact_request.subject', post: @post.title)
)
end
end
end
6 changes: 6 additions & 0 deletions app/models/organization.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ def rejected_alliances
source_alliances.rejected.or(target_alliances.rejected)
end

def allied_organizations
source_org_ids = source_alliances.accepted.pluck(:target_organization_id)
target_org_ids = target_alliances.accepted.pluck(:source_organization_id)
Organization.where(id: source_org_ids + target_org_ids)
end

def ensure_reg_number_seq!
update_column(:reg_number_seq, members.maximum(:member_uid))
end
Expand Down
64 changes: 62 additions & 2 deletions app/models/transfer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,45 @@
# account, so the total sum of the system is zero
#
class Transfer < ApplicationRecord
attr_accessor :source, :destination, :amount, :hours, :minutes
attr_accessor :source, :destination, :amount, :hours, :minutes, :is_cross_bank, :meta

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: Account.find(destination_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)
end

def source_id
Expand All @@ -39,4 +64,39 @@ 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
Loading