Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add feature to print unfulfilled requests as a picklists PDF #4598

Merged
merged 18 commits into from
Oct 13, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 19 additions & 1 deletion app/controllers/requests_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ def index
.undiscarded
.during(helpers.selected_range)
.class_filter(filter_params)

@unfulfilled_requests_count = current_organization.requests.where(status: [:pending, :started]).count
@paginated_requests = @requests.page(params[:page])
@calculate_product_totals = RequestsTotalItemsService.new(requests: @requests).calculate
@items = current_organization.items.alphabetized
Expand Down Expand Up @@ -40,6 +40,24 @@ def start
redirect_to new_distribution_path(request_id: request.id)
end

def print_unfulfilled
requests = current_organization
.requests
.includes(:item_requests, partner: [:profile])
norrismei marked this conversation as resolved.
Show resolved Hide resolved
.where(status: [:pending, :started])
.order(created_at: :desc)

respond_to do |format|
format.any do
pdf = PicklistsPdf.new(current_organization, requests)
send_data pdf.compute_and_render,
filename: format("Picklists_%s.pdf", Time.current.to_fs(:long)),
type: "application/pdf",
disposition: "inline"
end
end
end

private

def load_items
Expand Down
167 changes: 167 additions & 0 deletions app/pdfs/picklists_pdf.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,167 @@
# Configures a Prawn PDF template for generating Distribution manifests
class PicklistsPdf
include Prawn::View
include ItemsHelper

def initialize(organization, requests)
@requests = requests
@organization = organization
end

def compute_and_render
font_families["OpenSans"] = PrawnRails.config["font_families"][:OpenSans]
font "OpenSans"
font_size 10
footer_height = 35

@requests.each do |request|
logo_image = if @organization.logo.attached?
StringIO.open(@organization.logo.download)
else
Organization::DIAPER_APP_LOGO
end

# Bounding box containing non-footer elements
bounding_box [bounds.left, bounds.top], width: bounds.width, height: bounds.height - footer_height do
image logo_image, fit: [250, 85]

bounding_box [bounds.right - 225, bounds.top], width: 225, height: 85 do
text @organization.name, align: :right
text @organization.address, align: :right
text @organization.email, align: :right
end

text "Requested by:", style: :bold
font_size 12
text request.partner.name
move_up 24

text "Partner Primary Contact:", style: :bold, align: :right
font_size 12
text request.partner.profile.primary_contact_name, align: :right
font_size 10
text request.partner.profile.primary_contact_email, align: :right
text request.partner.profile.primary_contact_phone, align: :right
move_down 10

if request.partner.profile.pick_up_name.present?
move_up 10
text "Partner Pickup Person:", style: :bold
font_size 12
text request.partner.profile.pick_up_name
font_size 10
text request.partner.profile.pick_up_email
text request.partner.profile.pick_up_phone
move_up 24

text "Requested on:", style: :bold, align: :right
font_size 12
text request.created_at.to_fs(:date_picker), align: :right
font_size 10
move_down 30
else
text "Requested on:", style: :bold
font_size 12
text request.created_at.to_fs(:date_picker)
font_size 10
end

if @organization.ytd_on_distribution_printout
move_up 22
text "Items Received Year-to-Date:", style: :bold, align: :right
font_size 12
text request.partner.quantity_year_to_date.to_s, align: :right
font_size 10
end

move_down 10
text "Comments:", style: :bold
font_size 12
text request.comments

move_down 20

line_items = request.item_requests
data = has_custom_units?(line_items) ? data_with_units(line_items) : data_no_units(line_items)

font_size 11

# Line item table
table(data, width: bounds.width, column_widths: {1 => 65, -2 => 35}) do
self.header = true
self.cell_style = {padding: [5, 10, 5, 10]}
self.row_colors = %w[dddddd ffffff]

cells.borders = []

# Header row
row(0).borders = [:bottom]
row(0).border_width = 2
row(0).font_style = :bold
row(0).size = 10
row(0).column(1..-1).borders = %i[bottom left]
end
end

start_new_page unless request == @requests.last
end

repeat :all do
# Page footer
bounding_box [bounds.left, bounds.bottom + footer_height], width: bounds.width do
stroke_bounds
font "OpenSans"
font_size 9
stroke_horizontal_rule
move_down 5

logo_offset = (bounds.width - 190) / 2
bounding_box([logo_offset, 0], width: 190, height: 33) do
text "Lovingly created with", valign: :center
image Organization::DIAPER_APP_LOGO, width: 75, vposition: :center, position: :right
end
end
end

number_pages "Page <page> of <total>",
start_count_at: 1,
at: [bounds.right - 130, 22],
align: :right

render
end

def has_custom_units?(line_items)
Flipper.enabled?(:enable_packs) && line_items.any? { |line_item| line_item.request_unit }
end

def data_with_units(line_items)
data = [["Items Requested",
"Quantity",
"Unit (if applicable)",
"[X]",
"Differences / Comments"]]

data + line_items.map do |line_item|
[line_item.name,
line_item.quantity,
line_item.request_unit&.capitalize&.pluralize(line_item.quantity),
"[ ]",
""]
end
end

def data_no_units(line_items)
data = [["Items Requested",
"Quantity",
"[X]",
"Differences / Comments"]]

data + line_items.map do |line_item|
[line_item.name,
line_item.quantity,
"[ ]",
""]
end
end
end
6 changes: 6 additions & 0 deletions app/views/requests/index.html.erb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,12 @@
<%= 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>
Expand Down
1 change: 1 addition & 0 deletions config/routes.rb
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,7 @@ def set_up_flipper
member do
post :start
end
get :print_unfulfilled, on: :collection
end
resources :requests, except: %i(destroy) do
resource :cancelation, only: [:new, :create], controller: 'requests/cancelation'
Expand Down
4 changes: 4 additions & 0 deletions spec/factories/requests.rb
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@ def random_request_items
status { 'pending' }
end

trait :discarded do
status { 'discarded' }
end

trait :with_varied_quantities do
request_items {
# get 10 unique item ids
Expand Down
87 changes: 87 additions & 0 deletions spec/pdfs/picklists_pdf_spec.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
describe PicklistsPdf do
let(:organization) { create(:organization) }
let(:item1) { create(:item, name: "Item 1", organization: organization) }
let(:item2) { create(:item, name: "Item 2", organization: organization) }

describe "#compute_and_render" do
it "renders multiple requests correctly" do
request1 = create(:request, :pending, organization: organization)
request2 = create(:request, :pending, organization: organization)
create(:item_request, request: request1, item: item1, name: "Item 1")
create(:item_request, request: request2, item: item2, name: "Item 2")

pdf = described_class.new(organization, [request1, request2])
pdf_test = PDF::Reader.new(StringIO.new(pdf.compute_and_render))

expect(pdf_test.page(1).text).to include(request1.partner.name)
expect(pdf_test.page(1).text).to include(request1.partner.profile.primary_contact_name)
expect(pdf_test.page(1).text).to include(request1.partner.profile.primary_contact_email)
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("Items Requested")
expect(pdf_test.page(1).text).to include("Item 1")

expect(pdf_test.page(2).text).to include(request2.partner.name)
expect(pdf_test.page(2).text).to include(request2.partner.profile.primary_contact_name)
expect(pdf_test.page(2).text).to include(request2.partner.profile.primary_contact_email)
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("Items Requested")
expect(pdf_test.page(2).text).to include("Item 2")
end

context "When partner pickup person is set" do
it "renders pickup person details" do
partner = create(:partner)
partner.profile.pick_up_name = "Paul Bunyan"
partner.profile.pick_up_email = "paul@kenton.com"
partner.profile.pick_up_phone = "503-123-4567"
request = create(:request, :pending, organization: organization, partner: partner)
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(request.partner.profile.pick_up_name)
expect(pdf_test.page(1).text).to include(request.partner.profile.pick_up_email)
expect(pdf_test.page(1).text).to include(request.partner.profile.pick_up_phone)
end
end
end

context "When packs are not enabled" do
specify "#data_no_units" do
request = create(:request, :pending, organization: organization)
create(:item_request, request: request, item: item1, name: "Item 1")
create(:item_request, request: request, item: item2, name: "Item 2")
pdf = described_class.new(organization, [request])
data = pdf.data_no_units(request.item_requests)

expect(data).to eq([
["Items Requested", "Quantity", "[X]", "Differences / Comments"],
["Item 1", "5", "[ ]", ""],
["Item 2", "5", "[ ]", ""]
])
end
end

context "When packs are enabled" do
before { Flipper.enable(:enable_packs) }

specify "#data_with_units" do
item_with_units = create(:item, name: "Item with units", organization: organization)
create(:item_unit, item: item_with_units, name: "Pack")
request = create(:request, :pending, organization: organization)
create(:item_request, request: request, item: item_with_units, name: "Item with units", request_unit: "Pack")
create(:item_request, request: request, item: item2, name: "Item 2")
pdf = described_class.new(organization, [request])
data = pdf.data_with_units(request.item_requests)

expect(data).to eq([
["Items Requested", "Quantity", "Unit (if applicable)", "[X]", "Differences / Comments"],
["Item with units", "5", "Packs", "[ ]", ""],
["Item 2", "5", nil, "[ ]", ""]
])
end
end
end
15 changes: 15 additions & 0 deletions spec/requests/requests_requests_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,21 @@

it { is_expected.to be_successful }
end

context "when there are pending or started requests" do
it "shows print unfulfilled picklists button with correct quantity" do
Request.delete_all

create(:request, :pending)
create(:request, :started)
create(:request, :fulfilled)
create(:request, :discarded)

get requests_path

expect(response.body).to include('Print Unfulfilled Picklists (2)')
end
end
end

describe 'GET #show' do
Expand Down