diff --git a/app/controllers/requests_controller.rb b/app/controllers/requests_controller.rb
index 4e4c53a367..82aa5567f7 100644
--- a/app/controllers/requests_controller.rb
+++ b/app/controllers/requests_controller.rb
@@ -42,6 +42,23 @@ def start
redirect_to new_distribution_path(request_id: request.id)
end
+ def print_picklist
+ request = current_organization
+ .requests
+ .includes(:item_requests, partner: [:profile])
+ .find(params[:id])
+
+ respond_to do |format|
+ format.any do
+ pdf = PicklistsPdf.new(current_organization, [request])
+ send_data pdf.compute_and_render,
+ filename: format("Picklists_%s.pdf", Time.current.to_fs(:long)),
+ type: "application/pdf",
+ disposition: "inline"
+ end
+ end
+ end
+
def print_unfulfilled
requests = current_organization
.requests
diff --git a/app/pdfs/picklists_pdf.rb b/app/pdfs/picklists_pdf.rb
index 5f1e1a6b55..9aea947f02 100644
--- a/app/pdfs/picklists_pdf.rb
+++ b/app/pdfs/picklists_pdf.rb
@@ -1,3 +1,5 @@
+require "prawn/table"
+
# Configures a Prawn PDF template for generating Distribution manifests
class PicklistsPdf
include Prawn::View
diff --git a/app/views/requests/_request_row.html.erb b/app/views/requests/_request_row.html.erb
index 86523947ed..a2ca86b30a 100644
--- a/app/views/requests/_request_row.html.erb
+++ b/app/views/requests/_request_row.html.erb
@@ -23,5 +23,6 @@
<%= view_button_to request_path(request_row) %>
<%= button_to 'Cancel', new_request_cancelation_path(request_id: request_row.id), method: :get, class: 'btn btn-danger btn-xs' %>
- |
+ <%= print_button_to print_picklist_request_path(request_row), { format: :pdf, text: "Print", size: "xs" } %>
+
diff --git a/app/views/requests/show.html.erb b/app/views/requests/show.html.erb
index 328e44b126..c2560ce0cd 100644
--- a/app/views/requests/show.html.erb
+++ b/app/views/requests/show.html.erb
@@ -108,6 +108,7 @@
diff --git a/config/routes.rb b/config/routes.rb
index c0c80f4f91..024f8743cc 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -241,6 +241,7 @@ def set_up_flipper
post :start
end
get :print_unfulfilled, on: :collection
+ get :print_picklist, on: :member
end
resources :requests, except: %i(destroy) do
resource :cancelation, only: [:new, :create], controller: 'requests/cancelation'
diff --git a/docs/user_guide/bank/essentials_requests.md b/docs/user_guide/bank/essentials_requests.md
index 8757d050eb..1e2eefcfb4 100644
--- a/docs/user_guide/bank/essentials_requests.md
+++ b/docs/user_guide/bank/essentials_requests.md
@@ -138,6 +138,16 @@ This will create a .csv file with the following information for each filtered Re
- the quantity requested
Note: If you use custom units, there will be a column for each item/unit that is available to be requested.
+## Printing Request picklists
+You can print out a "picklist" for any Request you can see. This function produces a pdf showing all items requested for the selected Request.
+
+This is visible on the Request detail page:
+
+
+
+And also visible on the Request list page:
+
+
## Printing unfulfilled Request picklists
Finally, you can also print "picklists" for your unfulfilled Requests.
diff --git a/docs/user_guide/bank/images/essentials/requests/essentials_requests_print_picklist_navigation.png b/docs/user_guide/bank/images/essentials/requests/essentials_requests_print_picklist_navigation.png
new file mode 100644
index 0000000000..118abd7a0a
Binary files /dev/null and b/docs/user_guide/bank/images/essentials/requests/essentials_requests_print_picklist_navigation.png differ
diff --git a/docs/user_guide/bank/images/essentials/requests/essentials_requests_print_picklist_navigation2.png b/docs/user_guide/bank/images/essentials/requests/essentials_requests_print_picklist_navigation2.png
new file mode 100644
index 0000000000..989a974362
Binary files /dev/null and b/docs/user_guide/bank/images/essentials/requests/essentials_requests_print_picklist_navigation2.png differ
diff --git a/spec/pdfs/picklists_pdf_spec.rb b/spec/pdfs/picklists_pdf_spec.rb
index 795d091684..42f55adbf9 100644
--- a/spec/pdfs/picklists_pdf_spec.rb
+++ b/spec/pdfs/picklists_pdf_spec.rb
@@ -5,8 +5,8 @@
describe "#compute_and_render" do
it "renders multiple requests correctly" do
- request1 = create(:request, :pending, organization: organization)
- request2 = create(:request, :pending, organization: organization)
+ request1 = create(:request, :pending, organization: organization, comments: "Request 1 comments")
+ request2 = create(:request, :pending, organization: organization, comments: "Request 2 comments")
create(:item_request, request: request1, item: item1, name: "Item 1")
create(:item_request, request: request2, item: item2, name: "Item 2")
@@ -19,6 +19,7 @@
expect(pdf_test.page(1).text).to include("Requested on:")
expect(pdf_test.page(1).text).to include("Items Received Year-to-Date:")
expect(pdf_test.page(1).text).to include("Comments")
+ expect(pdf_test.page(1).text).to include(request1.comments)
expect(pdf_test.page(1).text).to include("Items Requested")
expect(pdf_test.page(1).text).to include("Item 1")
@@ -28,10 +29,29 @@
expect(pdf_test.page(2).text).to include("Requested on:")
expect(pdf_test.page(2).text).to include("Items Received Year-to-Date:")
expect(pdf_test.page(2).text).to include("Comments")
+ expect(pdf_test.page(2).text).to include(request2.comments)
expect(pdf_test.page(2).text).to include("Items Requested")
expect(pdf_test.page(2).text).to include("Item 2")
end
+ context "when ytd_on_distribution_printout is enabled for the organization" do
+ before { organization.update(ytd_on_distribution_printout: true) }
+
+ it "renders the YTD quantity" do
+ partner = create(:partner)
+ request = create(:request, :pending, organization: organization, partner: partner)
+ create(:item_request, request: request, item: item1, name: "Item 1", quantity: 17)
+
+ # stub out the quantity_year_to_date method, it's not the PDF's job to make sure the calculation is correct
+ allow(partner).to receive(:quantity_year_to_date).and_return(17827)
+ pdf = described_class.new(organization, [request])
+ pdf_test = PDF::Reader.new(StringIO.new(pdf.compute_and_render))
+
+ expect(pdf_test.page(1).text).to include("Items Received Year-to-Date:")
+ expect(pdf_test.page(1).text).to include("17827")
+ end
+ end
+
context "When partner pickup person is set" do
it "renders pickup person details" do
partner = create(:partner)
diff --git a/spec/requests/partners/requests_spec.rb b/spec/requests/partners/requests_spec.rb
index 261a5b0737..24d537f38a 100644
--- a/spec/requests/partners/requests_spec.rb
+++ b/spec/requests/partners/requests_spec.rb
@@ -431,6 +431,112 @@
end
end
+ describe "GET #print_unfulfilled" do
+ let(:item1) { create(:item, name: "Good item") }
+ let(:item2) { create(:item, name: "Crap item") }
+ let(:partner1) { create(:partner, organization: organization) }
+ let(:partner_user) { partner1.primary_user }
+ let!(:pending_request) { create(:request, :with_item_requests, :pending, partner: partner1, request_items: [{ item_id: item1.id, quantity: '100' }]) }
+ let!(:started_request) { create(:request, :with_item_requests, :started, partner: partner1, request_items: [{ item_id: item2.id, quantity: '50' }]) }
+ let!(:discarded_request) { create(:request, :with_item_requests, :discarded, partner: partner1, request_items: [{ item_id: item2.id, quantity: '30' }]) }
+ let!(:fulfilled_request) { create(:request, :with_item_requests, :fulfilled, partner: partner1, request_items: [{ item_id: item2.id, quantity: '20' }]) }
+
+ before do
+ partner_user.add_role(Role::ORG_ADMIN, organization)
+ sign_in(partner_user)
+ get print_unfulfilled_requests_path(format: :pdf)
+ end
+
+ it "returns a PDF file" do
+ PDF::Reader.new(StringIO.new(response.body))
+ expect(response.content_type).to eq('application/pdf')
+ expect(response.headers['Content-Disposition']).to include('inline')
+ expect(response.body.bytes[0..3]).to eq('%PDF'.bytes)
+ end
+
+ it "includes only 'pending' and 'started' requests" do
+ pdf_content = PDF::Reader.new(StringIO.new(response.body))
+ # this is a semi-lazy check, since we're ensuring 1 page for each request. In real world,
+ # it's possible that there could be more than 1 page per request if the request is long.
+
+ expect(pdf_content.page_count).to eq(2)
+ end
+
+ it "calls compute_and_render with the 2 matching requests" do
+ # Create a double for the PDF instance
+ pdf_double = double("PicklistsPdf")
+
+ # Expect PicklistsPdf.new to be called with correct args and return our double
+ expect(PicklistsPdf).to receive(:new)
+ .with(organization, kind_of(ActiveRecord::Relation))
+ .and_return(pdf_double)
+
+ # Expect compute_and_render to be called on our double and return some PDF data
+ # We don't really care about the content, the PDF model is tested elsewhere
+ expect(pdf_double).to receive(:compute_and_render)
+ .and_return("fake pdf content")
+
+ # Make the request
+ get print_unfulfilled_requests_path(format: :pdf)
+
+ # Verify the response
+ expect(response).to be_successful
+ expect(response.content_type).to eq("application/pdf")
+ expect(response.headers["Content-Disposition"]).to include("inline")
+ expect(response.body).to eq("fake pdf content")
+ end
+ end
+
+ describe "GET #print_picklist" do
+ let(:organization) { create(:organization) }
+ let(:partner) { create(:partner, organization: organization) }
+ let(:partner_user) { partner.primary_user }
+ let(:org_admin) { create(:organization_admin, organization: organization) }
+ let(:request) { create(:request, :with_item_requests, organization: organization, partner: partner, partner_user: org_admin) }
+
+ before do
+ sign_in(org_admin)
+ end
+
+ it "generates a PDF for a single request" do
+ # Create a double for the PDF instance
+ pdf_double = double("PicklistsPdf")
+
+ # Expect PicklistsPdf.new to be called with correct args and return our double
+ expect(PicklistsPdf).to receive(:new)
+ .with(organization, [request])
+ .and_return(pdf_double)
+
+ # Expect compute_and_render to be called on our double and return some PDF data
+ expect(pdf_double).to receive(:compute_and_render)
+ .and_return("fake pdf content")
+
+ # Make the request
+ get print_picklist_request_path(request, format: :pdf)
+
+ # Verify the response
+ expect(response).to be_successful
+ expect(response.content_type).to eq("application/pdf")
+ expect(response.headers["Content-Disposition"]).to include("inline")
+ expect(response.headers["Content-Disposition"]).to include("Picklists_")
+ expect(response.body).to eq("fake pdf content")
+ end
+
+ it "includes correct associations in the query" do
+ pdf_double = double("PicklistsPdf", compute_and_render: "pdf content")
+
+ expect(PicklistsPdf).to receive(:new) do |org, requests|
+ # Verify the request includes the necessary associations
+ expect(requests.first.association(:item_requests)).to be_loaded
+ expect(requests.first.association(:partner)).to be_loaded
+ expect(requests.first.partner.association(:profile)).to be_loaded
+ pdf_double
+ end
+
+ get print_picklist_request_path(request, format: :pdf)
+ end
+ end
+
describe 'POST #validate' do
it 'should handle missing CSRF gracefully' do
sign_in(partner_user)