Skip to content

Commit

Permalink
Bank admins can enter requests on behalf of partners (#4961)
Browse files Browse the repository at this point in the history
* Requests page: add button for bank admins to create new requests. Fix styling of buttons

* Requests page: select partner from list

* Bank admins can view new request page

* Remove extraneous requests#create action; post to partners/requests#create directly

* Bank admins can submit new request

* RequestsConfirmationMailer: display name of person who submitted the request

* RequestsConfirmationMailer: always send to the user who submitted the request

* DistributionMailer uses new Request#requester method

* Cancel partner request uses correct link for bank admins

* Document new feature for bank admins to submit request on behalf of partner

* Bank admin can request requests for active partners only

* RequestsConfirmationMailer: rename @requester_name

* Partners::RequestsController: set layout for all actions

* Fix error page not showing for requests submitted by bank admin

* Fix confirmation modal not showing for requests submitted by bank admin
  • Loading branch information
jp524 authored Feb 4, 2025
1 parent 7bf1805 commit b818390
Show file tree
Hide file tree
Showing 23 changed files with 312 additions and 54 deletions.
49 changes: 44 additions & 5 deletions app/controllers/partners/requests_controller.rb
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
module Partners
class RequestsController < BaseController
skip_before_action :require_partner, only: [:new, :create, :validate]
before_action :require_partner_or_org_admin, only: [:new, :create, :validate]
layout :layout

protect_from_forgery with: :exception

def index
Expand All @@ -21,15 +25,20 @@ def show
def create
create_service = Partners::RequestCreateService.new(
request_type: "quantity",
partner_user_id: current_user.id,
partner_id: partner.id,
user_id: current_user.id,
comments: partner_request_params[:comments],
item_requests_attributes: partner_request_params[:item_requests_attributes]&.values || []
)

create_service.call
if create_service.errors.none?
flash[:success] = 'Request was successfully created.'
redirect_to partners_request_path(create_service.partner_request.id)
if current_partner
redirect_to partners_request_path(create_service.partner_request.id)
else
redirect_to request_path(create_service.partner_request.id)
end
else
@partner_request = create_service.partner_request
@errors = create_service.errors
Expand All @@ -45,15 +54,16 @@ def create
def validate
create_service = Partners::RequestCreateService.new(
request_type: "quantity",
partner_user_id: current_user.id,
partner_id: partner.id,
user_id: current_user.id,
comments: partner_request_params[:comments],
item_requests_attributes: partner_request_params[:item_requests_attributes]&.values || []
).initialize_only

if create_service.errors.none?
@partner_request = create_service.partner_request
@total_items = @partner_request.total_items
@quota_exceeded = current_partner.quota_exceeded?(@total_items)
@quota_exceeded = partner.quota_exceeded?(@total_items)
body = render_to_string(template: 'partners/requests/validate', formats: [:html], layout: false)
render json: {valid: true, body: body}
else
Expand All @@ -68,13 +78,42 @@ def partner_request_params
end

def fetch_items
@requestable_items = PartnerFetchRequestableItemsService.new(partner_id: current_partner.id).call
@requestable_items = PartnerFetchRequestableItemsService.new(partner_id: partner.id).call
if Flipper.enabled?(:enable_packs)
# hash of (item ID => hash of (request unit name => request unit plural name))
@item_units = Item.where(id: @requestable_items.to_h.values).to_h do |i|
[i.id, i.request_units.to_h { |u| [u.name, u.name.pluralize] }]
end
end
end

def require_partner_or_org_admin
return if current_partner

partner_id = params.permit(:partner_id)[:partner_id]
return redirect_invalid_user if partner_id.blank?

partner = Partner.find(partner_id)
if current_user.has_role?(Role::ORG_ADMIN, current_organization) && current_organization == partner&.organization
@partner = partner
else
redirect_invalid_user
end
end

def redirect_invalid_user
respond_to do |format|
format.html { redirect_to dashboard_path, flash: {error: "Logged in user is not set up as a 'partner'."} }
format.json { render body: nil, status: :forbidden }
end
end

def partner
@partner ||= current_partner
end

def layout
@layout ||= current_partner ? "partners/application" : "application"
end
end
end
2 changes: 1 addition & 1 deletion app/controllers/requests_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ def index
@paginated_requests = @requests.includes(:partner).page(params[:page])
@calculate_product_totals = RequestsTotalItemsService.new(requests: @requests).calculate
@items = current_organization.items.alphabetized.select(:id, :name)
@partners = current_organization.partners.alphabetized.select(:id, :name)
@partners = current_organization.partners.alphabetized.select(:id, :name, :status)
@statuses = Request.statuses.transform_keys(&:humanize)
@partner_users = User.where(id: @paginated_requests.map(&:partner_user_id)).select(:id, :name, :email)
@request_types = Request.request_types.transform_keys(&:humanize)
Expand Down
8 changes: 4 additions & 4 deletions app/mailers/distribution_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ def partner_mailer(current_organization, distribution, subject, distribution_cha
@partner = distribution.partner
@distribution = distribution
@comment = distribution.comment
requestee_email = distribution.request ? distribution.request.user_email : @partner.email
requester_email = distribution.request ? distribution.request.requester.email : @partner.email

delivery_method = @distribution.delivery? ? 'delivered' : 'picked up'
@default_email_text = current_organization.default_email_text
Expand All @@ -35,17 +35,17 @@ def partner_mailer(current_organization, distribution, subject, distribution_cha
cc.compact!
cc.uniq!

mail(to: requestee_email, cc: cc, subject: "#{subject} from #{current_organization.name}")
mail(to: requester_email, cc: cc, subject: "#{subject} from #{current_organization.name}")
end

def reminder_email(distribution_id)
distribution = Distribution.find(distribution_id)
@partner = distribution.partner
@distribution = distribution
requestee_email = distribution.request ? distribution.request.user_email : @partner.email
requester_email = distribution.request ? distribution.request.requester.email : @partner.email

return if @distribution.past? || !@partner.send_reminders || @partner.deactivated?

mail(to: requestee_email, cc: @partner.email, subject: "#{@partner.name} Distribution Reminder")
mail(to: requester_email, cc: @partner.email, subject: "#{@partner.name} Distribution Reminder")
end
end
8 changes: 4 additions & 4 deletions app/mailers/requests_confirmation_mailer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -3,17 +3,17 @@ def confirmation_email(request)
@organization = request.organization
@partner = request.partner
@request_items = fetch_items(request)
requestee_email = request.user_email
# If the requestee organization has opted in to receiving an email when a
# request is made, CC them
requester = request.requester
@requester_user_name = requester.is_a?(User) ? requester.name : nil # Requester can be the partner, if no user is specified
# If the organization has opted in to receiving an email when a request is made, CC them
cc = [@partner.email]
if @organization.receive_email_on_requests
cc.push(@organization.email)
end
cc.flatten!
cc.compact!
cc.uniq!
mail(to: requestee_email, cc: cc, subject: "#{@organization.name} - Requests Confirmation")
mail(to: requester.email, cc: cc, subject: "#{@organization.name} - Requests Confirmation")
end

private
Expand Down
5 changes: 3 additions & 2 deletions app/models/request.rb
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,9 @@ def total_items
request_items.sum { |item| item["quantity"] }
end

def user_email
partner_user_id ? User.find_by(id: partner_user_id).email : Partner.find_by(id: partner_id).email
def requester
# Despite the field being called "partner_user_id", it can refer to both a partner user or an organization admin
partner_user_id ? partner_user : partner
end

def request_type_label
Expand Down
10 changes: 8 additions & 2 deletions app/services/partners/family_request_create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,8 @@ def call
return self unless valid?

request_create_svc = Partners::RequestCreateService.new(
partner_user_id: partner_user_id,
partner_id: partner.id,
user_id: partner_user_id,
comments: comments,
request_type: request_type,
item_requests_attributes: item_requests_attributes
Expand All @@ -41,7 +42,8 @@ def call

def initialize_only
Partners::RequestCreateService.new(
partner_user_id: partner_user_id,
partner_id: partner.id,
user_id: partner_user_id,
comments: comments,
request_type: request_type,
item_requests_attributes: item_requests_attributes
Expand Down Expand Up @@ -81,5 +83,9 @@ def convert_person_count_to_item_quantity(item_id:, person_count:)
def included_items_by_id
@included_items_by_id ||= Item.where(id: family_requests_attributes.pluck(:item_id)).index_by(&:id)
end

def partner
@partner ||= ::User.find(partner_user_id).partner
end
end
end
11 changes: 6 additions & 5 deletions app/services/partners/request_create_service.rb
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ class RequestCreateService

attr_reader :partner_request

def initialize(request_type:, partner_user_id:, comments: nil, item_requests_attributes: [], additional_attrs: {})
@partner_user_id = partner_user_id
def initialize(request_type:, partner_id:, user_id:, comments: nil, item_requests_attributes: [], additional_attrs: {})
@partner_id = partner_id
@user_id = user_id
@comments = comments
@request_type = request_type
@item_requests_attributes = item_requests_attributes
Expand Down Expand Up @@ -34,7 +35,7 @@ def initialize_only
organization_id: organization_id,
comments: comments,
request_type: request_type,
partner_user_id: partner_user_id
partner_user_id: user_id
)
@partner_request = populate_item_request(partner_request)
@partner_request.assign_attributes(additional_attrs)
Expand All @@ -50,7 +51,7 @@ def initialize_only

private

attr_reader :partner_user_id, :comments, :item_requests_attributes, :additional_attrs, :request_type
attr_reader :user_id, :partner_id, :comments, :item_requests_attributes, :additional_attrs, :request_type

def populate_item_request(partner_request)
# Exclude any line item that is completely empty
Expand Down Expand Up @@ -130,7 +131,7 @@ def organization_id
end

def partner
@partner ||= ::User.find(partner_user_id).partner
@partner ||= Partner.find(partner_id)
end
end
end
10 changes: 6 additions & 4 deletions app/views/partners/requests/_error.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,15 @@
<h3 class='text-4xl font-extrabold'>Oops! Something went wrong with your Request</h3>
<p class='text-lg font-bold'>
Ensure each line item has a item selected AND a quantity greater than 0.
<% if Flipper.enabled?(:enable_packs) && current_partner.organization.request_units.any? %>
<% if Flipper.enabled?(:enable_packs) && (current_partner&.organization || current_organization).request_units.any? %>
Please ensure a single unit is selected for each item that supports it.
<% end %>
</p>
<p class='text-md'>
Still need help? Please contact your essentials bank, <%= current_partner.organization.name %>, if you need further assistance.<br>
Our email on record for them is: <%= mail_to current_partner.organization.email %>
<% if current_partner %>
<p class='text-md'>
Still need help? Please contact your essentials bank, <%= current_partner.organization.name %>, if you need further assistance.<br>
Our email on record for them is: <%= mail_to current_partner.organization.email %>
<% end %>
</p>
</div>
</div>
9 changes: 5 additions & 4 deletions app/views/partners/requests/new.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<% content_for :title, "New Request - #{current_user.display_name}" %>
<% content_for :title, "New Request - #{current_partner ? current_user.display_name : @partner.name}" %>
<h1><i class="fa fa-users"></i>&nbsp;&nbsp;
New Request
<small>for <%= current_user.display_name %></small>
<small>for <%= current_partner ? current_user.display_name : @partner.name %></small>
</h1>
</div>
<div class="col-sm-6">
<ol class="breadcrumb float-sm-right">
<li class="breadcrumb-item"><a href="<%= partner_user_root_path %>"><i class="fa fa-home fa-lg"></i></a></li>
<li class="breadcrumb-item"><a href="<%= current_partner ? partner_user_root_path : dashboard_path %>"><i class="fa fa-home fa-lg"></i></a></li>
<li class="breadcrumb-item"><a href="#">New Essentials Request</a></li>
</ol>
</div>
Expand All @@ -35,6 +35,7 @@
<%= simple_form_for @partner_request, url: partners_requests_path(@partner_request),
html: {role: 'form', class: 'form-horizontal'}, method: :post, data: { controller: 'form-input', confirmation_target: "form" } do |form| %>
<%= form.input :comments, label: "Comments:", as: :text, class: "form-control", wrapper: :input_group %>
<%= hidden_field_tag :partner_id, params[:partner_id] %>

<table class='table'>
<thead>
Expand All @@ -61,7 +62,7 @@
</div>
<div class="card-footer">
<!-- TODO(chaserx): we should add some js to prevent submission if the items selected are the blank option or any item has an empty quantity -->
<%= form.submit("Submit Essentials Request", class: "btn btn-success", data: { action: "click->confirmation#openModal" }) %> <%= link_to "Cancel Request", partners_requests_path, class: "btn btn-danger" %>
<%= form.submit("Submit Essentials Request", class: "btn btn-success", data: { action: "click->confirmation#openModal" }) %> <%= link_to "Cancel Request", (current_partner ? partners_requests_path : requests_path), class: "btn btn-danger" %>
</div>
<% end %>

Expand Down
18 changes: 18 additions & 0 deletions app/views/requests/_new.html.erb
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<div id="newRequest" class="modal fade">
<div class="modal-dialog modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h4 class="modal-title">New Quantity Request</h4>
<button type="button" class="close" data-bs-dismiss="modal" aria-label="Close">
<span aria-hidden="true">&times;</span>
</button>
</div>
<div class="modal-body p-4">
<%= form_with(url: new_partners_request_path, method: :get) do |form| %>
<%= form.collection_select(:partner_id, @partners.active, :id, :name, {include_blank: "Select a partner", required: true}, {class: "form-control mb-2"} ) %>
<%= submit_button({text: "Next", icon: nil, name: ""}) %>
<% end %>
</div>
</div>
</div>
</div>
30 changes: 18 additions & 12 deletions app/views/requests/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,23 @@
</div>
</div>
<div class="card-footer">
<%= filter_button %>
<%= clear_filter_button %>
<%= modal_button_to("#calculateTotals", {text: "Calculate Product Totals", icon: nil, size: "md", type: "success"}) %>
<span class="float-right">
<% if @unfulfilled_requests_count > 0 %>
<%= print_button_to(
print_unfulfilled_requests_path(format: :pdf),
text: "Print Unfulfilled Picklists (#{@unfulfilled_requests_count})",
size: "md") %>
<% end %>
<%= download_button_to(requests_path(format: :csv, filters: filter_params.merge(date_range: date_range_params)), {text: "Export Requests", size: "md"}) if @requests.any? %>
</span>
<div class="d-flex flex-wrap justify-content-between gap-2">
<div class="d-flex flex-wrap gap-2">
<%= filter_button %>
<%= clear_filter_button %>
<%= modal_button_to("#calculateTotals", {text: "Calculate Product Totals", icon: nil, size: "md", type: "success"}) %>
</div>
<div class="d-flex flex-wrap gap-2">
<% if @unfulfilled_requests_count > 0 %>
<%= print_button_to(
print_unfulfilled_requests_path(format: :pdf),
text: "Print Unfulfilled Picklists (#{@unfulfilled_requests_count})",
size: "md") %>
<% end %>
<%= download_button_to(requests_path(format: :csv, filters: filter_params.merge(date_range: date_range_params)), {text: "Export Requests", size: "md"}) if @requests.any? %>
<%= modal_button_to("#newRequest", text: "New Quantity Request", icon: "plus", type: "success") if current_user.has_role?(Role::ORG_ADMIN, current_organization) %>
</div>
</div>
</div>
<% end # form %>
</div>
Expand Down Expand Up @@ -122,4 +127,5 @@
</div>
</div>
<%= render partial: "calculate_product_totals" %>
<%= render partial: "new" %>
</section>
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
<p>Hello <%= @partner.name %>,</p>

<p>This is an email confirmation that the <%= @organization.name %> has received your request of:</p>
<p>This email confirms that <%= @organization.name %> has received a request<%= " submitted by #{@requester_user_name}" if @requester_user_name.present? %> for:</p>
<ul>
<% @request_items.each do |item| %>
<li><%= item['name'] %> -
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Hello <%= @partner.name %>,

This is an email confirmation that the <%= @organization.name %> has received your request of:
This email confirms that <%= @organization.name %> has received a request<%= " submitted by #{@requester_user_name}" if @requester_user_name.present? %> for:
<% @request_items.each do |item| %>
<%= item['name'] %> - <%= item['quantity'] || item['person_count'] %>
<% end %>
Expand Down
7 changes: 7 additions & 0 deletions docs/user_guide/bank/essentials_requests.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,13 @@ If you have set the [Partner](getting_started_partners.md) to receive emails for
the partner will be sent an email letting them know that their Request has been fulfilled. This will contain the text you have [customized](getting_started_customization.md), with an attachment showing the details of the distribution.
![Example distribution printout](images/essentials/distributions/essentials_distributions_printout.png)

## Submitting a Request on Behalf of a Partner
Bank admins can submit a Request on behalf of a Partner by clicking the "New Quantity Request" button.
![New Request button](images/essentials/requests/essentials_requests_new_request_button.png)

This will bring up a list of Partners to choose from. After selecting a Partner, the details for the Request can be entered.
![New Request details](images/essentials/requests/essentials_requests_new_request_details.png)

## Cancelling a Request
To cancel a Request from the Requests list, click the "cancel" button beside it.
![navigation to cancel Request](images/essentials/requests/essentials_requests_cancel_navigation.png)
Expand Down
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions lib/previews/requests_confirmation_mailer_preview.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class RequestsConfirmationMailerPreview < ActionMailer::Preview
def confirmation_email_with_requester
RequestsConfirmationMailer.confirmation_email(Request.last)
end

def confirmation_email_without_requester
request = Request.last
request.partner_user_id = nil
RequestsConfirmationMailer.confirmation_email(request)
end
end
Loading

0 comments on commit b818390

Please sign in to comment.