From 704dd2c7bf70e8f1deb97c1b21c3a055d4226c28 Mon Sep 17 00:00:00 2001 From: Joe Paolicelli Date: Thu, 6 Mar 2025 20:29:37 +0000 Subject: [PATCH 1/7] allow printing only filtered picklists --- app/controllers/requests_controller.rb | 4 +++- app/views/requests/index.html.erb | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/app/controllers/requests_controller.rb b/app/controllers/requests_controller.rb index 82aa5567f7..a1c364bb68 100644 --- a/app/controllers/requests_controller.rb +++ b/app/controllers/requests_controller.rb @@ -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 + @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) @@ -65,6 +65,8 @@ def print_unfulfilled .includes(:item_requests, partner: [:profile]) .where(status: [:pending, :started]) .order(created_at: :desc) + .during(helpers.selected_range) + .class_filter(filter_params) respond_to do |format| format.any do diff --git a/app/views/requests/index.html.erb b/app/views/requests/index.html.erb index 388d22f581..eb335a364e 100644 --- a/app/views/requests/index.html.erb +++ b/app/views/requests/index.html.erb @@ -69,7 +69,7 @@
<% if @unfulfilled_requests_count > 0 %> <%= print_button_to( - print_unfulfilled_requests_path(format: :pdf), + print_unfulfilled_requests_path(format: :pdf, filters: filter_params.merge(date_range: date_range_params)), text: "Print Unfulfilled Picklists (#{@unfulfilled_requests_count})", size: "md") %> <% end %> From d4ed99965a3d9e7ce1da68465363af7320676534 Mon Sep 17 00:00:00 2001 From: Joe Paolicelli Date: Thu, 10 Apr 2025 14:33:04 +0000 Subject: [PATCH 2/7] test for correct unfulfilled picklists button count when filtered --- spec/requests/requests_requests_spec.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/spec/requests/requests_requests_spec.rb b/spec/requests/requests_requests_spec.rb index 6ff8095bfc..bba24d2c98 100644 --- a/spec/requests/requests_requests_spec.rb +++ b/spec/requests/requests_requests_spec.rb @@ -41,6 +41,23 @@ expect(response.body).to include('Print Unfulfilled Picklists (2)') end end + + context "when there is a filter applied" do + let(:request) { + create(:request, partner_user: ::User.partner_users.first) + create(:request, partner_user: ::User.partner_users.last) + } + + it "shows print unfulfilled picklists button with correct quantity when filtered" do + Request.delete_all + + params = { filters: { by_partner: ::User.partner_users.first} } + + get requests_path(request), params: params + + expect(response.body).to include('Print Unfulfilled Picklists (1)') + end + end end describe 'GET #show' do From 918e2c3f5496d98785cbf21d012f49de79ba72a5 Mon Sep 17 00:00:00 2001 From: Joe Paolicelli Date: Wed, 16 Apr 2025 20:33:41 +0000 Subject: [PATCH 3/7] fix issues with picklists filtering test --- spec/requests/requests_requests_spec.rb | 10 +++------- 1 file changed, 3 insertions(+), 7 deletions(-) diff --git a/spec/requests/requests_requests_spec.rb b/spec/requests/requests_requests_spec.rb index bba24d2c98..e7e07326e1 100644 --- a/spec/requests/requests_requests_spec.rb +++ b/spec/requests/requests_requests_spec.rb @@ -43,17 +43,13 @@ end context "when there is a filter applied" do - let(:request) { - create(:request, partner_user: ::User.partner_users.first) - create(:request, partner_user: ::User.partner_users.last) - } - it "shows print unfulfilled picklists button with correct quantity when filtered" do Request.delete_all - params = { filters: { by_partner: ::User.partner_users.first} } + create(:request, :pending) + create(:request, :started) - get requests_path(request), params: params + get requests_path({ filters: { by_status: :started} }) expect(response.body).to include('Print Unfulfilled Picklists (1)') end From 7c4cca6da5f527da917c119bbd63d373af90060f Mon Sep 17 00:00:00 2001 From: jonkaplan Date: Sat, 13 Sep 2025 14:49:19 -0400 Subject: [PATCH 4/7] Fix consistent naming --- app/pdfs/picklists_pdf.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/pdfs/picklists_pdf.rb b/app/pdfs/picklists_pdf.rb index 63e03f3eb8..8d91d69d2b 100644 --- a/app/pdfs/picklists_pdf.rb +++ b/app/pdfs/picklists_pdf.rb @@ -1,6 +1,6 @@ require "prawn/table" -# Configures a Prawn PDF template for generating Distribution manifests +# Configures a Prawn PDF template for generating Picklist pdfs class PicklistsPdf include Prawn::View include ItemsHelper From d62f9193ff94ace1c11b9aba78cd614c5a8e3057 Mon Sep 17 00:00:00 2001 From: jonkaplan Date: Sat, 13 Sep 2025 14:53:38 -0400 Subject: [PATCH 5/7] Add regression test for Picklist pdf generation - Generate the pdf and add to fixture - Rename content in PDFComparisonTestFactory to be more domain specific - Add compare_pdf to spec to compare the file to the --- .../pdf_comparison_test_factory.rb | 83 ++++++++++++++---- spec/fixtures/files/picklist.pdf | Bin 0 -> 33180 bytes spec/pdfs/distribution_pdf_spec.rb | 5 +- spec/pdfs/picklists_pdf_spec.rb | 40 +++++++++ 4 files changed, 111 insertions(+), 17 deletions(-) create mode 100644 spec/fixtures/files/picklist.pdf diff --git a/lib/test_helpers/pdf_comparison_test_factory.rb b/lib/test_helpers/pdf_comparison_test_factory.rb index 8a1b3796d5..77ba43a76b 100644 --- a/lib/test_helpers/pdf_comparison_test_factory.rb +++ b/lib/test_helpers/pdf_comparison_test_factory.rb @@ -4,7 +4,7 @@ module PDFComparisonTestFactory extend ActiveSupport::Testing::TimeHelpers StorageCreation = Data.define(:organization, :storage_location, :items) - 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) + 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, :expected_picklist_file_path) def self.get_logo_file Rack::Test::UploadedFile.new(Rails.root.join("spec/fixtures/files/logo.jpg"), "image/jpeg") @@ -41,17 +41,30 @@ def self.create_partner(organization) Partner.create!(name: "Leslie Sue", organization: organization, email: "leslie1@gmail.com") end + def self.create_partner_with_quota(organization) + Partner.create!(name: "Leslie Sue", organization: organization, email: "leslie1@gmail.com", quota: 100) + end + def self.get_file_paths expected_pickup_file_path = Rails.root.join("spec", "fixtures", "files", "distribution_pickup.pdf") expected_same_address_file_path = Rails.root.join("spec", "fixtures", "files", "distribution_same_address.pdf") expected_different_address_file_path = Rails.root.join("spec", "fixtures", "files", "distribution_program_address.pdf") expected_incomplete_address_file_path = Rails.root.join("spec", "fixtures", "files", "distribution_incomplete_address.pdf") expected_no_contact_file_path = Rails.root.join("spec", "fixtures", "files", "distribution_no_contact.pdf") - 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) + expected_picklist_file_path = Rails.root.join("spec", "fixtures", "files", "picklist.pdf") + + 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, + expected_picklist_file_path + ) end private_class_method def self.create_profile(partner:, program_address1:, program_address2:, program_city:, program_state:, program_zip:, - address1: "Example Address 1", city: "Example City", state: "Example State", zip: "12345", primary_contact_name: "Jaqueline Kihn DDS", primary_contact_email: "van@durgan.example") + address1: "Example Address 1", city: "Example City", state: "Example State", zip: "12345", primary_contact_name: "Jaqueline Kihn DDS", primary_contact_email: "van@durgan.example", pick_up_name: nil, pick_up_email: nil, pick_up_phone: nil) Partners::Profile.create!( partner_id: partner.id, essentials_bank_id: partner.organization.id, @@ -66,7 +79,10 @@ def self.get_file_paths program_address2: program_address2, program_city: program_city, program_state: program_state, - program_zip_code: program_zip + program_zip_code: program_zip, + pick_up_name: pick_up_name, + pick_up_email: pick_up_email, + pick_up_phone: pick_up_phone ) end @@ -90,9 +106,26 @@ def self.create_profile_no_contact_with_program_address(partner) 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: "") end - def self.create_line_items_request(distribution, partner, storage_creation) + def self.create_profile_with_pickup_person(partner) + create_profile( + partner: partner, + pick_up_name: "Pickup Person", + pick_up_email: "pickup@example.com", + pick_up_phone: "1234567890", + program_address1: "Example Program Address 1", + program_address2: "", + program_city: "Example Program City", + program_state: "Example Program State", + program_zip: 54321 + ) + end + + def self.create_line_items_for_distribution(distribution, storage_creation) LineItem.create!(itemizable: distribution, item: storage_creation.items[0], quantity: 50) LineItem.create!(itemizable: distribution, item: storage_creation.items[1], quantity: 100) + end + + def self.create_line_items_request(partner:, storage_creation:, distribution: nil) storage_creation.organization.request_units.find_or_create_by!(name: "pack") ItemUnit.find_or_create_by!(item: storage_creation.items[3], name: "pack") 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) @@ -107,30 +140,48 @@ def self.create_line_items_request(distribution, partner, storage_creation) {"item_id" => storage_creation.items[2].id, "quantity" => 50}, {"item_id" => storage_creation.items[3].id, "quantity" => 120, "request_unit" => "pack"} ], - item_requests: [req1, req2, req3] + item_requests: [req1, req2, req3], + created_at: Time.zone.local(2024, 12, 30, 0, 0, 0) ) end def self.create_dist(partner, storage_creation, delivery_method) Time.zone = "America/Los_Angeles" dist = Distribution.create!(id: 123, 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) - create_line_items_request(dist, partner, storage_creation) + create_line_items_for_distribution(dist, storage_creation) + create_line_items_request(distribution: dist, partner: partner, storage_creation: storage_creation) dist end - def self.render_pdf_at_year_end(organization, distribution) + def self.render_distribution_pdf_at_year_end(organization, distribution) travel_to(Time.zone.local(2024, 12, 30, 0, 0, 0)) do return DistributionPdf.new(organization, distribution).compute_and_render end end - private_class_method def self.create_comparison_pdf(storage_creation, profile_create_method, expected_file_path, delivery_method) + def self.render_picklist_pdf(organization, requests) + PicklistsPdf.new(organization, requests).compute_and_render + end + + private_class_method def self.create_distribution_comparison_pdf(storage_creation, profile_create_method, expected_file_path, delivery_method) # Partner creation must be rolled back otherwise Items requested YTD will accumulate ActiveRecord::Base.transaction(requires_new: true) do partner = create_partner(storage_creation.organization) PDFComparisonTestFactory.public_send(profile_create_method, partner) dist = create_dist(partner, storage_creation, delivery_method) - pdf_file = render_pdf_at_year_end(storage_creation.organization, dist) + pdf_file = render_distribution_pdf_at_year_end(storage_creation.organization, dist) + File.binwrite(expected_file_path, pdf_file) + raise ActiveRecord::Rollback + end + end + + private_class_method def self.create_picklist_comparison_pdf(storage_creation, partner_create_method, profile_create_method, expected_file_path) + # Partner creation must be rolled back otherwise Items requested YTD will accumulate + ActiveRecord::Base.transaction(requires_new: true) do + partner = PDFComparisonTestFactory.public_send(partner_create_method, storage_creation.organization) + PDFComparisonTestFactory.public_send(profile_create_method, partner) + request = create_line_items_request(partner: partner, storage_creation: storage_creation) + pdf_file = render_picklist_pdf(storage_creation.organization, [request]) File.binwrite(expected_file_path, pdf_file) raise ActiveRecord::Rollback end @@ -151,11 +202,13 @@ def self.create_comparison_pdfs ActiveRecord::Base.transaction do storage_creation = create_organization_storage_items(logo) - create_comparison_pdf(storage_creation, :create_profile_no_address, file_paths.expected_pickup_file_path, :pick_up) - create_comparison_pdf(storage_creation, :create_profile_without_program_address, file_paths.expected_same_address_file_path, :shipped) - create_comparison_pdf(storage_creation, :create_profile_with_program_address, file_paths.expected_different_address_file_path, :delivery) - create_comparison_pdf(storage_creation, :create_profile_with_incomplete_address, file_paths.expected_incomplete_address_file_path, :delivery) - create_comparison_pdf(storage_creation, :create_profile_no_contact_with_program_address, file_paths.expected_no_contact_file_path, :delivery) + create_distribution_comparison_pdf(storage_creation, :create_profile_no_address, file_paths.expected_pickup_file_path, :pick_up) + create_distribution_comparison_pdf(storage_creation, :create_profile_without_program_address, file_paths.expected_same_address_file_path, :shipped) + create_distribution_comparison_pdf(storage_creation, :create_profile_with_program_address, file_paths.expected_different_address_file_path, :delivery) + create_distribution_comparison_pdf(storage_creation, :create_profile_with_incomplete_address, file_paths.expected_incomplete_address_file_path, :delivery) + create_distribution_comparison_pdf(storage_creation, :create_profile_no_contact_with_program_address, file_paths.expected_no_contact_file_path, :delivery) + + create_picklist_comparison_pdf(storage_creation, :create_partner_with_quota, :create_profile_with_pickup_person, file_paths.expected_picklist_file_path) raise ActiveRecord::Rollback end diff --git a/spec/fixtures/files/picklist.pdf b/spec/fixtures/files/picklist.pdf new file mode 100644 index 0000000000000000000000000000000000000000..216a6f10c6985dcdab807ba8dab39fa44d06d8be GIT binary patch literal 33180 zcmeFZbzD`;7dL!J=@O*zAl;ng(4EpHh@f;xHyEUVNJ)nvh@g~+2uMpKD2;TgbV{g% zy!&8W^m^~_exB#~^WB`o-ZOj6+H2O#S~K(A>kONwoB|(&A5Oq_1RMkquprpU%#J`z z3@jk)YH8~3 zH?R=`UC#JKLHLP+s+Nw{?lxe!5DJB+aB~Oh-W{4fMq*MdMm0Zj%G1nXK6h`|v; zFd-NcfwUBYBZZL=q`43bDhP)HSC&XKs2~g?0oJu6kk=(RW(E^N@B``;LVhY1TJ!D4U-AQ}|-L&9J%K?!59uF6jt;X*KesIVXuECfZOYyMl6 zjl_<{f&jHOhe6L;)gN2niz1kqD?I5(Wrn@hkgpsz$jItxL7 z8l%+;2Ob2}3HeJ+g@j?Ie`1J$@IzoHWQBff9zYDBdCUP}5r7T=^`pf-(I9Xrf*%D1 zBau*kKMGN0}2*UUQMV-*;u{|1z0bTDWlmElc zi4X=105H+tl#4b^C}1vdz(9f4hM{1>fa#oUaD*V7Ul;{O3IaX`^}YN_t^KKU1KsC0 z9Raq@5408-Aq?k-{Zhs6eHv{SXqRJwL^A|>CEAA3HV%k~{=$C|3J!w;=8O_VfDwT6 z5&Uk?e~D!z1{XdyH4C)oI(9l}=E6WvIyOIqFyJ_Z`B8w+5eB*u0^RkY7C;Frs0AG8 z_~^2~n)o%>}xOXAIf&I6J_?^WG<$ve% zKb8MGixUd}A~PYNe9yBd&>=7~2Bysxz#_owZ{?5YzUXN& zFp~hrqrdy*--nG~=WRdTE?Ux)9uFSorr^0`qmC=KrCV z5TTzw_8+QxJOlyLCtwf)#!4YzMEpL5{F7XNuZRP|3G@ALl{>-YXXQ@h0i(iytQ-{n z)8qeB<$&Q1T`i~}6a~zkkid)^A|!Y`g-6e%AiyjGJ(>UaO8>or4k!Eef2;HfCO<2E zBJaOf8kn2?7(@Q4(q;%0!s1`s@q}sq?4$^xV~hB2)j7f7XLU~G{rBp?AwP%2KUW8s z!=YE7=;L_Se_~;XZd_nKho0L(t-dc4?w{Ap^%enLCClD)%U3*dhYpC zng7J;gq{5ZyW_>^58L_^n?L2D9o2~j{|D9xU@;C%Z~toYKe75#-d}zYn9RWuCzl0( z)4^ZMwSVGtLI?lA4!!*US5^KK>k~To2iE@&bN~_LM+ys{n9=`D2Y;L0Mhhx`2*+; zfQ-slE0H6RI?K^z+9Z^FAUw;S4&~TcoB><3p z!_x%REG=wJWt_ahM!-@7jD!GSjxf|1K+#;Cod1w5V{d6`@rQg}S6hcaWJ|lc{~=e_ z$ zN7EoO6$NDl5C#SY=rZsJIvNAXf?PH9E%X&#EsQM!d|bl{j;adM(+iIFj=IsG-AaXoV4aO7}1~~|m90Q9S zlltp6-W7k!hg?KKPyfpLenphbEUls`B}2Ag1LXKql2Hc3@i3%8AWVM{2n$3Dq6hh7 zv7WjDB*Jh+?y02-r-;xAlVRnxV`aU5$Ta{0;VKbPlI1plu$l2ZIYA&24w-wA$w>QI zVnz~q5eAYFV#?n31z9wQ5D*ju*8~V-(~tna+daL_BSDtJX@VZYF}-nF?L8t4{&05? z2$0qvOCE%YNfxRj1p=W`an&EOSSewYf^)-N;oA42atQz`j6f`KCS|5S8Ch=+2n3qL z$X0~L^k##wt}p<~8-}7u{E09rGkd_;U{QeLm&0PthL*Wh4&+bfzam5T?G|&4j#LPV ze>+^3JtB>n7-Z2vfRPCTN^`~1i})YYyn5Xmrzo2elvSD%mnqDsZmpA|?vI75B$Ech z1j&%SQqlRQVmw#4MQ_inHe-~Pbm=MClxz-df1()wcBD3$KV^uLb~^F5M~1Qh_wSZ+ z=wM?5R7#$J{w#2C{XzaAN;+AY$23?RFa;GIHs%LdS?!D@);f2yGeKCa%otD68h3|( zsm-6ttsCzDB6|?DkZVCy6y3E;g5xv743)#30 zbGC070~x{v?U`7zUl77yL?`18mdH~Tm}qWm2#TqrR#!aVMUA`kM@bBKgKOb7T5rA@ zH0z?&HwXK}`!9MD2=|+l(X5zXpczMPZ`!ul776y2CNF>F%xsC0?m`7b-Lef8%4O(I zE~MQt$0g~T)sm;;JQo<|F9W8h)RLwo3#(!oWhl;l7i~TGVzvK)`LIJ<=gyUF^>bMl z3)C1I5Lzt^(X6u~)Sr3u?#YHNI6rX~;UJgAMn!+YC89;TP;wW{TB+%zbMH5%mV|2 zD~Wl`$LOG0 z)$3<5y;#>I#4}0FF0dodwB9OKY9rnll)xMpPa{!_40)nm%fsM39lLSI`5ta-lLz0z zx_@Y6js&?()=IA<#Y3UHJZ3UO>rQdn%|-Ffc6*rd_G~7>> zKa%zV$CfQ7mEnfgo^(hd-OK5drS?1c^|oV6rdxe?WEmOUO_ zQlK~>diDk}m4d0ez(TQu(nD=7s%6O^Lmc3^K_IeNW!6|_Yzp-M-#-+f-*W((7%6N_ zX>g1*Ys|0oF-okM_}CyN>|glc14)1w8}nCUe|*+J3~UOFll(z}fEYk=3~bN|Vt*-Y zGISE)1AKnaE3pCs{KgX?z*J%-12`zNf|OWerGOy@>fEOaq<%=4F=(3`vZa<=li3j1!F3MfX9LG zz-aM+4?cE`6xbi&4*~;E2V^)V22tRlDFcBIz#Ib*5}h9d0>@y0{qb01@xUNJw(rUW zQ{aPRmC)k(D}gYj0GW@O0NMtw@zE8hz`&0AmU;5OH2Bx%K$rR#zl8Q&WdGB1eWTz8 zJQpx#U}Iq6fPlFbaA1G{w?rnbW$FSJ)ZCB?xSLhd&^xpF(?OB#-QluQ*_v3+s@sK6OWJMV=?=0*IuR~aD=oFTtM;L|nZq=R zKfAKmYu>wmJ=8I0bu@QzsFg$l;`ObD~#4Gu(jhWPdchyAYR0u&uIHB*GxC^(u zw`cS0jhyZ{udfL4-exP!u>?UVF-+=WjE^?cmRO z+oISL2V-pKHBDN$b$r2iams^BqO+dyAI8Usb)P-i#CAU{-@0T;?X31WFi(cKMv7`G z;avxtLThV^qNqethR<{@!ian}JO_z^NOzQzE?A zh2D;{LX+gF3jAu}*Ek-1HCRih&oi5inAM?$g$?Eav&g%bqk0Xlz40l@1u9^<(YH^ zT~|BXQWwoP_313t81Wzy@>A|zg}j*F)0x}Tnk_B06P+@P#hLaEr(p%-HDY6e*~Lo6 zdhe5x!-JwF38OxHF>YKno?j4|wo`PS4F~`!t?s8uRvfehm9vTGh^M?YsAiw)ieuy( zPYLDyTG}EKO}ffcr01d^QG$OVZb2kfa3*9n*bBQ;^;5HG5{U^z%i~g)Lc`0_uf*Mk z4w?#sw~7h#_4hlC3crNcN+$1!){%|smX}fHT&Q%rde=rIZb;nk_0ACpqU+gx$x8Hr zmWWJm+ZJKr!GxNrUZmUuR=NPq@oV^YQv$pmt)azbyUF3oO62p;SIEhSFv`uROR1U| zU9y%O7`olbhZfwZct#Rdnu?t9&956HT&E7@Wv3!sRd6nxG7=I~?ba=}A`Y6waVjLZ zCEV7evQDNZ`yxIFw`BW_$c&yrWbNxuVOWp0MNvY+QJnFW98F?@B4qPMb%3N!cP{Xi~T$HJg*` z*WKG7Yad7K#i<^}lJ7OGd*;RCO~hU*#}f~}{2H~Jb6ucq2!@Efw8%joKbOP#D@+ta zvrCucR~gLcmG#6O^=s~LZ5yFzK!S>%L#yPB5uH4v=Q1nbh2_aEn?VN{j2wnM$q9%zrrYJNa0r`01Y_+>w8l-%VY3C01ky& zK~Ovz&X$fkrjBkW5CUCS4@-1x(l?kw&eF}?)z%puHw6Tsd;=sK!t^ViT3&nw)ytX{@0i8C+_~?<=HWG305Hx1l#?1M|S)^`8YxhfsUg( zjvWJD=j(iX(RO^pf#BQwf#U@F#nUlJ2|>Sz1!5Znf%uExus{H}H%D6i5-H)Ih2JI#;n53sM}#{hAK|0slqgChV@ z0#ZsyoP^-K@2!qE;QI?w;xusXUM?l}rce$O)oGCA6QgQFPp6~A^ooD*)g25FEQ~ScN-HM5*(p}w3hXHrID9-i0~l4(Oo7DTg!-QG@{FVKHIVinn)YP4 z=ePLEzZ>iSgQitKFx?+*3q=Aw_=wbidxT&P7dP9tD z8kg~yLu#IeXm&(USZ`<;bl!t8UbTNmr*an&0?E&{HGCB5_PT8OY>M9L#xSEZ^9JTA zkk^k#cv(`(AEbEs$XON1>vMMKP-2j;b**RY&tF2-yRDa6ItlFGh~1wQ zp1QWS{;7K2Yu#r)W1ofvFA)6u=Ui4L&m_iy#$8IYm0g3HY$*r|Hp?hCKGJk;=0o?0 z83o>91tUd?tg;G-QyE`My9O%^##TxzdJtu7RDHX|uMNh4I9gY}sNbRUj&(6kW0sbX zfY=Kk#Kwh@$;t+be~5kMQtX-4D56Vu?2WDrt%oFpJuTfAs3{-i-E1UpwQ{syWT?4W zB{DJKYU031v+(%xbV+h~?w8q@SBBE``Y%PEJz%e>-Ro&-u(>)R<|LI9$%r-7C8QT@ z>GG;e$^GFc_lc&-@-VU9;68JV3twsi4rBc)lEb~krKzf@SwZ)==G6(jFASwGGOmef z-<>PyB#xj>iNTIbeutI3ow!xJboUspyCi*e)r4%fKFXT7ADgSB<+3HOhcxm9> zOI)SgUd1YZbz&}P%P`%jpkj<;HDRy8`+WE_>&3^_*}X1L-3{!H^4Sujl+57Ki)@Ql$#U!6}DiqUa?++yv+X_}ce?6*2bU3_sN!rDdr~N}N$}5Ii zw1DaIwgt<|nu?`}p{>j+gpwpQ&D)3NC<7{iqqLw+$O@t@Bk%PZDwnKyz>b;IFycEA z$NEn7iD6feqa-6(xoqw+xnsb`r!GAa(EUKuua__?$!cg^wKIhjplsGfZY_D9n;#)r z8r6-RW>1M4z8FKQpI|(}=*4R3X;_vsFDpa__*z7U9Pqr)1rZvnP*5acn>7skRf{ zM>5#oM+SxZZ}-rcJ8fL6da{33#^Zui0@Mt1i-yc&LFrNnt?5elV8k1x+N}OT<*Jru zTHN;`9%0k$Q;jx8WiS|#_ki~jS9jfWiB=OXB~w~O4Mgo>mzqzKwu0&vCP`|5Lmx7} zQKnR5VO!w6UdwQBDlFV8UMxN>B^CM=kL|PqCHZ9N9s181K3l^@r{DPPwU6Y)lb`kq zzI^=jnF$Zu+>dpM+pmnP&riM8tAGoM8$=CL%1MeX56u~*-mA|u_sh_4Rc%)H@45TJ z4Aw|9tVvw=L?TZ)^~OiT7g3V?)7WiCksJ|pQ?;E<)^kyH)N97Oz7@2Nz1P;>g~=$W zsj3`5X){VpWr9Pw0_(Z7vh8f#%vk-G4)ITZL8Wgh#8odq2dW+G>Q*L&C9emyrR$bn zWVxz(To6JVQ2ila|C6%6aOI1Bh=mVH^rmRHdD9N|l@NEyy8V@Pro*MsgKBo#q#LT( z?1_Mofpp4&ZB?BYC+4j^3h#IKL)SOzX`?@`e)1!p{942oL8Y#F{`2~Lo!@QOF>~rz zgVDeWEklQMo|wT=%~WaRU07lj6=TnPe3ME1<<^60{f}x>yQh$$ z2A;hEE=#8^56IUm^gr~CTxwlwwJgIwOPQ^V))FS2%Z(@R$MTqq);?X^;-9LszL0OW z&jMA|uo_9AJ0d(_aCa=KP8ORYTDwsv?r>iDDM?=>U>kH-a$UL^Zvj&Dv=gcaZ&aC9 zn0yk&H<05qaXj;AKz|R;bc&b1h(f2oMs^v~d+gnugkFTsM+|p#6Gy)gcb>8H8(%6u_N>>U*Y;Z!mY?D$>bCL+T`KBOh(=#(p}5f z+{C5y;a=>hYuvKDJMRO^moInKM{CG^DeRoz@T=499Bx{zzPQ(@bC#Q^lskFq<|Un$ zVc4zes2%I~2jH}deB}Lfa=>X})vR<1Pm+mLbhDhoW5l0MaBaO=*D%#y)yHVP52H?! z#=P0LUX1k=bD6~IsadrarS&BULzL6U!a&j0nbl^T`Dwn8L*J_f*B_e$gaBayNlpGdhceN36G~i%#fF)q#~h?Mo7q!g4yWLgpyv>gc%- zQaSsQSU0wd+&nj(qCNJ?c0axwTIuWQPHb55ev(`AO5lv>UH&UD0xV!y|0 zpq*|YQU*(dnNQ(`pCgcYA$?a?H$IX~iz5s#oPg;LIe9P6y5*tnB}y$XDr4sK^`eXv zGR;q)#HP;HXSjO}8tJVYukXLLAXC6w?A4ab&VA7FQF{S{)q}L=i%WuSiWlpIAxY*~ z=4E5p&=kCNY=cHd7jz{z6{jfhrhT(xF#J>FXLDGZ)tlOWzn(C%LlK#9tkMHMwf1ni zmlag4#HDk*sVO_o6WMsSS!Z{gZ=~1n4psSqB;2Xin0G)uwe1H4FG@2dpWbv_cDPJ@ zC;8MNY1Yxqy;i!TfcqIY$=;F%eyyqeI+nV@rGoR$WPxz&z-?KdJ=a0XMw4G=i)*%F92tg%LkMmhCNX$T)RUYNK-%IM4S0gWZVEvG_pKPhC%Yd)`NZ3l% zsmx+V-MOSk?;HV+fI(aFDh4Ch+$a4{YcuNY$$OV&E!xQjqU>#;W~Mn9Ia#((bwh9B z7@N!W3(OD;HkOVV3cWNBa@vEZ-u8RXrSirF-l<9HhGi8G$$v)X6XSozr6d+-qO&-s zM#d(B%bb)#O=_7{L?Ph)XK2Txdp10!v~Pm<@y5%XH9|%4*FHE`+}hi5?$)=+lrO}1 zo)l6w0(+ch8!&deD$msUPVL}poCS`TH|W;<8jSB)!HNSE#X>};J(~^|Uk`Cg-Iw9p za5PQKJhKE2x3bE_HlWW$ZPUCp!+#vX&AuN*gZJaY8Q;ouaU@^OZ#Fix><9%2-%`bY?z{VF1+#jaFV5>sA*`8`+` zOA{_;ZiZuh57k2zO@YfsqOPK@taiS;-QC*qsNVMIs8TFP=h5dHy*O?TbsEXF+!Awc zg`}i}L`rI^GZ7!(M>9aK5BJoJ-(w!3U9`>PdKE-5YmC)`vbGOyvFTZUZkG^y!I@W9 zhlY(1uO)nc{dx|>17!hJW^3sFy=Ph}yvrY% zTboBS5ZQVSqa|yST3-~C?do+5Sr2ihCBoY#(2{dkG6V}$RBdtXTQo$!5FP6~2>qgHSh+x%RMM5HM@D}(*@0nG<;(VX;<$Wclv!2Nq7<<@T zlcI2`9&`}GYb(^wIPqb6S@aZHm{4l47?SBrhO3mAHBve`hTWY8Nw>WScamc9Wt4_b`(sSD5SnyI z?_YU<0g}OoHwVsa5OH1-8^_grLBtkiBzS4V>=Z#aE)w&$jEk|B4b$S~(0H~rU4t{N zt#|pSxp>`BH@c=1VpSWSlAR;s@qRGyY=bPAi)Ok(lCaZ)tk)9T#kYay?QMw?6Fl$M zYq#y?#GfU~cEorvJsW(u?0XoGV<)3dL*f+%#bQ&A`r1Jp-*H&yI6~a?Va7BootlSx z4Br(JOctA2Am$%Nc+EAEMEFb$F`vu|717h255yCbA2ws&T#}3jlXqSnYf)Ku_CYqs zu|>56C*tc(hb!Z7y&@YWsFJI3l}S2PTzGF*oG9;-f$*#9`;EG$9s$Ky!4W>BgfVan zPWeo1B@s+uq&}V1)VakNDvrmOph!y#{&bbZl(p%zAW?RztS*@q9z}Q(z7P1rTL!a0 zmW&O1+$-0~%5_6tU7$Y4z4h3DbY06A9-rvyZi+cd8bb|^oOpKiAj@O&^Kl=Mzrh~;x<6P;0F`5cg=z zx)s?I8;m9Kq=72cGX<30$edEu#}v-0diJ!&RP;eRkS~z#<{opNS@SmC7v?-Fmr6Db zL%5T!UD113DWu=9-RkP@w6fspWiK3&GLg%Dre80DUccW!W4^z;$K6)hVx@x3z`ae6 z*jY5+-tlSz`Emh~}1&^3uX-i%Q|1bb+5jUlTHt7LAPU~4yN)SUs;XT&#vyZw&A%L{Wir(Lvq)SH6!2~u^iw6!zvAY*?x zs6O(Ebyw5P*GV^9+g>`Ya0%l&r7@)Vrrl(gCa2b- zIN_%4leU!pE30oO1va?11s*DpGa=%K>55{R=5_izQkgzuF$-%+?nHfo!YV0 zYu0K}-8Rl$b&rT++H}K3d0vWS1A7VEM)MY~FOl>3yON7doKysb@13zbx`jUEqeD&hV+`DQlwaVqSdwbqR zpNh_}U*GC6T;$wo^1a^uwTPa3OrQ9ykYD%y{)O|BH9U;-uS~5(76vgGSTkk1rA|S4 zD8tHIY%8U2KcBFw(EGgNnb1k4Gvmgk+e&bMDK$~cL@xDUsg1VWnV^7p*z}&uCjJOA z%}9{$o>{r5o6_0p;LCe`UBm(Ei|c*eMQyp0AEgm>#;J9t{c%o?b){8qYY(}m_(k3B zrqo&a5jlxIUEH2G``Bbliy|+0EBRvfZHpnBTF&Wqa&oqSU8_=oAaPZj7aXn4+%xwI zGRfccSrJ;Ptraj=G_{2mZ@>D^MqlY??coSD6UTl1EbIZ5m|Em!FT*(` zQ30_hhNqQi5sjiwfALM}`zwnU*%f?8tCY;MUI< zDxbS|C3CV8P}Mcd5%Sp-x#?M%D&3K^slwqJzdPF_LKj@D_8q49ug^A|5@GV)383;`i1i?3GOTxIxi))i z!fiL{Laq0HL$o?m<+Him`BWyKMU^%Op0~Wak_Jm7(%n@>o};xmO=FJ+`B=--iTmy* zn=10dJ5J9Fw&v+(!k;<>^5gPe?Oo)&!-lC^kV$ENV_?V8y5oFR*HfD7#N#9~*@o`} zDWvZ`PYIOqSCtF*ZXMK_x(L5HM=Du>5I&W5`S97NiBnw@?1pb8pPOzCI85G7TZ}DW zOx)3WsyvY(bhI@g0I65o!+vyjw01@yJ1G&qa$gyBdBCWV(Tp_HK0Z2J+{J8(Y(nr> zOMvDB`J@hC1^M@LP!{iOItk^H6!+^CDsJ=DYCg`7p&u+e(h#q!ss$#=GEO%Zp6-6$ zuYbp$&@PjvO;dliQdnP|l8iMlpi8wi*mT@Jabh>-Q}5m@!hwU#Q&x^6*EVf>>t@?O z=%lSD)vb$gFkYvlo__x6>->U7AC~qz!y4Rd(v27M#g}fb#w%aYFSCd3^j6*JT;doP zduK7dI;oTfA1*%%y3uXgG}qKO*`1@)m-VWXV`j&j-D)Gzty{TUrJC(ts9|Tow2)t6 zfH)@qBUzse8kI+Lkn^svH=HyYyfC7- zF-*M_Msrp!8#1ueDq0ej5?Qcmu$Xk7V(>N#i5LTFkG^ZcclQhRNRY-^!*U_*1dbaY z_a3?T9#B%4vi<$LwfYw#2cn~eI<=)8V0W%{8)H1 zYv%bQ0{21IRyS@cE3#TmShWH5S(Lym8Pjo*DEriVacU+aNwqNf_W7es|bghRQbYpZ3e}P`+01!A2Q$T=(}|XDMBM0 zV$ozr_$WoidbCj zlbTqd>fEa<9S0O#>2KH6AAFpu8)w?)%r$vifp6*aK$o|35xkSk$8GgoBWIS9px@?x z;GAQR_hi`yenQkGF+7y+yD-&S_lqL+#iys#iC46o_U~7itldoMJviLYFn{^ny#Gn$ zvP|9Udzc+`7V?%%NoUMevfl_Zg!e~rnKw9ZXsZ~t|MLcVYR-VS|I zpoH?xEO0#$b|lK|HC^zz*Szn-hnjnkY1!k}<~GiXqvPHE#R3?#T6omZ7!iuuAeWIR zV2q@Ae9yGVtcQMI!N$h04^BJj`wE3q+q#V9W?JD+BAUZ8?sV19jpE68ANh4eL@4`_#GqH@ zV!SSl2ls+jD$9;$!knbk)bCs5zRbG<=H7WR>+PYcYVq*)Bje3v?Y8(m&N4c~qCE;= zJ(}O#Hv01R$D?Z)^F;GXK|G55BQgmu1{W|zEqUG(L}e_z=a8uTyxA20PFO}Qew;9YU|>l-%uUosfIBLaA;_IH)N*sLXPJa6lJTUn%#l{^Nu6xm<|b=wB@ zSJ0}L%V(exCY}skePBz>hbmJtN6_jiXyOUJ9*N0m4ka3@j1fI2SwA&NaK@+si=NdR z2OsCmt!rW8my%%w_ce@;n$^~^EMnCjxn1B7^_Eq6K z&u5=hO%z<--&=k+uYUhV^l%;yye2gTm&&ZB?LjDG?n9Q9duLEE@M&kO>aRTsce`4p zcl?z+vcjUNaad+R;RJSbY@DVElF`>tE$OAaI-^IQvkQ=~ORrGBy#iB@DlIOPKOZ-> z=R7{gIGoevOEcyOgRC_q|Y{E_X`x?MxB4( z_{gYQlo@0CfqiFTk3x0@k5S#;^#0kd45P<-f{dS(NxdA$dMj<)cHaO^S{SPF(fnQO zY|@dN%gl^dGhtgjYG-nUEQ!4*lC}%pBvwMsU7ea6$?fak^n^d)9e5UXr;u~PBYf$8 zg@W%`q2uH`P0pIy=03Xmw2B^wNN_r+PtRB@h{C7%ndBclu6cm$|g}N zqY*a&zQ6S(0aMO4SIlQ$rlzYa0H%*(HB299>bn&+bh+(o+E{GraDK~WQ^HmLJT!zWB=fbSn(^q(HAhaB~tbV?57TetguHrR6`90me`BT)1 zp zl-?*Gm$vrl!|9CiUKg;}MrM}mDdH~64reoy-I($5dkmDZJaq4&naoU~(vP(B2F2Q* zikZWK;8z;&4E@JN&n1$B`uyc?_C)wF_in^6Xcva^(BHw-Q297Z>!c+AInL4cRqRBy z54X7-u?c6+c46)d;Xc(a*Bx*5^D)Q_qUW|2Tb#_gg1omZY#Uo>$b$t*-QJIk<8SDa zCwR*h^x9ya4pWI!B6TNzYFL!3q(G0~%a824@>X_L)_#V>f!4Vp3|_Gx(pYz2>xp5T zx;@Kq)0x>zYJ{HSQ@zhOvLYEj34eahVQnrJ)$MIMLl~O3c5Q9M@5Max$D5m~-H=@S z&d*!|s!wiw5H~A}X#>@tiJ!SGb!HZNCg)AQ79G*FAa0P#ku`~1ZT;~6waz+4eEK!n zt)o5m%|z<14tI{v77rRwLRdC-n%Kf>$#?YwD7Ef#(>GIKIlSNpeUeo3kJB7seVTQ3 zp9ncbw0JoQ>F0Cm?In@uoeP&ow?4nS_UY?~O_C0_L)O}0lt#{>*xS(rOVT+wqX4&DrSh(#J zi&_*gLCx&#eg0CRepALB=iXd<6@itf?DNQ|@Nl>sK^JGJ@ma>x=KZN}2*T=c`M5W2 zkBpWG=AOJ}7QS)2V=}45b-cO3RW7P3&zxt!XHreHobSt6&e^V;bxO1@8le}*o;e@w zKiP7+;j%QKSg?7eFeD#Y=GN&q*=lWfJ_B!$JRNkMZu+5VFLwerl_flH=i=XNi) zn09ooJM}xC!{dn_HI^7Ki7z|IiPE#W*(_m|3b^AYWARhT&rVmAG+d-ue_Yu@j` zb!gJm&#+{=Jbu)pxTsNiOeDO5RHdl!qbg&{1;it>mZ)d@;R?|zOetx`RT$n8tF=n2 zrsrQ?%?uYZ;8iK>lY^0aCX8I+-&qvHt;HnD!K39~dhdZ_-XjrHoYlwt3fktthA3`P zNQvc8K+)lzFJ2or)Q(hCDOPvt=U(fh+eF#D`8@0Nu=<)~af+Jfw&Sp|&&Q*S&&2Ky z7xvibS|tqT_SzBG$P297rHNcBiw&Q+aah@5gMXj5#rcC>v-uSr$-WY9nZ=t=cHX>G zHPm2Y4S>bZcVxXe&0c>!RfJZ8R#PhFphEhV9vxpnvu~x8XJ|K$`U-OFWgk0C?ww-J z(jj%*vbV?B*nW2Ik}F<}MPJn$!-LFZ9mO1&Mz`a8ACHi{{g=y? zXEMkyk8q^tuF-n#r5O3HnRPs*v%M}q={BNk)V*}W0Ska;Fm+z)VYat-LR)yPLrPHbW6x6WuMlrW|~41*M-M#UNZPSe)9k?pKU-t zb7EjgGfgpQ;kl+!r0ZgutOIeXLNRGsSp5!jQKiEgoeuMfHOM18^jt zonx}z6!!`hv>(=|Gky0iZ#)v&m8!!*32ftpK<*pS)6M5ZUCmG90LHR1)xxwvWZRTj zeon5ViGy)z%(-o1d>jX2H@jOA+uIU7@2lp{81P#>w0dZ5S!HeYp8YNa>h54DduN*Z zV%lMuhuh(s3W7*j6Vd1SK?1kKJHfObGHw%vA&GCRtfp&BGcQ|#p6K$n_W?mW||>u2)hT-K>CR?V8f43~(^qj}jeB!rXOxHO52ijqFGb2P> z+S6YTxiOIX^jFeZwp3pz9|al4Su*f3FIkx6i+zgTK(uR|^D1}^%aXh>x2W%rc#u*b z*DK)0)n0n0;aUihL*fVK;ft0_(nZMj0Xqx4UdN}B^cFUW^KTnVb2T*hUe2#0Hrqtl zJZ*6nG~;pa-}=Zw)mnVn`1Hjz?uxm_*GJ1$`Y~n7iTUbpS9p`@2VCk#KHoVg_p~un{h}c$ZO%{8>1?S4+Tro>4E5l0!~LMo_=}A)61%h(>NJX(;k|enfKnS z?>%)NnYA97B2{gzlu-QTuhc%8qilb8zyJ5mG12kTe+8feZ}x$-x~T*3oAtkiucL1P zQJ0oL^fWq7K>Hi_NLZAs4Ngs?r!Oz4+f$( zm4UrGZOx?}t?ez*NohB8^iSY{y$sN?p5GFDP&jZ$*3|jOPvPN6ApF4KM@mo_i4M~K z9y)#;^Zp~!_)IO2vbSGDGW5;Ncy6af8fZ=fs!_6Ig zAQsF+ICbMo?dgZMS6{XY+`i{ice#oA#^>$r{{AoSQ)h2h8!x%lueU9ZJ07ln{Gh*I zx1H4QW4%(BzI3?E^Y!h}^$EuExcgI6Ylpxv#=jpLIx+KUwW(r%^~TXmn zP3~87d4KZbsH$r1^RJ$7%BDVk$ysNLYHJIHFnJx0f8Dw(%rwtsInezDNhCbQJ;=)LICt}M7);e166M74*3r%mHDsZ4K1^Hxp^%0MmWjzL$8)MM0^-cIEw%EX4~?I9q-y2 zrsAZbcRL?_YfMa{hdP%xeSO4D1d9LdrqfG$;Pq`#4Te*U&@}N!+#2agf1!5z4O}0r zN$|hlY^MJZ>789e?i4RHJMq=7;!9P*fm70L*4ru+$`lE#v8>$6tjfBvO0kbw@3Lws zpHqGiD;F!r_BR5#*oUmQS(TM3lw)IoQXUdBY9HBalqZ9QUZriY`<$L+b!tp|ujU@9lw-6}!-?5ax5w{Ljm?CNZxcQr-v@i#PvXOg)o+zYRAmQm)3cc>Zg z1+oG#*2!F3M5m`2q3_>Zo$tE0c}Atiu+*}~FeE~V=Q_!~Rt)buM6^yi;Xu-;HO;a; zZ79`;Zt?*ot&{%#$GNgZLcVuJ5qByU{m&JD_^O?IA(A_>A8z0hza*PtqOxbinN2eN z#DpRU(%4x2fZE46A*=mrXJ@y0H!! zTRTlXvL@uEQ#m9yyp$Q7Nox*DVEW^bS{fSGb)X)$7gduL!*B`pRttTt_bP#^?5=h_Ia| z%~hHz77^E3&N~__^*wa0c8Fd)xcYLdGUK(z{MXg`(B%*LrQ7>LB_kUQe;6Bn>FR|@7pt>z&4|3S_BjhMqdLPxgN`c1O`7e{c%}A6i{9W4D82rd=Ji1l(2rGmZYuvT)O|*lzxJyM%X4KJiFNJ+q5b;}t z+!)>_huk?5y2HP>i2l`28k;Lc2;*CH=Q+Vlkd|v%0f-sX2q5f1#BGBCI!q)!m+q0?@0v5fA%Znia!39;?~V zZh0vxf}Wn@!l?(C<-WDd-`tNv(}s3C41^_$pu~EwPaK7;F8fAUm4g#BLOE~b$gSip zV#q914PxPr{*+tfn^Ep0SweUH&JZrpC+yt4(p>jfPL~J7@tvR2kJ$+JNgVS&e7`3x zyG3grWhJ3M_^@ziX>g0y4>$KoLP!8ci#Usy2Jt&gBXVZ?wS)CENgjKOS0WJ+YJp75 zPIE2$^tkU(?q%ND9*bzbN3r0YxxR&_`20OZb-Gf0(qpfvtSF`HuEQU<*WMMNs*7(% z8JUz+AdVVykbZkZaa;M@{Eo_Jkm7;`s`eJp?Sv)0{&guD zUx{T83#t*3UUUu!EqIFrw=>EuZ52K_Qh9%H80JWpQbyk=V`xwimC{zWUu#S`?hn3t z!9%#vU`HO}2AKMYU5}C_ItIeQKfy8P?RAGCPS>hv7~kyWxfGk25l+0(cA{dwoKr7j z1WTx;6m42uT<8b*U}0{)UK+5m&8B56j;K9LQmyZ9#p!Dqi$s31if}B5$AsId&Jpb=T~Xx z=Gz`qSLKYr4R3A8cw_P+;sh_Wxu)mHNCA~0daGrWt6A)6qhdcW(4#UGG>_I^1jvn} z-KyLt0zf~dfVG_8o5qPrxaI{vi`HX%rG=BKL#N-MaC>~DEQRvmu4y)*QmC!@l5t$9 z5J9X#zdZzR0<*!YyKQvdBFuevqgIV8JBCZ3BENmOL_Ja+dv&C@1zX;uAmb%EIzY0m~Qb<9rLw-rDulCs?#E z*>!L2Gn0%u9qmlWEoR9Fe(6wAW9-V%AMadYYHb1+K?CPLDPoWe7U`|g%YSaAw*(mW zt?*avTA5VRU_=ciuq7s_kZp-qD$Q;`wsg_uQbWwhCB?VU3Q~?^PNuTSz@}Xr0+Kaz zip>l?R5PS9SxPEbV4vF=aw}EQ+lxvqv+{~iO5;4hN_}{oAKeTaNAg>kHFY27K1}-V zmPY)!`c!L^Ma)q)-=g#^a-i|Lc!9r`fXT(mt5UiLN8h%kIE1QNdo_9dz_`uHa($*2 zRP9Vz+zTs}k{WgDm6}}_-Q3nbL?0)oIBa0FoG7K>6Faz^d73v&hdV0J18yB|R(urO zxCdDTm5*XI7B;o=vlES4?3b!~3dHyhR~M&tCet6^v}XiN0zz4xxG&?2Eptx_L#bj$ zY*^BG9iHUlvXv218F_RZ7Lr-CuZ))AG_Lc=OTiqh#O^+_QVnI6CoZap>hK=fC*PqL z-&nH8^I5@!wvXsY7D&;ZANbTPRGd>%5baPtPPxQ!%NgV}SNLGaz(csmGU2GZY-Rne zbmA~7W+{%LgAm)RQMC+WxbL@sDXjLcAYc1y&oiY@y%sy9x^(v(^8MF01=fyBqH!OR zQ{FIu>A430!_m_OJ>(0cc{8=rTtznzMz|wS?~N>4L_NUh*>NXEvBSLbfM_e;Qmudovcv-|4$-L&Y$+s!4(v%0rl z+Z}FLKv2;oRMb>%;=%=mYo~_~b)ttaRGO0e3T-3Y?lg3{GET7{ih3h{6ryz%VVxjg zjScJ}Pp{$mmL3GYe9B}zNo@;tId5HGqwz^MpO758m7Tf|>s8;v&+u2%bXP90C$kr_ z4+@(h7mk%Un8qjN2aG!H8svZ}s^Y8!?f|cW)X}NLWX58o8fK7=)orfcLw;lTomMVe zdw=Gj?Ufm>V~vZe)zU1>FwMC8jL~#s!d+udl^lcU3^%?0)2e@U@d@paeY_c$6v8viLi(~`S9mVLx5Ap9 zm(nMvvG#d7+|?lc!xx&ix9+zge$`sjBYHMHTReyZ*yM0#NiGOcYVG#g!G>C4lT*^h zhvP2V6!-zp+bQR+Y$e9<8q+794?E9ODpA#TPigeDxe2DnZ z&hA$Z?>20y$Y*c%I(Unl?=9-2lAbb_dg(#uUJws(wVg+E1arJ7>drVA_Hvw=U2Z5b zrxnN85dTp#;s9I1Wc59Sdj-cbtW}K6$CK>w!sI!Zr56IEj=dr9*k$Y~JO5;us{q(2 z6CAe~#w|`tG#J@YH!)eCRW>ncSR(pJk1LpJR^={zn@zOBHb!a!!ovpFC;M3vMoKO_ zJp7oM8$iRlsnc}MoC0MF9}*QRpSGHl=PH}7%j#|+AhmQI9=$YJd&R>WFVA2}<|W(6 zPL0*UC!9Tj=jv)(KOXw&wW_Vw+`_fog6f`~L8WF^0-sHuV*zuJ8CbfF<6ICgw(z6d zuJ+yr6xwQs@2-6P4zc$wb#Vxo&+G|W>Hu3UI+4}Hnr~^Cym4}lMljyefynt7Wy*wJ zc8a*;RKPAg@1&J3PFe_%v+;+{zGu*+jG0j(Oqfbi;;Wo6XaYA`^-e3&xnX9GZzXc% zi^_eF^oOK0uzw%fQ-543Cex;b8u(;!Gw=uFzW(PNvHp${mJuS{5mIfnP9?$ZFJ`j7 zw`BT5%)2vs5%RT0awGRIH!i$Ol4srXOTCteq-%u8A8dQbPjQ~3lE=uSjSq?7L}b`u zHL+&)hnmBTOQ#pMGXV{S$-S1*Dpc{9pBe3MNZ#)A%8Xcqyoc8e02{e9s zH~cwCzO5`ay!N!BtH9)II)WKSqTO;B9`7G5>00pPZ1RNR`0%KX>WjqXr}(&9F9oxc zYtWqabGI7n4-{3Pf>}(FcW(wIh(;-Jjl%YT+1(G+`+J^bUuwdxb-fB@4d}-@Oqw$n zqK0TT%f-qc_Mf=ANgGNT4a;aN9;C|H9$$%$*^SiyLe1Em1BFvxfT8X|MWG5w93a zU)e}^xf+`Epd-K0RN>o)R793%vWTAjx}ldW!{MjU(@)-TA{VvCreVVd_CQgOcpf zb)VaSZ;R{aif=rRhWIr0LpvC7p<#iSBQ42 z6DPfBp3&U)Xy82(2|aVjY#HhdZ%=vzIbYdPql}$>55U{dgzd=z3T)zxvH0cU((H@0 zn$dv$^!fK&bTC!-u55UdKLz?&*jz4>nYZowwRrA-c{&DXjI^FNRaMrBW3>Wak6dQy zzQ?$9%PhF^eh0~ZVvQTtB8-({-9*)sk1E}-pnK-(FCgJaBI~D39&O(*K9a1i-w^X6 zJ63RdJ1IEv8DK13(#-qPBz{8)aqFH}=CuAnzH{$*Vmv@FNn?yL$2+IP4|l)A_e?bW zhu?>Pa$i{^N#RgjP`Z-8)=TW48h_r)QsFx_A^WTi*Ldh&8|c32i>nQPOu-D*Kecw zS*(5>Uxn~nsx>6YZfi-jH{5oHLqc28QypSD$6?sx~7EEvU@PWh?2Rc>)~oB{4!>a0vX4FqKu>`qeh_XI&Mo8;XhD z6~k;co*Da#AtC}ls;04z7tZcqmYp#r%WZS|r*1kB;(km=i${4(t)avyRz6)vN#Q|n zDR~gND`a%y>icoprOXQhCfn8mWwoO^CO0X``fgwS_;9HBFi?MCHZ$-?KEk%|(L3?6 zoV>gOy8+1^(21$%VDe=ZcPF1jCIXK-8XQOtZ<;=5fQaDDo3uwZYZ{l85vr&ZXRB0B zyIqnr0gu+eu&0Ly@PKlj9J_C3{;_P|2Gev=DKjSCJv4TWFD==X^|d69ktcgUIO+1K z{047&40Ju-Ov*?(Vn89AYS6UCy01#dsfowF-0Zr#a7oqVyxxr%@v+&i8{MR>$Bf2j zg6moV51k9e`wV+65669jb;vVnwW*`r35*(qEPgTBpN zADefUESw0*G`+(YPI=ioa}vV>WeW-fwepS~>x>FH#DG{Dp4CUC$$p{0$b3KXI`Q@E ze7?+eJ`?qGj__&6dEM)(e8TdZfi4q;==nnB=u|Z^Qec7J{MNVVH+rYt zs_0>B+Jw2c&w4?016MB#G(*)VzRT}capjDLWe_jkKdJ0x1TY#LAL#3FIKA?!nM$5lsItSW^iy9@Ae_v3 z9Dg3(VYL}og{`R@&J3`pO;2 z_|u$}VCO`(|M0#j;f3HI2uKw#+TO{rvzLEOE)ydXtX?p2^(N@BX?%d z70*&txa(CW@*?*fk5$X4b$&-QOt4{da_!Oli^Gru7pZKaaf|v$Jd8oYDo}|ELET~` z2m4-aG$@%wzahG_G=FAj*tPsheN~FS%pslkg>Sr}O!FzdcF;r-8P1HMcF~v_!aSa{ zhTUiekYXuU;VJaF+O!|XEI#P#YZ#x7LM;n|TRf662%*qt8S(wOVo(=n+>Cca_!6{< zYd^c(b-!$sB&y3?frh)OXl! zZcR^@lO9EQdRag{6($F=x)y{0b5vMiU)vWUGD2_L(FGx;hXZuqOM^7$d8SB`UR|Jz zoD#x^Q!Kgm?Do{ts^UhD_l+~_>3H8tmQ)v4=69CSV=Kr2i+7DW&`(#e=rFgNh(qr* zp(=Vv;a~I;T$B~GC9wDCc*CA>?QW%#6d`DdTJ?n6Fb18afDcmW*2C-f#ovsayiiNP z9Ze*YQ4VI>g$4(jaEJ>tW2z(^Ykmv%KItPg^bYXp;0K0ihp+%;bfVpE53WFZkf1qI z5eDPzOFj3tc~LpA)aCP)2EyEcT52Ehsq4q_sO7LE5l75iCtV25#$x#lx-OhBrd`kZ z5nH(BBZrzN(64iK(j8u^`3v!$ffU&_@~`C`?zCdlVsshLK?Of=inTL{2pJa%ZVz>N zY~1;}QsGOYooE_u#lT`$!7D^B;!!R{H=i7F8!~%C-t9_U_LznF`3`+m=HApzn}F|i zDcu+D74gUH#*IFlpCK(uwvuiKZ_r&UpZmu6zs#7_wLQAsR()pqy6YghdchNQL4t5I zBDq$aO07`xEora60cNxXkGHF%g!W)(ce=%qH*CvR6&6E7K*WjO^l7Hqe{8au4l?Wnd_BeTgNZ^DI zIhV%+5ETADf=YOJe#?3N9V+>=apK=l$=^`P-%!clP|4p=$^Ql_`Q6&}e}qbY!WBP1 ze+DA>c#)wz$jNbjB;@gD{lD8r`j!6AwR}I*xc?Kr;N$zp@&2-ZWIUzNPk==5U+shh z{&cLL@Cy3{jHW4sUx?6!pQSM zra=9C4E_ehAlvc_@FT-?g=qPa@v}eo2_W-*fA9a#+rMjnum5$w{vTh9KcEtCHydPr zFf#ZZ3-HH+{2>V-!*^|I|ImJ#OaEMGA%AE*0OVNyRYQi^{i^Z)XIlXPa-jTDhfGpO zLd5^xACMnOb^WRd@*^E*f2kAxk8|Nc#>XP load "lib/test_helpers/pdf_comparison_test_factory.rb" diff --git a/spec/pdfs/picklists_pdf_spec.rb b/spec/pdfs/picklists_pdf_spec.rb index 49171f5fc8..b79d4c1925 100644 --- a/spec/pdfs/picklists_pdf_spec.rb +++ b/spec/pdfs/picklists_pdf_spec.rb @@ -1,3 +1,5 @@ +require_relative("../../lib/test_helpers/pdf_comparison_test_factory") + describe PicklistsPdf do let(:organization) { create(:organization) } let(:item1) { create(:item, name: "Item 1", organization: organization) } @@ -141,4 +143,42 @@ ]) end end + + describe "picklist pdf output" do + def compare_picklist_pdf(requests, expected_file_path) + pdf_file = PDFComparisonTestFactory.render_picklist_pdf(organization, requests) + begin + # Run the following from Rails sandbox console (bin/rails/console --sandbox) to regenerate these comparison PDFs: + # => load "lib/test_helpers/pdf_comparison_test_factory.rb" + # => Flipper.enable(:enable_packs) + # => PDFComparisonTestFactory.create_comparison_pdfs + expect(pdf_file).to eq(IO.binread(expected_file_path)) + rescue RSpec::Expectations::ExpectationNotMetError => e + Rails.root.join("tmp", "failed_match_picklist_" + expected_file_path.to_s.split("/").last + ".pdf").binwrite(pdf_file) + raise e.class, "PDF does not match, written to tmp/", cause: nil + end + end + + # The generated PDFs (PDFs to use for comparison) are expecting the packs feature to be enabled. + before(:each) do + Flipper.enable(:enable_packs) + end + + let(:storage_creation) { PDFComparisonTestFactory.create_organization_storage_items } + let(:organization) { storage_creation.organization } + let(:partner) { PDFComparisonTestFactory.create_partner_with_quota(organization) } + let(:file_paths) { PDFComparisonTestFactory.get_file_paths } + let(:expected_picklist_file_path) { file_paths.expected_picklist_file_path } + + context "when generating picklist PDF with comprehensive data" do + before(:each) do + PDFComparisonTestFactory.create_profile_with_pickup_person(partner) + end + + it "compares against expected PDF file" do + request = PDFComparisonTestFactory.create_line_items_request(partner: partner, storage_creation: storage_creation) + compare_picklist_pdf([request], expected_picklist_file_path) + end + end + end end From b00730f085cbc32d958e48c0315504a0f00f196e Mon Sep 17 00:00:00 2001 From: jonkaplan Date: Sat, 20 Sep 2025 14:00:25 -0400 Subject: [PATCH 6/7] Test more explicitly what is showing in the filtered response --- spec/requests/requests_requests_spec.rb | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/spec/requests/requests_requests_spec.rb b/spec/requests/requests_requests_spec.rb index e7e07326e1..2b3966acfe 100644 --- a/spec/requests/requests_requests_spec.rb +++ b/spec/requests/requests_requests_spec.rb @@ -43,15 +43,17 @@ end context "when there is a filter applied" do - it "shows print unfulfilled picklists button with correct quantity when filtered" do + it "shows only filtered requests, print unfulfilled picklists button with correct quantity" do Request.delete_all - create(:request, :pending) - create(:request, :started) + started_request = create(:request, :started, comments: "Started request - should appear") + pending_request = create(:request, :pending, comments: "Pending request - should not appear") get requests_path({ filters: { by_status: :started} }) expect(response.body).to include('Print Unfulfilled Picklists (1)') + expect(response.body).to include(started_request.comments) + expect(response.body).not_to include(pending_request.comments) end end end From a3c66f71cc4062ded97f1754eb70e456ab02e528 Mon Sep 17 00:00:00 2001 From: jonkaplan Date: Mon, 22 Sep 2025 09:10:32 -0400 Subject: [PATCH 7/7] Use direct string comparison --- .prettierignore | 1 + spec/requests/requests_requests_spec.rb | 10 +++++----- 2 files changed, 6 insertions(+), 5 deletions(-) create mode 100644 .prettierignore diff --git a/.prettierignore b/.prettierignore new file mode 100644 index 0000000000..1d085cacc9 --- /dev/null +++ b/.prettierignore @@ -0,0 +1 @@ +** diff --git a/spec/requests/requests_requests_spec.rb b/spec/requests/requests_requests_spec.rb index 2b3966acfe..8ad8c05403 100644 --- a/spec/requests/requests_requests_spec.rb +++ b/spec/requests/requests_requests_spec.rb @@ -46,14 +46,14 @@ it "shows only filtered requests, print unfulfilled picklists button with correct quantity" do Request.delete_all - started_request = create(:request, :started, comments: "Started request - should appear") - pending_request = create(:request, :pending, comments: "Pending request - should not appear") + create(:request, :started, comments: "Started request - should appear") + create(:request, :pending, comments: "Pending request - should not appear") get requests_path({ filters: { by_status: :started} }) - expect(response.body).to include('Print Unfulfilled Picklists (1)') - expect(response.body).to include(started_request.comments) - expect(response.body).not_to include(pending_request.comments) + expect(response.body).to include("Print Unfulfilled Picklists (1)") + expect(response.body).to include("Started request - should appear") + expect(response.body).not_to include("Pending request - should not appear") end end end