Skip to content

Commit 7450be0

Browse files
authored
Fix #4138 fix delivery address (#4540)
* REFACTOR group examples, remove rubocop disable lines, * group examples together with variables * rubocop -A passes on this file without needing to disable ArrayAlignment * RED add rspec to test address output in distribution PDFs * Compare generated against expected PDFs * Add helper test to regenerate expected PDFs * Add helper module * GREEN Fix #4138 address output changes based on delivery method * Address output prints delivery address if filled in, otherwise partner address * Only does this for delivery/shipped method * BUGFIX pdf now writes when test fails * RED add rspec for when partner has no addresses * REFACTOR out profile name and email * GREEN don't print address if no address or delivery address * Add comments clarifying gitignore and comparison pdf helper rspec * Update bundler version * REFACTOR replace helper test with environment variable * Use environment variable for regenerating comparison pdfs * update schema date and bundler version * RED remove env variable code, stub out console helper method * GREEN add Rails console method for generating comparison pdfs, update comparison PDFs * Replace instance vars with structs and let * FIX destroy request so db is clean, use destroy! instead so exceptions fire * Replace FactoryBot with calling ActiveModel.create, move methods to lib/ * Fix formatting when address is incomplete, add rspecs, create and delete item units correctly when generating test files * FIX replace with space if partner primary contact name/email/phone is blank * Add rspec * Other comparison pdfs change because previously blank phone # skipped line, now blank phone # adds a blank line * Move instance_method call inside function * FIX spacing between issued to and delivery address * RED Make methods module instead of instance * Move helper compare_pdf method into Rspec group * Failing tests are due to Items received year to date being inaccurate in new year * GREEN fix test and comparison pdf generation errors * travel_to time when creating distribution so Items received YTD is always the same * use public_send * regenerate comparison PDFs, contents are identical, only binaries differ (possibly because of prawn-rails update to 1.6.0?) * write expected_file_path to filename when outputting non matching PDF * Fix linting, use block for travel_to * Switch to using transactions with rollback to clean up * Remove accidental duped tests on merge, move merged test into example group
1 parent ef9d24a commit 7450be0

File tree

10 files changed

+374
-132
lines changed

10 files changed

+374
-132
lines changed

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,9 @@ dump.rdb
5151
*.pdf
5252
/spec/example_failures.txt
5353

54+
# Track, don't ignore, RSpec comparison PDFs
55+
!spec/fixtures/files/*.pdf
56+
5457
# Ignore Docker stuff (see issues #503, #603)
5558
Dockerfile
5659
docker-compose.yml

Gemfile.lock

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -255,6 +255,7 @@ GEM
255255
concurrent-ruby (~> 1.1)
256256
webrick (~> 1.7)
257257
websocket-driver (~> 0.7)
258+
ffi (1.17.0)
258259
ffi (1.17.0-arm64-darwin)
259260
ffi (1.17.0-x86_64-darwin)
260261
ffi (1.17.0-x86_64-linux-gnu)
@@ -374,6 +375,7 @@ GEM
374375
method_source (1.1.0)
375376
mini_magick (4.13.2)
376377
mini_mime (1.1.5)
378+
mini_portile2 (2.8.8)
377379
minitest (5.25.4)
378380
monetize (1.12.0)
379381
money (~> 6.12)
@@ -404,6 +406,9 @@ GEM
404406
net-protocol
405407
newrelic_rpm (9.16.0)
406408
nio4r (2.7.4)
409+
nokogiri (1.18.1)
410+
mini_portile2 (~> 2.8.2)
411+
racc (~> 1.4)
407412
nokogiri (1.18.1-arm64-darwin)
408413
racc (~> 1.4)
409414
nokogiri (1.18.1-x86_64-darwin)
@@ -717,6 +722,7 @@ PLATFORMS
717722
arm64-darwin-21
718723
arm64-darwin-22
719724
arm64-darwin-23
725+
ruby
720726
x86_64-darwin-20
721727
x86_64-darwin-21
722728
x86_64-darwin-22

app/pdfs/distribution_pdf.rb

Lines changed: 39 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
require "prawn/table"
2+
13
# Configures a Prawn PDF template for generating Distribution manifests
24
class DistributionPdf
35
include Prawn::View
@@ -31,42 +33,53 @@ def compute_and_render
3133
text @organization.email, align: :right
3234
end
3335

34-
text "Issued to:", style: :bold
35-
font_size 12
36-
text @distribution.partner.name
37-
move_up 24
36+
profile = @distribution.partner.profile
3837

38+
font_size 12
3939
text "Partner Primary Contact:", style: :bold, align: :right
40+
text (profile.primary_contact_name.presence || " "), align: :right
41+
font_size 10
42+
text (profile.primary_contact_email.presence || " "), align: :right
43+
text (profile.primary_contact_phone.presence || " "), align: :right
44+
move_up 40
45+
46+
text "Issued to:", style: :bold
4047
font_size 12
41-
text @distribution.partner.profile.primary_contact_name, align: :right
48+
text @distribution.partner.name
4249
font_size 10
43-
text @distribution.partner.profile.primary_contact_email, align: :right
44-
text @distribution.partner.profile.primary_contact_phone, align: :right
4550
move_down 10
4651

47-
if %w(shipped delivered).include?(@distribution.delivery_method)
48-
move_up 10
52+
if (profile.address1.present? || profile.program_address1.present?) &&
53+
(@distribution.delivery? || @distribution.shipped?)
54+
if profile.program_address1.blank?
55+
address1 = profile.address1
56+
address2 = profile.address2
57+
city = profile.city
58+
state = profile.state
59+
zip_code = profile.zip_code
60+
else
61+
address1 = profile.program_address1
62+
address2 = profile.program_address2
63+
city = profile.program_city
64+
state = profile.program_state
65+
zip_code = profile.program_zip_code.to_s
66+
end
67+
4968
text "Delivery address:", style: :bold
5069
font_size 10
51-
text @distribution.partner.profile.address1
52-
text @distribution.partner.profile.address2
53-
text @distribution.partner.profile.city
54-
text @distribution.partner.profile.state
55-
text @distribution.partner.profile.zip_code
56-
move_up 40
57-
58-
text "Issued on:", style: :bold, align: :right
59-
font_size 12
60-
text @distribution.distributed_at, align: :right
61-
font_size 10
62-
move_down 30
63-
else
64-
text "Issued on:", style: :bold
65-
font_size 12
66-
text @distribution.distributed_at
67-
font_size 10
70+
text address1
71+
text address2
72+
text city
73+
text state
74+
text zip_code
6875
end
6976

77+
move_down 10
78+
text "Issued on:", style: :bold
79+
font_size 12
80+
text @distribution.distributed_at
81+
font_size 10
82+
7083
if @organization.ytd_on_distribution_printout
7184
move_up 22
7285
text "Items Received Year-to-Date:", style: :bold, align: :right
Lines changed: 164 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,164 @@
1+
require "active_support/testing/time_helpers"
2+
3+
module PDFComparisonTestFactory
4+
extend ActiveSupport::Testing::TimeHelpers
5+
6+
StorageCreation = Data.define(:organization, :storage_location, :items)
7+
FilePaths = Data.define(:expected_pickup_file_path, :expected_same_address_file_path, :expected_different_address_file_path, :expected_incomplete_address_file_path, :expected_no_contact_file_path)
8+
9+
def self.get_logo_file
10+
Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures/files/logo.jpg"), "image/jpeg")
11+
end
12+
13+
def self.create_organization_storage_items(logo = get_logo_file)
14+
org = Organization.create!(
15+
name: "Essentials Bank 1",
16+
short_name: "db",
17+
street: "1500 Remount Road",
18+
city: "Front Royal",
19+
state: "VA",
20+
zipcode: "22630",
21+
22+
logo: logo
23+
)
24+
25+
storage_location = StorageLocation.create!(
26+
name: "Smithsonian Conservation Center",
27+
address: "1500 Remount Road, Front Royal, VA 22630",
28+
organization: org
29+
)
30+
31+
base_item = BaseItem.find_or_create_by!(name: "10T Diapers", partner_key: "10t_diapers")
32+
33+
item1 = Item.create!(name: "Item 1", package_size: 50, value_in_cents: 100, organization: org, partner_key: base_item.partner_key)
34+
item2 = Item.create!(name: "Item 2", value_in_cents: 200, organization: org, partner_key: base_item.partner_key)
35+
item3 = Item.create!(name: "Item 3", value_in_cents: 300, organization: org, partner_key: base_item.partner_key)
36+
item4 = Item.create!(name: "Item 4", package_size: 25, value_in_cents: 400, organization: org, partner_key: base_item.partner_key)
37+
38+
StorageCreation.new(org, storage_location, [item1, item2, item3, item4])
39+
end
40+
41+
def self.create_partner(organization)
42+
Partner.create!(name: "Leslie Sue", organization: organization, email: "[email protected]")
43+
end
44+
45+
def self.get_file_paths
46+
expected_pickup_file_path = Rails.root.join("spec", "fixtures", "files", "distribution_pickup.pdf")
47+
expected_same_address_file_path = Rails.root.join("spec", "fixtures", "files", "distribution_same_address.pdf")
48+
expected_different_address_file_path = Rails.root.join("spec", "fixtures", "files", "distribution_program_address.pdf")
49+
expected_incomplete_address_file_path = Rails.root.join("spec", "fixtures", "files", "distribution_incomplete_address.pdf")
50+
expected_no_contact_file_path = Rails.root.join("spec", "fixtures", "files", "distribution_no_contact.pdf")
51+
FilePaths.new(expected_pickup_file_path, expected_same_address_file_path, expected_different_address_file_path, expected_incomplete_address_file_path, expected_no_contact_file_path)
52+
end
53+
54+
private_class_method def self.create_profile(partner:, program_address1:, program_address2:, program_city:, program_state:, program_zip:,
55+
address1: "Example Address 1", city: "Example City", state: "Example State", zip: "12345", primary_contact_name: "Jaqueline Kihn DDS", primary_contact_email: "[email protected]")
56+
Partners::Profile.create!(
57+
partner_id: partner.id,
58+
essentials_bank_id: partner.organization.id,
59+
primary_contact_name: primary_contact_name,
60+
primary_contact_email: primary_contact_email,
61+
address1: address1,
62+
address2: "",
63+
city: city,
64+
state: state,
65+
zip_code: zip,
66+
program_address1: program_address1,
67+
program_address2: program_address2,
68+
program_city: program_city,
69+
program_state: program_state,
70+
program_zip_code: program_zip
71+
)
72+
end
73+
74+
def self.create_profile_no_address(partner)
75+
create_profile(partner: partner, program_address1: "", program_address2: "", program_city: "", program_state: "", program_zip: "", address1: "", city: "", state: "", zip: "")
76+
end
77+
78+
def self.create_profile_without_program_address(partner)
79+
create_profile(partner: partner, program_address1: "", program_address2: "", program_city: "", program_state: "", program_zip: "")
80+
end
81+
82+
def self.create_profile_with_program_address(partner)
83+
create_profile(partner: partner, program_address1: "Example Program Address 1", program_address2: "", program_city: "Example Program City", program_state: "Example Program State", program_zip: 54321)
84+
end
85+
86+
def self.create_profile_with_incomplete_address(partner)
87+
create_profile(partner: partner, program_address1: "Example Program Address 1", program_address2: "", program_city: "", program_state: "", program_zip: "")
88+
end
89+
90+
def self.create_profile_no_contact_with_program_address(partner)
91+
create_profile(partner: partner, program_address1: "Example Program Address 1", program_address2: "", program_city: "Example Program City", program_state: "Example Program State", program_zip: 54321, primary_contact_name: "", primary_contact_email: "")
92+
end
93+
94+
def self.create_line_items_request(distribution, partner, storage_creation)
95+
LineItem.create!(itemizable: distribution, item: storage_creation.items[0], quantity: 50)
96+
LineItem.create!(itemizable: distribution, item: storage_creation.items[1], quantity: 100)
97+
storage_creation.organization.request_units.find_or_create_by!(name: "pack")
98+
ItemUnit.find_or_create_by!(item: storage_creation.items[3], name: "pack")
99+
req1 = Partners::ItemRequest.new(item: storage_creation.items[1], quantity: 30, name: storage_creation.items[1].name, partner_key: storage_creation.items[1].partner_key)
100+
req2 = Partners::ItemRequest.new(item: storage_creation.items[2], quantity: 50, name: storage_creation.items[2].name, partner_key: storage_creation.items[2].partner_key)
101+
req3 = Partners::ItemRequest.new(item: storage_creation.items[3], quantity: 120, name: storage_creation.items[3].name, partner_key: storage_creation.items[3].partner_key, request_unit: "pack")
102+
Request.create!(
103+
organization: storage_creation.organization,
104+
partner: partner,
105+
distribution: distribution,
106+
request_items: [
107+
{"item_id" => storage_creation.items[1].id, "quantity" => 30},
108+
{"item_id" => storage_creation.items[2].id, "quantity" => 50},
109+
{"item_id" => storage_creation.items[3].id, "quantity" => 120, "request_unit" => "pack"}
110+
],
111+
item_requests: [req1, req2, req3]
112+
)
113+
end
114+
115+
def self.create_dist(partner, storage_creation, delivery_method)
116+
Time.zone = "America/Los_Angeles"
117+
dist = Distribution.create!(partner: partner, delivery_method: delivery_method, issued_at: DateTime.new(2024, 7, 4, 0, 0, 0, "-07:00"), organization: storage_creation.organization, storage_location: storage_creation.storage_location)
118+
create_line_items_request(dist, partner, storage_creation)
119+
dist
120+
end
121+
122+
def self.render_pdf_at_year_end(organization, distribution)
123+
travel_to(Time.zone.local(2024, 12, 30, 0, 0, 0)) do
124+
return DistributionPdf.new(organization, distribution).compute_and_render
125+
end
126+
end
127+
128+
private_class_method def self.create_comparison_pdf(storage_creation, profile_create_method, expected_file_path, delivery_method)
129+
# Partner creation must be rolled back otherwise Items requested YTD will accumulate
130+
ActiveRecord::Base.transaction(requires_new: true) do
131+
partner = create_partner(storage_creation.organization)
132+
PDFComparisonTestFactory.public_send(profile_create_method, partner)
133+
dist = create_dist(partner, storage_creation, delivery_method)
134+
pdf_file = render_pdf_at_year_end(storage_creation.organization, dist)
135+
File.binwrite(expected_file_path, pdf_file)
136+
raise ActiveRecord::Rollback
137+
end
138+
end
139+
140+
# helper function that can be called from Rails console to generate comparison PDFs
141+
def self.create_comparison_pdfs
142+
file_paths = get_file_paths
143+
144+
# ActiveStorage throws FileNotFoundError in a transaction
145+
# unless logo is uploaded before transaction
146+
logo = ActiveStorage::Blob.create_and_upload!(
147+
io: get_logo_file,
148+
filename: "logo.jpg",
149+
content_type: "image/jpeg"
150+
)
151+
152+
ActiveRecord::Base.transaction do
153+
storage_creation = create_organization_storage_items(logo)
154+
155+
create_comparison_pdf(storage_creation, :create_profile_no_address, file_paths.expected_pickup_file_path, :pick_up)
156+
create_comparison_pdf(storage_creation, :create_profile_without_program_address, file_paths.expected_same_address_file_path, :shipped)
157+
create_comparison_pdf(storage_creation, :create_profile_with_program_address, file_paths.expected_different_address_file_path, :delivery)
158+
create_comparison_pdf(storage_creation, :create_profile_with_incomplete_address, file_paths.expected_incomplete_address_file_path, :delivery)
159+
create_comparison_pdf(storage_creation, :create_profile_no_contact_with_program_address, file_paths.expected_no_contact_file_path, :delivery)
160+
161+
raise ActiveRecord::Rollback
162+
end
163+
end
164+
end
41.2 KB
Binary file not shown.
41.1 KB
Binary file not shown.
41 KB
Binary file not shown.
41.6 KB
Binary file not shown.
41.5 KB
Binary file not shown.

0 commit comments

Comments
 (0)