<% if mv.transfer&.post&.active? %>
<%= link_to mv.transfer.post, offer_path(mv.transfer.post) %>
+ <% elsif is_bank_to_bank_transfer?(mv.transfer) %>
+ <%= t("organizations.transfers.bank_transfer") %>
<% else %>
<%= mv.transfer.post %>
<% end %>
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 738372fd..dad7aa7b 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -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:
@@ -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
@@ -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
@@ -637,4 +650,4 @@ en:
last: Last
next: Next
previous: Previous
- truncate: Truncate
\ No newline at end of file
+ truncate: Truncate
diff --git a/config/locales/es.yml b/config/locales/es.yml
index 69a48217..400f33c9 100644
--- a/config/locales/es.yml
+++ b/config/locales/es.yml
@@ -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
@@ -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:
diff --git a/config/routes.rb b/config/routes.rb
index 54715059..e5c46d69 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -40,6 +40,8 @@
end
get :select_organization, to: 'organizations#select_organization'
+ get 'organization_transfers/new', to: 'organization_transfers#new', as: :new_organization_to_organization_transfer
+ post 'organization_transfers', to: 'organization_transfers#create', as: :organization_to_organization_transfers
resources :organization_alliances, only: [:index, :create, :update, :destroy]
resources :users, concerns: :accountable, except: :destroy, :path => "members" do
diff --git a/spec/controllers/organization_transfers_controller_spec.rb b/spec/controllers/organization_transfers_controller_spec.rb
new file mode 100644
index 00000000..309cad53
--- /dev/null
+++ b/spec/controllers/organization_transfers_controller_spec.rb
@@ -0,0 +1,161 @@
+RSpec.describe OrganizationTransfersController do
+ let(:source_organization) { Fabricate(:organization) }
+ let(:target_organization) { Fabricate(:organization) }
+ let(:manager) { Fabricate(:member, organization: source_organization, manager: true) }
+ let(:user) { manager.user }
+
+ let!(:alliance) do
+ OrganizationAlliance.create!(
+ source_organization: source_organization,
+ target_organization: target_organization,
+ status: "accepted"
+ )
+ end
+
+ before do
+ login(user)
+ session[:current_organization_id] = source_organization.id
+ controller.instance_variable_set(:@current_organization, source_organization)
+ end
+
+ describe "GET #new" do
+ it "assigns a new transfer and sets organizations" do
+ get :new, params: { destination_organization_id: target_organization.id }
+
+ expect(response).to be_successful
+ expect(assigns(:transfer)).to be_a_new(Transfer)
+ expect(assigns(:source_organization)).to eq(source_organization)
+ expect(assigns(:destination_organization)).to eq(target_organization)
+ end
+
+ context "when user is not a manager" do
+ let(:regular_member) { Fabricate(:member, organization: source_organization) }
+
+ before do
+ login(regular_member.user)
+ end
+
+ it "redirects to root path" do
+ get :new, params: { destination_organization_id: target_organization.id }
+
+ expect(response).to redirect_to(root_path)
+ expect(flash[:alert]).to eq(I18n.t('organization_alliances.not_authorized'))
+ end
+ end
+
+ context "when destination organization not found" do
+ it "redirects to organizations path" do
+ get :new, params: { destination_organization_id: 999 }
+
+ expect(response).to redirect_to(organizations_path)
+ expect(flash[:alert]).to eq(I18n.t('application.tips.user_not_found'))
+ end
+ end
+
+ context "when no alliance exists between organizations" do
+ let(:other_organization) { Fabricate(:organization) }
+
+ it "redirects to organizations path" do
+ get :new, params: { destination_organization_id: other_organization.id }
+
+ expect(response).to redirect_to(organizations_path)
+ expect(flash[:alert]).to eq(I18n.t('activerecord.errors.models.transfer.attributes.base.no_alliance_between_organizations'))
+ end
+ end
+
+ context "when alliance is pending" do
+ let(:pending_organization) { Fabricate(:organization) }
+ let!(:pending_alliance) do
+ OrganizationAlliance.create!(
+ source_organization: source_organization,
+ target_organization: pending_organization,
+ status: "pending"
+ )
+ end
+
+ it "redirects to organizations path" do
+ get :new, params: { destination_organization_id: pending_organization.id }
+
+ expect(response).to redirect_to(organizations_path)
+ expect(flash[:alert]).to eq(I18n.t('activerecord.errors.models.transfer.attributes.base.no_alliance_between_organizations'))
+ end
+ end
+ end
+
+ describe "POST #create" do
+ context "with valid parameters" do
+ it "creates a new transfer and redirects to organization path" do
+ persister_double = instance_double(::Persister::TransferPersister, save: true)
+ allow(::Persister::TransferPersister).to receive(:new).and_return(persister_double)
+
+ expect {
+ post :create, params: {
+ destination_organization_id: target_organization.id,
+ transfer: { hours: 2, minutes: 30, reason: "Testing alliance", amount: 150 }
+ }
+ }.not_to raise_error
+
+ expect(response).to redirect_to(organization_path(target_organization))
+ expect(flash[:notice]).to eq(I18n.t('organizations.transfers.create.success'))
+ end
+ end
+
+ context "with invalid parameters" do
+ it "renders the new template with errors" do
+ transfer_double = instance_double(Transfer)
+ persister_double = instance_double(::Persister::TransferPersister, save: false)
+
+ allow(Transfer).to receive(:new).and_return(transfer_double)
+ allow(transfer_double).to receive(:source=)
+ allow(transfer_double).to receive(:destination=)
+ allow(transfer_double).to receive(:post=)
+ error_messages = ["Amount can't be zero"]
+ allow(transfer_double).to receive(:errors).and_return(
+ instance_double("ActiveModel::Errors", full_messages: error_messages)
+ )
+ allow(::Persister::TransferPersister).to receive(:new).and_return(persister_double)
+
+ expect(controller).to receive(:render).with(:new)
+
+ post :create, params: {
+ destination_organization_id: target_organization.id,
+ transfer: { hours: 0, minutes: 0, reason: "", amount: 0 }
+ }
+
+ expect(flash[:error]).to include("Amount can't be zero")
+ end
+ end
+
+ context "when user is not a manager" do
+ let(:regular_member) { Fabricate(:member, organization: source_organization) }
+
+ before do
+ login(regular_member.user)
+ end
+
+ it "redirects to root path" do
+ post :create, params: {
+ destination_organization_id: target_organization.id,
+ transfer: { hours: 1, minutes: 0, reason: "Test", amount: 60 }
+ }
+
+ expect(response).to redirect_to(root_path)
+ expect(flash[:alert]).to eq(I18n.t('organization_alliances.not_authorized'))
+ end
+ end
+
+ context "when no alliance exists between organizations" do
+ let(:other_organization) { Fabricate(:organization) }
+
+ it "redirects to organizations path" do
+ post :create, params: {
+ destination_organization_id: other_organization.id,
+ transfer: { hours: 1, minutes: 0, reason: "Test", amount: 60 }
+ }
+
+ expect(response).to redirect_to(organizations_path)
+ expect(flash[:alert]).to eq(I18n.t('activerecord.errors.models.transfer.attributes.base.no_alliance_between_organizations'))
+ end
+ end
+ end
+end
diff --git a/spec/controllers/transfers_controller_cross_bank_spec.rb b/spec/controllers/transfers_controller_cross_bank_spec.rb
index 24cc99c2..ea1fcd48 100644
--- a/spec/controllers/transfers_controller_cross_bank_spec.rb
+++ b/spec/controllers/transfers_controller_cross_bank_spec.rb
@@ -48,5 +48,17 @@
expect(response).to redirect_to(offer)
expect(flash[:notice]).to eq(I18n.t('transfers.cross_bank.success'))
end
+
+ context 'when there is no accepted alliance between organizations' do
+ before do
+ alliance.update(status: "pending")
+ end
+
+ it 'redirects back with an error message about missing alliance' do
+ request!
+ expect(response).to redirect_to(request.referer || offer)
+ expect(flash[:alert]).to eq(I18n.t('transfers.cross_bank.no_alliance'))
+ end
end
end
+end
diff --git a/spec/helpers/transfers_helper_spec.rb b/spec/helpers/transfers_helper_spec.rb
index adb2048a..09830d59 100644
--- a/spec/helpers/transfers_helper_spec.rb
+++ b/spec/helpers/transfers_helper_spec.rb
@@ -15,4 +15,67 @@
expect(helper.accounts_from_movements(transfer, with_links: true)).to include(//)
end
end
+
+ describe "#is_bank_to_bank_transfer?" do
+ let(:organization1) { Fabricate(:organization) }
+ let(:organization2) { Fabricate(:organization) }
+ let(:user) { Fabricate(:user) }
+ let(:member) { Fabricate(:member, organization: organization1, user: user) }
+
+ context "when transfer is between two organizations" do
+ let(:transfer) do
+ transfer = Transfer.new(
+ source: organization1.account,
+ destination: organization2.account,
+ amount: 60
+ )
+ ::Persister::TransferPersister.new(transfer).save
+ transfer
+ end
+
+ it "returns true" do
+ expect(helper.is_bank_to_bank_transfer?(transfer)).to be true
+ end
+ end
+
+ context "when transfer is from a user to an organization" do
+ let(:transfer) do
+ transfer = Transfer.new(
+ source: member.account,
+ destination: organization1.account,
+ amount: 60
+ )
+ ::Persister::TransferPersister.new(transfer).save
+ transfer
+ end
+
+ it "returns false" do
+ expect(helper.is_bank_to_bank_transfer?(transfer)).to be false
+ end
+ end
+
+ context "when transfer has a post associated" do
+ let(:post) { Fabricate(:post, organization: organization1) }
+ let(:transfer) do
+ transfer = Transfer.new(
+ source: organization1.account,
+ destination: organization2.account,
+ amount: 60,
+ post: post
+ )
+ ::Persister::TransferPersister.new(transfer).save
+ transfer
+ end
+
+ it "returns false" do
+ expect(helper.is_bank_to_bank_transfer?(transfer)).to be false
+ end
+ end
+
+ context "when transfer is nil" do
+ it "returns false" do
+ expect(helper.is_bank_to_bank_transfer?(nil)).to be false
+ end
+ end
+ end
end
diff --git a/spec/models/transfer_factory_cross_bank_spec.rb b/spec/models/transfer_factory_cross_bank_spec.rb
index 720a69bc..ea35e071 100644
--- a/spec/models/transfer_factory_cross_bank_spec.rb
+++ b/spec/models/transfer_factory_cross_bank_spec.rb
@@ -30,7 +30,6 @@
before do
allow(transfer_factory).to receive(:destination_account).and_return(dest_org.account)
- allow_any_instance_of(Transfer).to receive(:is_cross_bank=)
end
describe '#build_transfer' do
diff --git a/spec/views/offers/show.html.erb_spec.rb b/spec/views/offers/show.html.erb_spec.rb
index f886b4d3..c0267ad9 100644
--- a/spec/views/offers/show.html.erb_spec.rb
+++ b/spec/views/offers/show.html.erb_spec.rb
@@ -91,8 +91,6 @@
assign :offer, offer
render template: 'offers/show'
- # Verificar que la vista muestra el nombre de la organización
- # sin depender del formato exacto del mensaje
expect(rendered).to include(offer.organization.name)
end
end
@@ -133,8 +131,6 @@
assign :offer, offer
render template: 'offers/show'
- # Verificar que la vista muestra el nombre de la organización
- # sin depender del formato exacto del mensaje
expect(rendered).to include(offer.organization.name)
end