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: +![Print Picklist](images/essentials/requests/essentials_requests_print_picklist_navigation.png) + + +And also visible on the Request list page: +![Print Picklist](images/essentials/requests/essentials_requests_print_picklist_navigation2.png) + ## 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)