diff --git a/app/controllers/requests_controller.rb b/app/controllers/requests_controller.rb index ce5677da0c..0df37c5993 100644 --- a/app/controllers/requests_controller.rb +++ b/app/controllers/requests_controller.rb @@ -3,39 +3,16 @@ class RequestsController < ApplicationController def index setup_date_range_picker - @requests = current_organization - .ordered_requests - .undiscarded - .during(helpers.selected_range) - .class_filter(filter_params) - @unfulfilled_requests_count = current_organization.requests.where(status: [:pending, :started]).during(helpers.selected_range).class_filter(filter_params).count - @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, :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) - @selected_request_type = filter_params[:by_request_type] - @selected_request_item = filter_params[:by_request_item_id] - @selected_partner = filter_params[:by_partner] - @selected_status = filter_params[:by_status] + @requests_info = View::Requests.new(params: params, organization: current_organization, helpers: helpers) respond_to do |format| format.html - format.csv { send_data Exports::ExportRequestService.new(@requests).generate_csv, filename: "Requests-#{Time.zone.today}.csv" } + format.csv { send_data Exports::ExportRequestService.new(@requests_info.requests).generate_csv, filename: "Requests-#{Time.zone.today}.csv" } end end def show - @request = current_organization.requests.find(params[:id]) - @item_requests = @request.item_requests.includes(:item) - - @inventory = View::Inventory.new(@request.organization_id) - @default_storage_location = @request.partner.default_storage_location_id || @request.organization.default_storage_location - @location = StorageLocation.find_by(id: @default_storage_location) - - @custom_units = Flipper.enabled?(:enable_packs) && @request.item_requests.any? { |item| item.request_unit } + @request_info = View::RequestInfo.new(params:, organization: current_organization) end # Clicking the "New Distribution" button will set the the request to started diff --git a/app/models/view/donations.rb b/app/models/view/donations.rb index 9518e98047..24e52d16f1 100644 --- a/app/models/view/donations.rb +++ b/app/models/view/donations.rb @@ -31,7 +31,6 @@ def from_params(params:, organization:, helpers:) .includes(:storage_location, :donation_site, :product_drive, - :product_drive_participant, :manufacturer, line_items: [:item]) .order(created_at: :desc) diff --git a/app/models/view/request_info.rb b/app/models/view/request_info.rb new file mode 100644 index 0000000000..682a34eefd --- /dev/null +++ b/app/models/view/request_info.rb @@ -0,0 +1,33 @@ +module View + class RequestInfo + attr_reader :request + + def initialize(params:, organization:) + @request = organization.requests.find(params[:id]) + end + + def item_requests + @item_requests ||= @request.item_requests.includes(:item) + end + + def inventory + @inventory ||= View::Inventory.new(@request.organization_id) + end + + def default_storage_location + return @default_storage_location if defined?(@default_storage_location) + + @efault_storage_location ||= @request.partner.default_storage_location_id || @request.organization.default_storage_location + end + + def location + return @location if defined?(@location) + + @location ||= StorageLocation.find_by(id: default_storage_location) + end + + def custom_units + Flipper.enabled?(:enable_packs) && request.item_requests.any? { |item| item.request_unit } + end + end +end diff --git a/app/models/view/requests.rb b/app/models/view/requests.rb new file mode 100644 index 0000000000..a82153a304 --- /dev/null +++ b/app/models/view/requests.rb @@ -0,0 +1,79 @@ +module View + class Requests + include DateRangeHelper + + attr_reader :filters, :helpers, :organization, :paginated_requests, :params, :requests + + def initialize(params:, organization:, helpers:) + @params = params + @organization = organization + @filters = filter_params(params) + @helpers = helpers + + @requests = organization + .ordered_requests + .undiscarded + .during(helpers.selected_range) + .class_filter(filters) + + @paginated_requests = requests.includes(:partner).page(params[:page]) + end + + def filter_params(params = {}) + if params.key?(:filters) + params.require(:filters).permit(:by_request_item_id, :by_partner, :by_status, :by_request_type) + else + {} + end + end + + def unfulfilled_requests_count + @unfulfilled_requests_count ||= organization + .requests + .where(status: [:pending, :started]) + .during(helpers.selected_range) + .class_filter(filters) + .count + end + + def calculate_product_totals + @calculate_product_totals ||= RequestsTotalItemsService.new(requests: requests).calculate + end + + def items + @items ||= organization.items.alphabetized.select(:id, :name) + end + + def partners + @partners ||= organization.partners.alphabetized.select(:id, :name, :status) + end + + def statuses + @statuses ||= Request.statuses.transform_keys(&:humanize) + end + + def partner_users + @partner_users ||= User.where(id: paginated_requests.map(&:partner_user_id)).select(:id, :name, :email) + end + + def request_types + @request_types ||= Request.request_types.transform_keys(&:humanize) + end + + def selected_request_type + filters[:by_request_type] + end + + def selected_request_item + filters[:by_request_item_id] + end + + def selected_partner + filters[:by_partner] + end + + def selected_status + filters[:by_status] + end + end +end diff --git a/app/views/requests/_calculate_product_totals.html.erb b/app/views/requests/_calculate_product_totals.html.erb index 5410923fb9..add383533f 100644 --- a/app/views/requests/_calculate_product_totals.html.erb +++ b/app/views/requests/_calculate_product_totals.html.erb @@ -16,7 +16,7 @@ - <% @calculate_product_totals.sort_by { |name, quantity| name.downcase }.each do |name, quantity| %> + <% @requests_info.calculate_product_totals.sort_by { |name, quantity| name.downcase }.each do |name, quantity| %> <%= name %> <%= quantity %> diff --git a/app/views/requests/_new.html.erb b/app/views/requests/_new.html.erb index 9463b0b9ff..a4a1c075e9 100644 --- a/app/views/requests/_new.html.erb +++ b/app/views/requests/_new.html.erb @@ -10,7 +10,7 @@ diff --git a/app/views/requests/show.html.erb b/app/views/requests/show.html.erb index 19a44d5cd5..b3bd1f21d2 100644 --- a/app/views/requests/show.html.erb +++ b/app/views/requests/show.html.erb @@ -2,10 +2,10 @@
- <% content_for :title, "Requests - #{@request.id} - #{current_organization.name}" %> + <% content_for :title, "Requests - #{@request_info.request.id} - #{current_organization.name}" %>

Request - from <%= @request.partner.name %> + from <%= @request_info.request.partner.name %>

@@ -29,7 +29,7 @@
-

This request was sent on <%= @request.created_at.to_fs(:distribution_date) %>

+

This request was sent on <%= @request_info.request.created_at.to_fs(:distribution_date) %>

@@ -44,11 +44,11 @@ - - - - - + + + + +
<%= @request.partner.name %><%= @request.partner_user&.formatted_email %><%= @request.request_type&.humanize %><%= render partial: "status", locals: {status: @request.status} %><%= @request.comments || 'None' %><%= @request_info.request.partner.name %><%= @request_info.request.partner_user&.formatted_email %><%= @request_info.request.request_type&.humanize %><%= render partial: "status", locals: {status: @request_info.request.status} %><%= @request_info.request.comments || 'None' %>
@@ -67,36 +67,36 @@ Item Quantity - <% if @custom_units %> + <% if @request_info.custom_units %> Units (if applicable) <% end %> - <% if @default_storage_location %> + <% if @request_info.default_storage_location %> Default storage location inventory <% end %> Total Inventory - <% @item_requests.each do |item_request| %> + <% @request_info.item_requests.each do |item_request| %> <%= item_request.item_name %> <%= item_request.quantity %> - <% if @custom_units %> + <% if @request_info.custom_units %> <%= item_request.request_unit&.pluralize(item_request.quantity.to_i) %> <% end %> - <% if @default_storage_location %> - <% on_hand_for_location = @inventory.quantity_for(storage_location: @location&.id, item_id: item_request.item_id) %> + <% if @request_info.default_storage_location %> + <% on_hand_for_location = @request_info.inventory.quantity_for(storage_location: @request_info.location&.id, item_id: item_request.item_id) %> <%= on_hand_for_location&.positive? ? on_hand_for_location : 'N/A' %> <% end %> - <% on_hand = @inventory.quantity_for(item_id: item_request.item_id) %> + <% on_hand = @request_info.inventory.quantity_for(item_id: item_request.item_id) %> <%= on_hand || 0 %> <% end %> Total (Quota) - <%= @request.total_items %> - <%= quota_display(@request.partner) %> + <%= @request_info.request.total_items %> + <%= quota_display(@request_info.request.partner) %> @@ -104,11 +104,11 @@
diff --git a/spec/models/view/request_info_spec.rb b/spec/models/view/request_info_spec.rb new file mode 100644 index 0000000000..16ba43a575 --- /dev/null +++ b/spec/models/view/request_info_spec.rb @@ -0,0 +1,152 @@ +RSpec.describe View::RequestInfo do + describe "#item_requests" do + context "when the request has item requests" do + it "returns the request's item requests" do + organization = build(:organization) + item = build(:item) + item_request = build(:item_request, item: item, quantity: 5) + request = create( + :request, + organization:, + item_requests: [item_request] + ) + + request_info = View::RequestInfo.new(params: {id: request.id}, organization:) + + expect(request_info.item_requests).to eq([item_request]) + end + end + + context "when the request has no item requests" do + it "returns an empty array" do + organization = build(:organization) + request = create(:request, organization:) + + request_info = View::RequestInfo.new(params: {id: request.id}, organization:) + + expect(request_info.item_requests).to eq([]) + end + end + end + + describe "#inventory" do + it "returns a View::Inventory instance" do + organization = build(:organization) + request = create(:request, organization:) + + request_info = View::RequestInfo.new(params: {id: request.id}, organization:) + + expect(request_info.inventory).to be_a_kind_of(View::Inventory) + end + end + + describe "#default_storage_location" do + context "when the request partner has a default_storage_location_id" do + it "returns the request partner's default_storage_location_id" do + storage_location = build(:storage_location) + organization = build(:organization) + request = create( + :request, + partner: build(:partner, default_storage_location_id: storage_location.id), + organization: + ) + + request_info = View::RequestInfo.new(params: {id: request.id}, organization:) + + expect(request_info.default_storage_location).to eq(storage_location.id) + end + end + + context "when the request organization has a default_storage_location_id" do + it "returns the request organization's default_storage_location_id" do + organization = build(:organization) + storage_location = build(:storage_location, organization:) + organization.update!(default_storage_location: storage_location.id) + request = create( + :request, + organization: + ) + + request_info = View::RequestInfo.new(params: {id: request.id}, organization:) + + expect(request_info.default_storage_location).to eq(storage_location.id) + end + end + end + + describe "#location" do + context "when no storage location is found" do + it "returns nil" do + organization = build(:organization) + request = create(:request, organization:) + + request_info = View::RequestInfo.new(params: {id: request.id}, organization:) + + expect(request_info.location).to be_nil + end + end + + context "when a storage location is found" do + it "returns the found location" do + organization = build(:organization) + storage_location = create(:storage_location, organization:) + organization.update!(default_storage_location: storage_location.id) + request = create( + :request, + organization: + ) + + request_info = View::RequestInfo.new(params: {id: request.id}, organization:) + + expect(request_info.location).to be_a_kind_of(StorageLocation) + end + end + end + + describe "#custom_units" do + context "when there are request units for an item request" do + context "when enable_packs is disabled" do + it "returns false" do + organization = build(:organization) + item = build(:item, name: "First item") + create(:item_unit, item: item, name: "flat") + request = create( + :request, + :with_item_requests, + organization:, + request_items: [ + {item_id: item.id, quantity: "559", request_unit: "flat"} + ] + ) + + request_info = View::RequestInfo.new(params: {id: request.id}, organization:) + + expect(request_info.custom_units).to be_falsey + end + end + + context "when enable_packs is enabled" do + it "returns true" do + Flipper.enable(:enable_packs) + + organization = build(:organization) + item = build(:item, name: "First item") + create(:item_unit, item: item, name: "flat") + request = create( + :request, + :with_item_requests, + organization:, + request_items: [ + {item_id: item.id, quantity: "559", request_unit: "flat"} + ] + ) + request_info = View::RequestInfo.new(params: {id: request.id}, organization:) + + expect(request_info.custom_units).to be_truthy + + Flipper.disable(:enable_packs) + end + end + end + end +end diff --git a/spec/models/view/requests_spec.rb b/spec/models/view/requests_spec.rb new file mode 100644 index 0000000000..3798b1a5e9 --- /dev/null +++ b/spec/models/view/requests_spec.rb @@ -0,0 +1,127 @@ +RSpec.describe View::Requests do + describe "#unfulfilled_requests_count" do + it "returns the unfulfilled requests count" do + organization = build(:organization) + create(:request, :pending, organization:) + create(:request, :started, organization:) + create(:request, :fulfilled, organization:) + + requests = View::Requests.new(params: {}, organization:, helpers:) + + expect(requests.unfulfilled_requests_count).to eq(2) + end + end + + describe "#calculate_product_totals" do + it "returns the calculated product total items" do + organization = build(:organization) + build(:request, organization:) + total_items_service_double = instance_double(RequestsTotalItemsService, calculate: {"Diaper" => 10}) + allow(RequestsTotalItemsService).to receive(:new).with(requests: organization.requests).and_return(total_items_service_double) + + requests = View::Requests.new(params: {}, organization:, helpers:) + + expect(requests.calculate_product_totals).to eq({"Diaper" => 10}) + end + end + + describe "#items" do + it "returns the organization's items id and name, alphabetized" do + organization = build(:organization) + build(:request, organization:) + base_item = build(:base_item) + item_two = create(:item, base_item:, organization:, name: "B item") + item_one = create(:item, base_item:, organization:, name: "A item") + + requests = View::Requests.new(params: {}, organization:, helpers:) + + expect(requests.items.map(&:id)).to eq([item_one.id, item_two.id]) + expect(requests.items.map(&:name)).to eq(["A item", "B item"]) + end + end + + describe "#partners" do + it "returns the organization's partners id, name and status, alphabetized" do + organization = build(:organization) + build(:request, organization:) + partner_two = create(:partner, organization:, name: "B partner", status: "approved") + partner_one = create(:partner, organization:, name: "A partner", status: "invited") + + requests = View::Requests.new(params: {}, organization:, helpers:) + + expect(requests.partners.map(&:id)).to eq([partner_one.id, partner_two.id]) + expect(requests.partners.map(&:name)).to eq(["A partner", "B partner"]) + expect(requests.partners.map(&:status)).to eq(["invited", "approved"]) + end + end + + describe "#statuses" do + it "returns the request statuses" do + organization = build(:organization) + build(:request, organization:) + humanized_statuses = {"Discarded" => 3, "Fulfilled" => 2, "Pending" => 0, "Started" => 1} + + requests = View::Requests.new(params: {}, organization:, helpers:) + + expect(requests.statuses).to eq(humanized_statuses) + end + end + + describe "#partner_users" do + it "returns the organization's partner users id, name and email" do + organization = build(:organization) + partner_user = create(:partner_user, name: "Jane Smith", email: "janesmith@example.com") + create(:request, organization:, partner_user:) + + requests = View::Requests.new(params: {}, organization:, helpers:) + + expect(requests.partner_users.map(&:id)).to eq([partner_user.id]) + expect(requests.partner_users.map(&:name)).to eq(["Jane Smith"]) + expect(requests.partner_users.map(&:email)).to eq(["janesmith@example.com"]) + end + end + + describe "#request_types" do + it "returns the request types" do + organization = build(:organization) + build(:request, organization:) + humanized_types = {"Child" => "child", "Individual" => "individual", "Quantity" => "quantity"} + + requests = View::Requests.new(params: {}, organization:, helpers:) + + expect(requests.request_types).to eq(humanized_types) + end + end + + describe "selected filter params" do + it "returns the given filter params" do + organization = build(:organization) + build(:request, organization:) + params = ActionController::Parameters.new( + { + filters: { + by_request_type: "quantity", + by_request_item_id: "1", + by_partner: "A Local Partner", + by_status: "pending" + } + } + ).permit! + + requests = View::Requests.new(params:, organization:, helpers:) + + expect(requests.selected_request_type).to eq("quantity") + expect(requests.selected_request_item).to eq("1") + expect(requests.selected_partner).to eq("A Local Partner") + expect(requests.selected_status).to eq("pending") + end + end + + def helpers + Module.new do + def self.selected_range + (1.month.ago..2.days.from_now) + end + end + end +end