From a35ceabccd35e18a6e4fd6ffc97953469d17ca36 Mon Sep 17 00:00:00 2001 From: gmartincor Date: Fri, 18 Apr 2025 14:06:47 +0200 Subject: [PATCH 01/17] Transfer time Between diferents organizations --- app/controllers/offers_controller.rb | 6 +- app/controllers/posts_controller.rb | 13 +++++ app/controllers/transfers_controller.rb | 38 +++++++++++- app/mailers/organization_notifier.rb | 14 +++++ app/models/transfer.rb | 28 ++++++++- app/models/transfer_factory.rb | 58 ++++++++++++------- app/views/inquiries/show.html.erb | 14 ++++- app/views/offers/show.html.erb | 25 +++++++- .../contact_request.html.erb | 26 +++++++++ app/views/shared/_post.html.erb | 12 +++- app/views/transfers/new.html.erb | 23 ++++++-- config/locales/en.yml | 15 +++++ config/locales/es.yml | 15 +++++ config/routes.rb | 11 +++- ...0031_add_cross_bank_fields_to_transfers.rb | 5 ++ 15 files changed, 266 insertions(+), 37 deletions(-) create mode 100644 app/views/organization_notifier/contact_request.html.erb create mode 100644 db/migrate/20250418100031_add_cross_bank_fields_to_transfers.rb diff --git a/app/controllers/offers_controller.rb b/app/controllers/offers_controller.rb index 232ba52e1..e3895f6ea 100644 --- a/app/controllers/offers_controller.rb +++ b/app/controllers/offers_controller.rb @@ -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 diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 4ef33dea4..906a93342 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -65,6 +65,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_now + flash[:notice] = t('posts.contact.success') + else + flash[:error] = t('posts.contact.error') + end + + redirect_to @post + end + private def resource diff --git a/app/controllers/transfers_controller.rb b/app/controllers/transfers_controller.rb index e65506566..7098453d0 100644 --- a/app/controllers/transfers_controller.rb +++ b/app/controllers/transfers_controller.rb @@ -3,6 +3,13 @@ class TransfersController < ApplicationController def create @source = find_source + + if params[:cross_bank] == "true" && params[:post_id].present? + post = Post.find(params[:post_id]) + create_cross_bank_transfer(post) + return + end + @account = Account.find(transfer_params[:destination]) transfer = Transfer.new( @@ -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 @@ -49,6 +61,28 @@ def delete_reason private + def create_cross_bank_transfer(post) + transfer_factory = TransferFactory.new( + current_organization, + current_user, + post.id, + nil, + true + ) + + transfer = transfer_factory.build_transfer + transfer.amount = transfer_params[:amount] + transfer.reason = transfer_params[:reason] + + persister = ::Persister::TransferPersister.new(transfer) + + if persister.save + redirect_to post, notice: t('transfers.cross_bank.success') + else + redirect_back fallback_location: post, alert: transfer.errors.full_messages.to_sentence + end + end + def find_source if admin? Account.find(transfer_params[:source]) diff --git a/app/mailers/organization_notifier.rb b/app/mailers/organization_notifier.rb index 2625b98e3..d2420731d 100644 --- a/app/mailers/organization_notifier.rb +++ b/app/mailers/organization_notifier.rb @@ -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 diff --git a/app/models/transfer.rb b/app/models/transfer.rb index 41e4db8ca..967496c1b 100644 --- a/app/models/transfer.rb +++ b/app/models/transfer.rb @@ -11,7 +11,7 @@ # 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 @@ -23,8 +23,32 @@ class Transfer < ApplicationRecord 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 diff --git a/app/models/transfer_factory.rb b/app/models/transfer_factory.rb index a093299cd..f8962a8b1 100644 --- a/app/models/transfer_factory.rb +++ b/app/models/transfer_factory.rb @@ -1,24 +1,41 @@ class TransferFactory - def initialize(current_organization, current_user, offer_id, destination_account_id) + def initialize(current_organization, current_user, offer_id, destination_account_id = nil, cross_bank = false) @current_organization = current_organization @current_user = current_user @offer_id = offer_id @destination_account_id = destination_account_id + @cross_bank = cross_bank end # Returns the offer that is the subject of the transfer # # @return [Maybe] def offer - current_organization.offers.find_by_id(offer_id) + if offer_id.present? + Offer.find_by_id(offer_id) + end end # Returns a new instance of Transfer with the data provided # # @return [Transfer] def build_transfer - transfer = Transfer.new(source: source, destination: destination_account.id) - transfer.post = offer unless for_organization? + transfer = Transfer.new(source: source) + + if cross_bank && offer && offer.organization != current_organization + transfer.destination = destination_organization_account.id + transfer.post = offer + transfer.is_cross_bank = true + transfer.meta = { + source_organization_id: current_organization.id, + destination_organization_id: offer.organization.id, + final_destination_user_id: offer.user.id + } + else + transfer.destination = destination_account.id + transfer.post = offer unless for_organization? + end + transfer end @@ -32,49 +49,50 @@ def transfer_sources end def accountable - @accountable ||= destination_account.accountable + @accountable ||= destination_account.try(:accountable) end private attr_reader :current_organization, :current_user, :offer_id, - :destination_account_id + :destination_account_id, :cross_bank # Returns the id of the account that acts as source of the transfer. # Either the account of the organization or the account of the current user. # # @return [Maybe] def source - organization = if accountable.is_a?(Organization) - accountable - else - current_organization - end - - current_user.members.find_by(organization: organization).account.id + current_user.members.find_by(organization: current_organization).account.id end # Checks whether the destination account is an organization # # @return [Boolean] def for_organization? - destination_account.accountable.class == Organization + destination_account.try(:accountable).class == Organization end def admin? current_user.try :manages?, current_organization end - # TODO: this method implements authorization by scoping the destination - # account in all the accounts of the current organization. If the specified - # destination account does not belong to it, the request will simply faily. + # Returns the account of the target organization for cross-bank transfers # + # @return [Account] + def destination_organization_account + offer.organization.account + end + # Returns the account the time will be transfered to # # @return [Account] def destination_account - @destination_account ||= current_organization - .all_accounts - .find(destination_account_id) + @destination_account ||= if destination_account_id + current_organization.all_accounts.find(destination_account_id) + elsif offer + # Get the destination account from the offer's user + member = offer.user.members.find_by(organization: offer.organization) + member.account if member + end end end diff --git a/app/views/inquiries/show.html.erb b/app/views/inquiries/show.html.erb index 637a1ddee..b9e881510 100644 --- a/app/views/inquiries/show.html.erb +++ b/app/views/inquiries/show.html.erb @@ -4,5 +4,17 @@ <%= render 'shared/post_actions', post: @inquiry %> <% end %> +<% else %> +
+ <% if current_user && current_user.active?(current_organization) %> + <%= link_to contact_inquiry_path(@inquiry), + method: :post, + data: { confirm: t('posts.show.contact_confirmation') }, + class: "btn btn-primary" do %> + <%= glyph :envelope %> + <%= t 'posts.show.request_contact' %> + <% end %> + <% end %> +
<% end %> -<%= render "shared/post", post: @inquiry %> +<%= render "shared/post", post: @inquiry %> \ No newline at end of file diff --git a/app/views/offers/show.html.erb b/app/views/offers/show.html.erb index 787ff8586..18ebd0123 100644 --- a/app/views/offers/show.html.erb +++ b/app/views/offers/show.html.erb @@ -4,6 +4,12 @@ <%= render 'shared/post_actions', post: @offer %> <% end %> <% if current_user and @offer.user != current_user %> + <% if current_organization != @offer.organization && current_user.active?(current_organization) %> + <%= link_to t('posts.show.request_contact'), + contact_post_path(@offer), + method: :post, + class: "btn btn-info" %> + <% end %> <%= link_to new_transfer_path(id: @offer.user.id, offer: @offer.id, destination_account_id: @destination_account.id), class: "btn btn-success" do %> <%= glyph :time %> @@ -11,5 +17,22 @@ <% end %> <% end %> +<% else %> +
+ <% if current_user && current_user.active?(current_organization) %> + <% if current_organization != @offer.organization %> + <%= link_to t('posts.show.request_contact'), + contact_post_path(@offer), + method: :post, + data: { confirm: t('posts.show.contact_confirmation') }, + class: "btn btn-primary me-2" %> + <% end %> + <%= link_to new_transfer_path(id: @offer.user.id, offer: @offer.id, cross_bank: true), + class: "btn btn-success" do %> + <%= glyph :time %> + <%= t ".give_time_for" %> + <% end %> + <% end %> +
<% end %> -<%= render "shared/post", post: @offer %> +<%= render "shared/post", post: @offer %> \ No newline at end of file diff --git a/app/views/organization_notifier/contact_request.html.erb b/app/views/organization_notifier/contact_request.html.erb new file mode 100644 index 000000000..b10fed862 --- /dev/null +++ b/app/views/organization_notifier/contact_request.html.erb @@ -0,0 +1,26 @@ +<%= t('organization_notifier.contact_request.greeting', name: @offerer.username) %> + +<%= t('organization_notifier.contact_request.message', + requester: @requester.username, + organization: @requester_organization.name, + post: @post.title) %> + +<%= t('organization_notifier.contact_request.requester_info') %>: + + + <%= t('activerecord.attributes.user.username') %>: <%= @requester.username %> + <% if @requester.has_valid_email? %> + <%= t('activerecord.attributes.user.email') %>: <%= @requester.email %> + <% end %> + <% phones = [@requester.phone, @requester.alt_phone].select(&:present?) %> + <% if phones.present? %> + <%= t('users.show.phone', count: phones.size) %>: + <% phones.each_with_index do |phone, index| %> + <%= " — " if index != 0 %> + <%= phone %> + <% end %> + + <% end %> + + +<%= t('organization_notifier.contact_request.closing') %> \ No newline at end of file diff --git a/app/views/shared/_post.html.erb b/app/views/shared/_post.html.erb index c079f84bf..3d1fc3eaa 100644 --- a/app/views/shared/_post.html.erb +++ b/app/views/shared/_post.html.erb @@ -69,17 +69,25 @@ -<% if !current_user || post.organization != current_organization || !current_user.active?(current_organization) %> + +<% if current_user && post.organization != current_organization && current_user.active?(current_organization) %> +
+ <%= t 'posts.show.contact_info_hidden', + type: post.class.model_name.human, + organization: post.organization.name %> +
+<% elsif !current_user || post.organization != current_organization || !current_user.active?(current_organization) %>
<%= t 'posts.show.info', type: post.class.model_name.human, organization: post.organization.name %>
<% end %> + <% unless current_user %>
<%= link_to t("layouts.application.login"), new_user_session_path, class: "btn btn-primary" %>
-<% end %> +<% end %> \ No newline at end of file diff --git a/app/views/transfers/new.html.erb b/app/views/transfers/new.html.erb index f4ac301c2..a5a1ec1b9 100644 --- a/app/views/transfers/new.html.erb +++ b/app/views/transfers/new.html.erb @@ -1,10 +1,17 @@

<%= t ".give_time" %> - <%= link_to accountable.display_name_with_uid, accountable_path(accountable) %> + <%= link_to accountable.try(:display_name_with_uid) || offer.user.username, accountable_path(accountable) || offer.user %>

+ <% if offer %>

<%= offer %>

+ <% if cross_bank %> +
+ <%= t 'transfers.cross_bank.info', organization: offer.organization.name %> +
+ <% end %> <% end %> + <%= simple_form_for transfer do |f| %>
<%= f.input :hours, @@ -24,7 +31,13 @@ } %> <%= f.input :amount, as: :hidden %> <%= f.input :reason %> - <%= f.input :destination, as: :hidden %> + + <% if cross_bank %> + <%= hidden_field_tag :cross_bank, "true" %> + <%= hidden_field_tag :post_id, offer.id %> + <% else %> + <%= f.input :destination, as: :hidden %> + <% end %> <% if sources.present? %>
@@ -38,11 +51,13 @@
<% end %> - <%= render partial: "#{accountable.model_name.singular}_offer", locals: { form: f, offer: offer, accountable: accountable } %> + <% unless cross_bank %> + <%= render partial: "#{accountable.model_name.singular}_offer", locals: { form: f, offer: offer, accountable: accountable } %> + <% end %>
<%= f.button :submit, class: "btn btn-secondary", data: { disable_with: "..." } %>
-<% end %> +<% end %> \ No newline at end of file diff --git a/config/locales/en.yml b/config/locales/en.yml index 523385ebb..7aff37d6b 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -408,6 +408,12 @@ en: subject: Newsletter text1: 'Latest offers published:' text2: 'Latest requests published:' + contact_request: + subject: "Contact request for your %{post}" + greeting: "Hello %{name}," + message: "%{requester} from %{organization} time bank 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: give_time: give_time: Give time to @@ -468,6 +474,12 @@ en: posts: show: info: This %{type} belongs to %{organization}. + contact_info_hidden: "Contact information is not visible as this %{type} is from %{organization} time bank. Click 'Request Contact' to connect with the member." + request_contact: "Request Contact" + contact_confirmation: "If you confirm, your contact information will be sent by email to the person offering this service. Do you want to proceed?" + contact: + success: "Contact request sent successfully. The offerer will receive your contact information by email." + error: "Unable to send contact request." reports: download: Download download_all: Download all @@ -555,6 +567,9 @@ en: other: "%{count} minutes" new: error_amount: Time must be greater than 0 + cross_bank: + info: "This is a time transfer to a member who belongs to another organization. The time will be transferred through both organizations." + success: "Cross-organization transfer completed successfully." users: avatar: change_your_image: Change your image diff --git a/config/locales/es.yml b/config/locales/es.yml index 1d0992ae0..62ec6deb8 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -408,6 +408,12 @@ es: subject: Boletín semanal text1: 'Últimas ofertas publicadas:' text2: 'Últimas demandas publicadas:' + contact_request: + subject: "Solicitud de contacto para tu %{post}" + greeting: "Hola %{name}," + message: "%{requester} del banco de tiempo %{organization} está interesado/a en tu %{post}." + requester_info: "Aquí está su información de contacto" + closing: "Si estás interesado/a, por favor contáctale directamente usando la información proporcionada." organizations: give_time: give_time: Dar Tiempo a @@ -468,6 +474,12 @@ es: posts: show: info: Esta %{type} pertenece a %{organization}. + contact_info_hidden: "La información de contacto no es visible ya que esta %{type} es del banco de tiempo %{organization}. Haz clic en 'Solicitar Contacto' para conectar con el miembro." + request_contact: "Solicitar Contacto" + contact_confirmation: "Si confirmas, tu información de contacto será enviada por correo electrónico a la persona que ofrece este servicio. ¿Deseas continuar?" + contact: + success: "Solicitud de contacto enviada correctamente. El ofertante recibirá tu información de contacto por correo electrónico." + error: "No se pudo enviar la solicitud de contacto." reports: download: Descargar download_all: Descargar todo @@ -555,6 +567,9 @@ es: other: "%{count} minutos" new: error_amount: 'El tiempo debe ser mayor que 0 ' + cross_bank: + info: "Esta es una transferencia de tiempo a un miembro perteneciente a otra organización. El tiempo se transferirá a través de ambas organizaciones." + success: "Transferencia entre organizaciones completada con éxito." users: avatar: change_your_image: Cambia tu imagen diff --git a/config/routes.rb b/config/routes.rb index 2eba7fd13..a4c723481 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -20,8 +20,13 @@ get "/pages/:page" => "pages#show", as: :page - resources :offers - resources :inquiries + concern :contactable do + post :contact, on: :member + end + + resources :offers, concerns: :contactable + resources :inquiries, concerns: :contactable + resources :posts, concerns: :contactable resources :device_tokens, only: :create concern :accountable do @@ -103,4 +108,4 @@ match '/404', to: 'errors#not_found', via: :all match '/500', to: 'errors#internal_server_error', via: :all -end +end \ No newline at end of file diff --git a/db/migrate/20250418100031_add_cross_bank_fields_to_transfers.rb b/db/migrate/20250418100031_add_cross_bank_fields_to_transfers.rb new file mode 100644 index 000000000..7ed50e8bc --- /dev/null +++ b/db/migrate/20250418100031_add_cross_bank_fields_to_transfers.rb @@ -0,0 +1,5 @@ +class AddCrossBankFieldsToTransfers < ActiveRecord::Migration[7.2] + def change + add_column :transfers, :meta, :jsonb, default: {}, null: false + end +end From 5897a3c1442e8bfa032eae3c9d6aae6e9a2a7a19 Mon Sep 17 00:00:00 2001 From: gmartincor Date: Fri, 18 Apr 2025 17:32:34 +0200 Subject: [PATCH 02/17] correct logic model and view --- app/models/transfer.rb | 18 +++++++++++++++ app/views/shared/_movements.html.erb | 33 ++++++++++++++++++---------- config/locales/en.yml | 4 ++-- config/locales/es.yml | 4 ++-- 4 files changed, 43 insertions(+), 16 deletions(-) diff --git a/app/models/transfer.rb b/app/models/transfer.rb index 967496c1b..e237b1a38 100644 --- a/app/models/transfer.rb +++ b/app/models/transfer.rb @@ -63,4 +63,22 @@ 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 end diff --git a/app/views/shared/_movements.html.erb b/app/views/shared/_movements.html.erb index 62fbd588f..c0be236dc 100644 --- a/app/views/shared/_movements.html.erb +++ b/app/views/shared/_movements.html.erb @@ -20,20 +20,29 @@ <%= l mv.created_at.to_date, format: :long %> - <% mv.other_side.account.tap do |account| %> - <% if account.accountable.present? %> - <% if account.accountable_type == "Organization" %> - <%= link_to account, - organization_path(account.accountable) %> - <% elsif account.accountable.active %> - <%= link_to account.accountable.display_name_with_uid, - user_path(account.accountable.user) %> - <% else %> - <%= t("users.show.inactive_user") %> - <% end %> + <% + 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 + %> + + <% if display_account.accountable.present? %> + <% if display_account.accountable_type == "Organization" %> + <%= link_to display_account, + organization_path(display_account.accountable) %> + <% elsif display_account.accountable.active %> + <%= link_to display_account.accountable.display_name_with_uid, + user_path(display_account.accountable.user) %> <% else %> - <%= t("users.show.deleted_user") %> + <%= t("users.show.inactive_user") %> <% end %> + <% else %> + <%= t("users.show.deleted_user") %> <% end %> diff --git a/config/locales/en.yml b/config/locales/en.yml index 7aff37d6b..e89e37852 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -474,7 +474,7 @@ en: posts: show: info: This %{type} belongs to %{organization}. - contact_info_hidden: "Contact information is not visible as this %{type} is from %{organization} time bank. Click 'Request Contact' to connect with the member." + contact_info_hidden: "Contact information is not visible because this %{type} belongs to %{organization}. Click the ‘Request Contact’ button to connect with the member." request_contact: "Request Contact" contact_confirmation: "If you confirm, your contact information will be sent by email to the person offering this service. Do you want to proceed?" contact: @@ -568,7 +568,7 @@ en: new: error_amount: Time must be greater than 0 cross_bank: - info: "This is a time transfer to a member who belongs to another organization. The time will be transferred through both 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: diff --git a/config/locales/es.yml b/config/locales/es.yml index 62ec6deb8..497dabbf7 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -474,7 +474,7 @@ es: posts: show: info: Esta %{type} pertenece a %{organization}. - contact_info_hidden: "La información de contacto no es visible ya que esta %{type} es del banco de tiempo %{organization}. Haz clic en 'Solicitar Contacto' para conectar con el miembro." + contact_info_hidden: "La información de contacto no es visible debido a que esta %{type} pertenece a %{organization}. Haga clic en el botón 'Solicitar Contacto' para conectar con el miembro." request_contact: "Solicitar Contacto" contact_confirmation: "Si confirmas, tu información de contacto será enviada por correo electrónico a la persona que ofrece este servicio. ¿Deseas continuar?" contact: @@ -568,7 +568,7 @@ es: new: error_amount: 'El tiempo debe ser mayor que 0 ' cross_bank: - info: "Esta es una transferencia de tiempo a un miembro perteneciente a otra organización. El tiempo se transferirá a través de ambas organizaciones." + 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." users: avatar: From 6a0ab387c23d29e8109abb1de5f0612f7084bdc2 Mon Sep 17 00:00:00 2001 From: gmartincor Date: Fri, 18 Apr 2025 17:57:14 +0200 Subject: [PATCH 03/17] correct style --- app/views/shared/_post.html.erb | 6 +++--- app/views/transfers/new.html.erb | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/app/views/shared/_post.html.erb b/app/views/shared/_post.html.erb index 3d1fc3eaa..4e15d3183 100644 --- a/app/views/shared/_post.html.erb +++ b/app/views/shared/_post.html.erb @@ -71,13 +71,13 @@ <% if current_user && post.organization != current_organization && current_user.active?(current_organization) %> -
+
<%= t 'posts.show.contact_info_hidden', type: post.class.model_name.human, organization: post.organization.name %>
<% elsif !current_user || post.organization != current_organization || !current_user.active?(current_organization) %> -
+
<%= t 'posts.show.info', type: post.class.model_name.human, organization: post.organization.name %> @@ -85,7 +85,7 @@ <% end %> <% unless current_user %> -
+
<%= link_to t("layouts.application.login"), new_user_session_path, class: "btn btn-primary" %> diff --git a/app/views/transfers/new.html.erb b/app/views/transfers/new.html.erb index a5a1ec1b9..0f7b4ad62 100644 --- a/app/views/transfers/new.html.erb +++ b/app/views/transfers/new.html.erb @@ -6,7 +6,7 @@ <% if offer %>

<%= offer %>

<% if cross_bank %> -
+
<%= t 'transfers.cross_bank.info', organization: offer.organization.name %>
<% end %> From 4721f1ce60c66e45a2ade0fda5d91388d8480b33 Mon Sep 17 00:00:00 2001 From: gmartincor Date: Fri, 18 Apr 2025 20:10:38 +0200 Subject: [PATCH 04/17] deliver_later --- app/controllers/posts_controller.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 906a93342..13da96117 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -69,7 +69,7 @@ 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_now + OrganizationNotifier.contact_request(@post, current_user, current_organization).deliver_later flash[:notice] = t('posts.contact.success') else flash[:error] = t('posts.contact.error') From ad285fcc89ccba41cad6d09de0c9d3934bf1f365 Mon Sep 17 00:00:00 2001 From: gmartincor Date: Sat, 19 Apr 2025 11:40:55 +0200 Subject: [PATCH 05/17] tests --- .../posts_controller_contact_spec.rb | 47 +++++++++++++++++++ .../transfers_controller_cross_bank_spec.rb | 44 +++++++++++++++++ ...anization_notifier_contact_request_spec.rb | 29 ++++++++++++ .../transfer_factory_cross_bank_spec.rb | 37 +++++++++++++++ 4 files changed, 157 insertions(+) create mode 100644 spec/controllers/posts_controller_contact_spec.rb create mode 100644 spec/controllers/transfers_controller_cross_bank_spec.rb create mode 100644 spec/mailers/organization_notifier_contact_request_spec.rb create mode 100644 spec/models/transfer_factory_cross_bank_spec.rb diff --git a/spec/controllers/posts_controller_contact_spec.rb b/spec/controllers/posts_controller_contact_spec.rb new file mode 100644 index 000000000..bc16e1371 --- /dev/null +++ b/spec/controllers/posts_controller_contact_spec.rb @@ -0,0 +1,47 @@ +RSpec.describe OffersController, type: :controller do + include ControllerMacros + include ActiveJob::TestHelper + + let(:source_org) { Fabricate(:organization) } + let(:dest_org) { Fabricate(:organization) } + + let(:active_user) { Fabricate(:user) } + let!(:source_member) { Fabricate(:member, user: active_user, organization: source_org, active: true) } + + let(:offer_owner) { Fabricate(:user) } + let!(:dest_member) { Fabricate(:member, user: offer_owner, organization: dest_org, active: true) } + + let!(:offer) { Fabricate(:offer, user: offer_owner, organization: dest_org) } + + before do + login(active_user) + session[:current_organization_id] = source_org.id + controller.instance_variable_set(:@current_organization, source_org) + ActiveJob::Base.queue_adapter = :test + end + + describe 'POST #contact' do + it 'sends a contact‑request email and sets a flash notice' do + perform_enqueued_jobs do + expect { + post :contact, params: { id: offer.id } + }.to change { ActionMailer::Base.deliveries.size }.by(1) + end + + expect(response).to redirect_to(offer) + expect(flash[:notice]).to eq(I18n.t('posts.contact.success')) + end + + context 'when the user belongs to the same organization as the post' do + let!(:same_org_offer) { Fabricate(:offer, organization: source_org) } + + it 'does not send any email and shows an error flash' do + expect { + post :contact, params: { id: same_org_offer.id } + }.not_to change { ActionMailer::Base.deliveries.size } + + expect(flash[:error]).to eq(I18n.t('posts.contact.error')) + end + end + end +end diff --git a/spec/controllers/transfers_controller_cross_bank_spec.rb b/spec/controllers/transfers_controller_cross_bank_spec.rb new file mode 100644 index 000000000..1a468543a --- /dev/null +++ b/spec/controllers/transfers_controller_cross_bank_spec.rb @@ -0,0 +1,44 @@ +RSpec.describe TransfersController, type: :controller do + include ControllerMacros + include ActiveJob::TestHelper + + let(:source_org) { Fabricate(:organization) } + let(:dest_org) { Fabricate(:organization) } + + let(:source_user) { Fabricate(:user) } + let!(:source_member) { Fabricate(:member, user: source_user, organization: source_org) } + + let(:dest_user) { Fabricate(:user) } + let!(:dest_member) { Fabricate(:member, user: dest_user, organization: dest_org) } + + let(:offer) { Fabricate(:offer, user: dest_user, organization: dest_org) } + + before do + login(source_user) + session[:current_organization_id] = source_org.id + controller.instance_variable_set(:@current_organization, source_org) + end + + describe 'POST #create (cross‑bank)' do + let(:params) do + { + cross_bank: 'true', + post_id: offer.id, + transfer: { amount: 4, reason: 'Helping across banks' } + } + end + + subject(:request!) { post :create, params: params } + + it 'creates one transfer and six movements' do + expect { request! }.to change(Transfer, :count).by(1) + .and change(Movement, :count).by(6) + end + + it 'redirects back to the post with a success notice' do + request! + expect(response).to redirect_to(offer) + expect(flash[:notice]).to eq(I18n.t('transfers.cross_bank.success')) + end + end +end diff --git a/spec/mailers/organization_notifier_contact_request_spec.rb b/spec/mailers/organization_notifier_contact_request_spec.rb new file mode 100644 index 000000000..3d0598f96 --- /dev/null +++ b/spec/mailers/organization_notifier_contact_request_spec.rb @@ -0,0 +1,29 @@ +RSpec.describe OrganizationNotifier, type: :mailer do + describe '.contact_request' do + let(:source_org) { Fabricate(:organization) } + let(:dest_org) { Fabricate(:organization) } + + let(:requester) { Fabricate(:user, email: 'requester@example.com', locale: :en) } + let!(:requester_member) { Fabricate(:member, user: requester, organization: source_org) } + + let(:offerer) { Fabricate(:user, email: 'offerer@example.com', locale: :en) } + let!(:offerer_member) { Fabricate(:member, user: offerer, organization: dest_org) } + + let(:post_offer) { Fabricate(:offer, user: offerer, organization: dest_org, title: 'Gardening help') } + + subject(:mail) { described_class.contact_request(post_offer, requester, source_org) } + + it 'is sent to the offerer' do + expect(mail.to).to eq([offerer.email]) + end + + it 'includes the post title in the localized subject' do + expect(mail.subject).to include(post_offer.title) + end + + it 'embeds the requester information in the body' do + expect(mail.body.encoded).to include(requester.username) + expect(mail.body.encoded).to include(source_org.name) + end + end +end diff --git a/spec/models/transfer_factory_cross_bank_spec.rb b/spec/models/transfer_factory_cross_bank_spec.rb new file mode 100644 index 000000000..c53fc9f95 --- /dev/null +++ b/spec/models/transfer_factory_cross_bank_spec.rb @@ -0,0 +1,37 @@ +RSpec.describe TransferFactory do + describe '#build_transfer (cross‑bank transfer)' do + let(:source_org) { Fabricate(:organization) } + let(:dest_org) { Fabricate(:organization) } + let(:current_user) { Fabricate(:user) } + let!(:source_member) { Fabricate(:member, user: current_user, organization: source_org) } + let(:offer) { Fabricate(:offer, organization: dest_org) } + + subject(:transfer) do + described_class.new(source_org, current_user, offer.id, nil, true).build_transfer + end + + it 'marks the transfer as cross‑bank' do + expect(transfer.is_cross_bank).to be true + end + + it 'sets the source to the current user account' do + expect(transfer.source_id).to eq(source_member.account.id) + end + + it 'sets the destination to the destination organization account' do + expect(transfer.destination_id).to eq(dest_org.account.id) + end + + it 'stores metadata required to rebuild the six‑movement chain' do + expect(transfer.meta).to eq( + source_organization_id: source_org.id, + destination_organization_id: dest_org.id, + final_destination_user_id: offer.user.id + ) + end + + it 'associates the offer as the transfer post' do + expect(transfer.post).to eq(offer) + end + end +end From 58333b63dd46be72e104b842aea7949a038a9b84 Mon Sep 17 00:00:00 2001 From: gmartincor Date: Sun, 20 Apr 2025 12:34:47 +0200 Subject: [PATCH 06/17] Correct offers visibility --- app/controllers/posts_controller.rb | 6 ++++++ app/models/organization.rb | 6 ++++++ 2 files changed, 12 insertions(+) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 13da96117..659982741 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -6,6 +6,12 @@ class PostsController < ApplicationController def index context = model.active.of_active_members + if current_user.present? && current_organization.present? + allied_org_ids = current_organization.allied_organizations.pluck(:id) + org_ids = [current_organization.id] + allied_org_ids + context = context.by_organizations(org_ids) + end + posts = apply_scopes(context) posts = posts.search_by_query(params[:q]) if params[:q].present? posts = posts.page(params[:page]).per(25) diff --git a/app/models/organization.rb b/app/models/organization.rb index e2cc5dff7..2d15c9ee5 100644 --- a/app/models/organization.rb +++ b/app/models/organization.rb @@ -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 From a45b9eaa99f25afd82ce5ed4985689897ca5ce45 Mon Sep 17 00:00:00 2001 From: gmartincor Date: Sun, 20 Apr 2025 13:01:18 +0200 Subject: [PATCH 07/17] Correc test offers Visibility --- spec/controllers/offers_controller_spec.rb | 118 +++++++++++++-------- 1 file changed, 75 insertions(+), 43 deletions(-) diff --git a/spec/controllers/offers_controller_spec.rb b/spec/controllers/offers_controller_spec.rb index f47bf4f76..c125486df 100644 --- a/spec/controllers/offers_controller_spec.rb +++ b/spec/controllers/offers_controller_spec.rb @@ -22,65 +22,97 @@ before { login(another_member.user) } it "populates an array of offers" do - get :index + get :index - expect(assigns(:offers)).to eq([other_offer, offer]) + expect(assigns(:offers)).to eq([other_offer, offer]) end context "when one offer is not active" do - before do - other_offer.active = false - other_offer.save! - end + before do + other_offer.active = false + other_offer.save! + end - it "only returns active offers" do - get :index + it "only returns active offers" do + get :index - expect(assigns(:offers)).to eq([offer]) - end + expect(assigns(:offers)).to eq([offer]) + end end context "when one offer's user is not active" do - before do - member.active = false - member.save! - end + before do + member.active = false + member.save! + end - it "only returns offers from active users" do - get :index + it "only returns offers from active users" do + get :index - expect(assigns(:offers)).to eq([other_offer]) - end + expect(assigns(:offers)).to eq([other_offer]) + end end context "when filtering by organization" do - let(:organization1) { Fabricate(:organization) } - let(:organization2) { Fabricate(:organization) } - let(:user1) { Fabricate(:user) } - let(:user2) { Fabricate(:user) } - let(:member1) { Fabricate(:member, user: user1, organization: organization1) } - let(:member2) { Fabricate(:member, user: user2, organization: organization2) } - let!(:offer1) { Fabricate(:offer, user: user1, organization: organization1, title: "Ruby on Rails nivel principiante") } - let!(:offer2) { Fabricate(:offer, user: user2, organization: organization2, title: "Cocina low cost") } - - before do - member1 - member2 - login(user1) - Fabricate(:member, user: user1, organization: organization2) unless user1.members.where(organization: organization2).exists? - end + let(:organization1) { Fabricate(:organization) } + let(:organization2) { Fabricate(:organization) } + let(:user1) { Fabricate(:user) } + let(:user2) { Fabricate(:user) } + let(:member1) { Fabricate(:member, user: user1, organization: organization1) } + let(:member2) { Fabricate(:member, user: user2, organization: organization2) } + let!(:offer1) { Fabricate(:offer, user: user1, organization: organization1, title: "Ruby on Rails nivel principiante") } + let!(:offer2) { Fabricate(:offer, user: user2, organization: organization2, title: "Cocina low cost") } - it 'displays only offers from the selected organization' do - get :index, params: { org: organization1.id } - expect(assigns(:offers)).to include(offer1) - expect(assigns(:offers)).not_to include(offer2) - end + before do + member1 + member2 + login(user1) + Fabricate(:member, user: user1, organization: organization2) unless user1.members.where(organization: organization2).exists? + end - it 'displays all offers when no organization is selected' do - get :index - expect(assigns(:offers)).to include(offer1) - expect(assigns(:offers)).to include(offer2) - end + it 'displays only offers from the selected organization' do + get :index, params: { org: organization1.id } + expect(assigns(:offers)).to include(offer1) + expect(assigns(:offers)).not_to include(offer2) + end + + it 'displays only offers from the current organization and allied organizations when no organization is selected' do + alliance = OrganizationAlliance.create!( + source_organization: organization1, + target_organization: organization2, + status: "accepted" + ) + + get :index + + expect(assigns(:offers)).to include(offer1) + + expect(assigns(:offers)).to include(offer2) + + organization3 = Fabricate(:organization) + user3 = Fabricate(:user) + member3 = Fabricate(:member, user: user3, organization: organization3) + offer3 = Fabricate(:offer, user: user3, organization: organization3, title: "Non-allied offer") + + get :index + + expect(assigns(:offers)).not_to include(offer3) + end + + it 'displays all offers when user is not logged in' do + allow(controller).to receive(:current_user).and_return(nil) + + organization3 = Fabricate(:organization) + user3 = Fabricate(:user) + member3 = Fabricate(:member, user: user3, organization: organization3) + offer3 = Fabricate(:offer, user: user3, organization: organization3, title: "Third org offer") + + get :index + + expect(assigns(:offers)).to include(offer1) + expect(assigns(:offers)).to include(offer2) + expect(assigns(:offers)).to include(offer3) + end end end From 4db2fb6a02b019fdd26eac7fc9ec460fcc3882f7 Mon Sep 17 00:00:00 2001 From: gmartincor Date: Sun, 20 Apr 2025 13:22:07 +0200 Subject: [PATCH 08/17] Add cross-bank transfer validation to only allow transfers between allied organizations --- app/models/transfer.rb | 18 ++++++++++++++++++ config/locales/en.yml | 1 + config/locales/es.yml | 1 + 3 files changed, 20 insertions(+) diff --git a/app/models/transfer.rb b/app/models/transfer.rb index e237b1a38..8644b3268 100644 --- a/app/models/transfer.rb +++ b/app/models/transfer.rb @@ -19,6 +19,7 @@ class Transfer < ApplicationRecord 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 @@ -81,4 +82,21 @@ def related_account_for(movement) 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 diff --git a/config/locales/en.yml b/config/locales/en.yml index e89e37852..738372fda 100644 --- a/config/locales/en.yml +++ b/config/locales/en.yml @@ -83,6 +83,7 @@ en: attributes: base: same_account: A transfer cannot be made to the same account + no_alliance_between_organizations: Transfers are only allowed between allied organizations user: attributes: email: diff --git a/config/locales/es.yml b/config/locales/es.yml index 497dabbf7..ce9c49aa1 100644 --- a/config/locales/es.yml +++ b/config/locales/es.yml @@ -83,6 +83,7 @@ es: attributes: base: same_account: No se puede hacer una transacción a la misma cuenta + no_alliance_between_organizations: Solo se permiten transferencias entre organizaciones aliadas user: attributes: email: From 4da8396d81d02e22fc032dc0126c30d64d449eb1 Mon Sep 17 00:00:00 2001 From: gmartincor Date: Sun, 20 Apr 2025 13:28:54 +0200 Subject: [PATCH 09/17] Update cross-bank transfer tests to include alliance creation --- spec/controllers/transfers_controller_cross_bank_spec.rb | 8 ++++++++ spec/models/transfer_factory_cross_bank_spec.rb | 9 +++++++++ 2 files changed, 17 insertions(+) diff --git a/spec/controllers/transfers_controller_cross_bank_spec.rb b/spec/controllers/transfers_controller_cross_bank_spec.rb index 1a468543a..e24ec78af 100644 --- a/spec/controllers/transfers_controller_cross_bank_spec.rb +++ b/spec/controllers/transfers_controller_cross_bank_spec.rb @@ -13,6 +13,14 @@ let(:offer) { Fabricate(:offer, user: dest_user, organization: dest_org) } + let!(:alliance) do + OrganizationAlliance.create!( + source_organization: source_org, + target_organization: dest_org, + status: "accepted" + ) + end + before do login(source_user) session[:current_organization_id] = source_org.id diff --git a/spec/models/transfer_factory_cross_bank_spec.rb b/spec/models/transfer_factory_cross_bank_spec.rb index c53fc9f95..7a84b25c3 100644 --- a/spec/models/transfer_factory_cross_bank_spec.rb +++ b/spec/models/transfer_factory_cross_bank_spec.rb @@ -6,6 +6,15 @@ let!(:source_member) { Fabricate(:member, user: current_user, organization: source_org) } let(:offer) { Fabricate(:offer, organization: dest_org) } + + let!(:alliance) do + OrganizationAlliance.create!( + source_organization: source_org, + target_organization: dest_org, + status: "accepted" + ) + end + subject(:transfer) do described_class.new(source_org, current_user, offer.id, nil, true).build_transfer end From 4e02352727d85de3fac0de97f3db7eac04f892d1 Mon Sep 17 00:00:00 2001 From: gmartincor Date: Sun, 20 Apr 2025 18:05:27 +0200 Subject: [PATCH 10/17] Change default posts visibility to only show current organization's posts --- app/controllers/posts_controller.rb | 4 ++++ app/views/shared/_post_filters.html.erb | 9 ++++++++- 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/app/controllers/posts_controller.rb b/app/controllers/posts_controller.rb index 659982741..ac9723c85 100644 --- a/app/controllers/posts_controller.rb +++ b/app/controllers/posts_controller.rb @@ -7,9 +7,13 @@ 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) diff --git a/app/views/shared/_post_filters.html.erb b/app/views/shared/_post_filters.html.erb index cf92f46d0..40767ed6b 100644 --- a/app/views/shared/_post_filters.html.erb +++ b/app/views/shared/_post_filters.html.erb @@ -51,7 +51,14 @@
-<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/transfers/new.html.erb b/app/views/transfers/new.html.erb index 0f7b4ad62..e399dc2d6 100644 --- a/app/views/transfers/new.html.erb +++ b/app/views/transfers/new.html.erb @@ -60,4 +60,4 @@ <%= f.button :submit, class: "btn btn-secondary", data: { disable_with: "..." } %>
-<% end %> \ No newline at end of file +<% end %> diff --git a/config/routes.rb b/config/routes.rb index a4c723481..54715059d 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -108,4 +108,4 @@ match '/404', to: 'errors#not_found', via: :all match '/500', to: 'errors#internal_server_error', via: :all -end \ No newline at end of file +end diff --git a/db/migrate/20250418100031_add_cross_bank_fields_to_transfers.rb b/db/migrate/20250418100031_add_cross_bank_fields_to_transfers.rb deleted file mode 100644 index 7ed50e8bc..000000000 --- a/db/migrate/20250418100031_add_cross_bank_fields_to_transfers.rb +++ /dev/null @@ -1,5 +0,0 @@ -class AddCrossBankFieldsToTransfers < ActiveRecord::Migration[7.2] - def change - add_column :transfers, :meta, :jsonb, default: {}, null: false - end -end From 58505051c81f625b743601c6707d0e8ec60beda5 Mon Sep 17 00:00:00 2001 From: gmartincor Date: Fri, 25 Apr 2025 08:33:35 +0200 Subject: [PATCH 16/17] refactor code --- .../application/organizations_filter.js | 59 +++++++++---------- .../organization_alliances/index.html.erb | 2 +- .../organizations/_alliance_button.html.erb | 2 +- .../organizations/_organizations_row.html.erb | 2 +- app/views/organizations/index.html.erb | 2 +- app/views/shared/_post_filters.html.erb | 2 +- 6 files changed, 32 insertions(+), 37 deletions(-) diff --git a/app/assets/javascripts/application/organizations_filter.js b/app/assets/javascripts/application/organizations_filter.js index 964149118..0ae1c372b 100644 --- a/app/assets/javascripts/application/organizations_filter.js +++ b/app/assets/javascripts/application/organizations_filter.js @@ -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(''); - } - } - - if (q) { - form.find('input[name="q"]').val(q); - } - - if (tag) { - if (form.find('input[name="tag"]').length === 0) { - form.append(''); - } - } - - // Enviar el formulario - form.submit(); - }); - }); \ No newline at end of file + $(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(''); + } + } + + if (q) { + form.find('input[name="q"]').val(q); + } + + if (tag) { + if (form.find('input[name="tag"]').length === 0) { + form.append(''); + } + } + + form.submit(); + }); + }); diff --git a/app/views/organization_alliances/index.html.erb b/app/views/organization_alliances/index.html.erb index 8b10b742a..2747be620 100644 --- a/app/views/organization_alliances/index.html.erb +++ b/app/views/organization_alliances/index.html.erb @@ -99,4 +99,4 @@
-
\ No newline at end of file + diff --git a/app/views/organizations/_alliance_button.html.erb b/app/views/organizations/_alliance_button.html.erb index fc7f5d09c..6be3d44e1 100644 --- a/app/views/organizations/_alliance_button.html.erb +++ b/app/views/organizations/_alliance_button.html.erb @@ -13,4 +13,4 @@ <% elsif alliance.rejected? %> <%= t('organization_alliances.rejected') %> <% end %> -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/organizations/_organizations_row.html.erb b/app/views/organizations/_organizations_row.html.erb index 1a834b24e..30b5a6c5d 100644 --- a/app/views/organizations/_organizations_row.html.erb +++ b/app/views/organizations/_organizations_row.html.erb @@ -12,4 +12,4 @@ <%= render "organizations/alliance_button", organization: org %> <% end %> - \ No newline at end of file + diff --git a/app/views/organizations/index.html.erb b/app/views/organizations/index.html.erb index 3860b986b..f750148d6 100644 --- a/app/views/organizations/index.html.erb +++ b/app/views/organizations/index.html.erb @@ -40,4 +40,4 @@ <%= paginate @organizations %> - \ No newline at end of file + diff --git a/app/views/shared/_post_filters.html.erb b/app/views/shared/_post_filters.html.erb index 40767ed6b..daeeccab4 100644 --- a/app/views/shared/_post_filters.html.erb +++ b/app/views/shared/_post_filters.html.erb @@ -78,4 +78,4 @@ - \ No newline at end of file + From 494d11d97f87892c05f7cc1cd726f55e043ffc7d Mon Sep 17 00:00:00 2001 From: gmartincor Date: Fri, 25 Apr 2025 08:46:58 +0200 Subject: [PATCH 17/17] structure.sql --- db/structure.sql | 169 +++++++++++++++++++++++++---------------------- 1 file changed, 90 insertions(+), 79 deletions(-) diff --git a/db/structure.sql b/db/structure.sql index 213044443..4388a5274 100644 --- a/db/structure.sql +++ b/db/structure.sql @@ -1,13 +1,25 @@ SET statement_timeout = 0; SET lock_timeout = 0; -SET idle_in_transaction_session_timeout = 0; SET client_encoding = 'UTF8'; SET standard_conforming_strings = on; SELECT pg_catalog.set_config('search_path', '', false); SET check_function_bodies = false; SET xmloption = content; SET client_min_messages = warning; -SET row_security = off; + +-- +-- Name: plpgsql; Type: EXTENSION; Schema: -; Owner: - +-- + +CREATE EXTENSION IF NOT EXISTS plpgsql WITH SCHEMA pg_catalog; + + +-- +-- Name: EXTENSION plpgsql; Type: COMMENT; Schema: -; Owner: - +-- + +COMMENT ON EXTENSION plpgsql IS 'PL/pgSQL procedural language'; + -- -- Name: hstore; Type: EXTENSION; Schema: -; Owner: - @@ -70,7 +82,7 @@ CREATE FUNCTION public.posts_trigger() RETURNS trigger SET default_tablespace = ''; -SET default_table_access_method = heap; +SET default_with_oids = false; -- -- Name: accounts; Type: TABLE; Schema: public; Owner: - @@ -1289,7 +1301,7 @@ CREATE UNIQUE INDEX unique_schema_migrations ON public.schema_migrations USING b -- Name: posts tsvectorupdate; Type: TRIGGER; Schema: public; Owner: - -- -CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE ON public.posts FOR EACH ROW EXECUTE FUNCTION public.posts_trigger(); +CREATE TRIGGER tsvectorupdate BEFORE INSERT OR UPDATE ON public.posts FOR EACH ROW EXECUTE PROCEDURE public.posts_trigger(); -- @@ -1395,79 +1407,78 @@ ALTER TABLE ONLY public.organization_alliances SET search_path TO "$user", public; INSERT INTO "schema_migrations" (version) VALUES -('20250412110249'), -('20250215163406'), -('20250215163405'), -('20250215163404'), -('20241230170753'), -('20231120164346'), -('20231120164231'), -('20230401114456'), -('20230314233504'), -('20230312231058'), -('20221016192111'), -('20210503201944'), -('20210502160343'), -('20210424174640'), -('20210423193937'), -('20190523225323'), -('20190523213421'), -('20190412163011'), -('20190411192828'), -('20190322180602'), -('20190319121401'), -('20181004200104'), -('20180924164456'), -('20180831161349'), -('20180828160700'), -('20180604145622'), -('20180530180546'), -('20180529144243'), -('20180525141138'), -('20180524143938'), -('20180514193153'), -('20180501093846'), -('20180221161343'), -('20150422162806'), -('20150330200315'), -('20150329193421'), -('20140514225527'), -('20140513141718'), -('20140119161433'), -('20131231110424'), -('20131227155440'), -('20131227142805'), -('20131227110122'), -('20131220160257'), -('20131104032622'), -('20131104013829'), -('20131104013634'), -('20131104004235'), -('20131103221044'), -('20131029202724'), -('20131027215517'), -('20131025202608'), -('20131017144321'), -('20130723160206'), -('20130703234042'), -('20130703234011'), -('20130703233851'), -('20130621105452'), -('20130621103501'), -('20130621103053'), -('20130621102219'), -('20130618210236'), -('20130514094755'), -('20130513092219'), -('20130508085004'), -('20130425165150'), -('20130222185624'), -('20130214181128'), -('20130214175758'), -('20121121233818'), -('20121104085711'), -('20121104004639'), -('20121019101022'), +('1'), ('2'), -('1'); - +('20121019101022'), +('20121104004639'), +('20121104085711'), +('20121121233818'), +('20130214175758'), +('20130214181128'), +('20130222185624'), +('20130425165150'), +('20130508085004'), +('20130513092219'), +('20130514094755'), +('20130618210236'), +('20130621102219'), +('20130621103053'), +('20130621103501'), +('20130621105452'), +('20130703233851'), +('20130703234011'), +('20130703234042'), +('20130723160206'), +('20131017144321'), +('20131025202608'), +('20131027215517'), +('20131029202724'), +('20131103221044'), +('20131104004235'), +('20131104013634'), +('20131104013829'), +('20131104032622'), +('20131220160257'), +('20131227110122'), +('20131227142805'), +('20131227155440'), +('20131231110424'), +('20140119161433'), +('20140513141718'), +('20140514225527'), +('20150329193421'), +('20150330200315'), +('20150422162806'), +('20180221161343'), +('20180501093846'), +('20180514193153'), +('20180524143938'), +('20180525141138'), +('20180529144243'), +('20180530180546'), +('20180604145622'), +('20180828160700'), +('20180831161349'), +('20180924164456'), +('20181004200104'), +('20190319121401'), +('20190322180602'), +('20190411192828'), +('20190412163011'), +('20190523213421'), +('20190523225323'), +('20210423193937'), +('20210424174640'), +('20210502160343'), +('20210503201944'), +('20221016192111'), +('20230312231058'), +('20230314233504'), +('20230401114456'), +('20231120164231'), +('20231120164346'), +('20241230170753'), +('20250215163404'), +('20250215163405'), +('20250215163406'), +('20250412110249');