Ruby Prawn PDF Prevent Header Space From Populating Next Page - ruby-on-rails

I am using Prawn to generate a pdf receipt. Ideally I would like the following format:
Header
Customer Info
Items for order
Total
However when it goes to the second page there is a huge gap on the top from the Header spacing. How can I prevent it from adding the extra spacing on top?
Here is the code generating the receipt below (Sorry there is a lot of code):
class OrderPdf < Prawn::Document
def initialize(order)
super()
#order = order
bounding_box([10, 700], :width => 500) do
logo
end
message
date_shipped
order_number
bounding_box([0, -70], :width => 2000) do
bounding_box([65, 560], :width => 200) do
shipping_address
end
bounding_box([320, 560], :width => 200) do
payment_method
end
bounding_box([25, 450], :width => 500, :position => :center) do
line_items
end
end
end
def logo
image "#{Rails.root}/app/assets/images/letterhead-header.jpg" , :scale => 0.25, :position => :center
end
def message
move_down 20
text "Thanks for shopping with Dwellers. If you have any questions, please email support#dwellers.com"
end
def date_shipped
move_down 10
data = [["Shipment on #{#order.date_to_ship.strftime("%B %d, %Y")}",
"Items: #{#order.order_items.count}",
"Total: #{helpers.humanized_money(Money.new((#order.total_cents), "USD"), {:no_cents_if_whole => false, :symbol => true})}"]]
table data do
cells.style(:borders => [], :background_color => "DDDDDD", :valign => :center)
column(0).font_style = :bold
column(0).width = 400
column(0).size = 18
end
end
def order_number
move_down 10
# order_id = Digest::MD5.hexdigest "#{#order.id}#{#order.date_to_ship}"
text "Order\# #{#order.number}", size: 14, style: :bold
end
def shipping_address
move_down 10
text "Shipping address:", size: 14, style: :bold
indent(20) do
text "#{#order.user.name} #{#order.user.name_last}"
address_fields = #order.user.address.split(",")
text "#{address_fields[0]}"
if !address_fields[1].empty?
text "#{address_fields[1]}"
end
text "#{address_fields[2]}, #{address_fields[3]} #{address_fields[4]}"
end
end
def payment_method
move_down 10
#card = #order.user.card
text "Payment Information:", size: 14, style: :bold
if #card != nil
indent(20) do
text "#{#card[:card_type].capitalize} **** **** **** #{#card[:last_four]}"
billing_address
end
end
end
def billing_address
move_down 10
#billing_address = #order.user.billing_address.split
address_fields = #order.user.billing_address.split(",")
text "#{#card[:name]}"
text "#{address_fields[0]}"
if !address_fields[1].empty?
text "#{address_fields[1]}"
end
text "#{address_fields[2]}, #{address_fields[3]} #{address_fields[4]}"
end
def line_items
move_down 10
text "In this shipment:", size: 14, style: :bold
table line_item_rows + summary do
row(0).font_style = :bold
columns(1..3).align = :left
self.header = true
cells.style(:borders => [])
column(0).width = 380
row(row_heights().length-5).borders = [:bottom]
row(row_heights().length-2..row_heights().length-1).column(1).font_style = :bold
row(row_heights().length-1).column(1..2).size = 14
end
end
def line_item_rows
[["Item names", "Qty", "Price"]] +
#order.order_items.map do |order_item|
product = order_item.item.name + " - " + order_item.item.description
quantity = " x " +order_item.quantity.to_s
[product, quantity, helpers.humanized_money(Money.new((order_item.price_paid_cents), "USD"), {:no_cents_if_whole => false, :symbol => true})]
end
end
def summary
# move_down 15
data = [["", "", ""],
["Subtotal:", "Shipping:", "Total:"],
[helpers.humanized_money(Money.new((#order.subtotal_cents), "USD"), {:no_cents_if_whole => false, :symbol => true}),
helpers.humanized_money(Money.new((#order.shipping_fee_cents), "USD"), {:no_cents_if_whole => false, :symbol => true}),
helpers.humanized_money(Money.new(#order.total_cents, "USD"), {:no_cents_if_whole => false, :symbol => true})]].transpose
end
# Need this to get access to View Helpers, such as number_to_currency
def helpers
ActionController::Base.helpers
end
end
Below is how the receipt looks like.

For your flowing text that is beneath the header, you can use a span rather than a bounding_box, and it will start at the top of the page. There are some examples in the manual, just search for span in there:
http://prawn.majesticseacreature.com/manual.pdf

Related

PayPal Express Checkout with multiple items with active merchant in rails

I am trying to use PayPal ExpressCheckout button with multiple items but with no success.I am using NetBeans IDE, rails 4 and MySQL db.Here is what I did so far:
In my production.rb file I have:
Rails.application.configure do
config/application.rb.
config.after_initialize do
ActiveMerchant::Billing::Base.mode = :test
paypal_options = {
:login => "xxxx",
:password => "xxxx ",
:signature => "xxxx "
}
::STANDARD_GATEWAY = ActiveMerchant::Billing::PaypalGateway.new(paypal_options)
::EXPRESS_GATEWAY = ActiveMerchant::Billing::PaypalExpressGateway.new(paypal_options)
end
In my transaction.rb model I have:
def valid_purchase
if express_token.blank?
standard_purchase
else
express_token
end
def express_purchase
# price_in_cents = total
response = EXPRESS_GATEWAY.purchase(total, express_purchase_options)
if response.success?
self.status = "processed"
else
errors.add(:transactions, "---- #{response.message}.")
end
end
def express_token=(token)
self[:express_token] = token
if new_record? && !token.blank?
details = EXPRESS_GATEWAY.details_for(token)
self.express_payer_id = details.payer_id
self.ship_to_first_name = details.params["first_name"]
self.ship_to_last_name = details.params["last_name"]
end
end
private
def express_purchase_options
{
:ip => customer_ip,
:token => express_token,
:payer_id => express_payer_id
}
end
And in my transaction_controller.rb I have:
def express_checkout
order_items =[]
postage_rate=nil
item = Hash.new
#order = Order.find(session[:order_id])
#receipts = Receipt.where(:order_id=>#order.id)
#receipts.each do |r|
postage_rate = r.postage_rate * 100
end
#cart = Cart.find(#order.cart_id)
#cart.cart_items.each do |i|
#product = Product.find(i.product_id)
item = {
name: #product.product_name,
quantity: i.amount,
description: "ORDER_ID: #{#order.id}",
amount: #product.selling_price * 100 ,
shipping: postage_rate/#cart.cart_items.size
}
order_items << item
end
price_in_cents = (#order.total_p_pr * 100).round(2)
options = {
:ip => request.remote_ip,
:return_url => url_for(:action=>:new, :only_path => false),
:cancel_return_url => catalogs_traders_url,
:currency => "USD",
:allow_guest_checkout=> true,
:items => order_items # this line outputs: [{:name=>"owl potty", :quantity=>1, :description=>"ORDER_ID: 249", :amount=>2808.0, :shipping=>332.0}, {:name=>"a bag", :quantity=>1, :description=>"ORDER_ID: 249", :amount=>1260.0, :shipping=>332.0}, {:name=>"bracelet", :quantity=>1, :description=>"ORDER_ID: 249", :amount=>120.0, :shipping=>332.0}, {:name=>"beautiful woman", :quantity=>1, :description=>"ORDER_ID: 249", :amount=>74352.0, :shipping=>332.0}]
}
#passing the cost of the order
response = EXPRESS_GATEWAY.setup_purchase(price_in_cents,options )
redirect_to EXPRESS_GATEWAY.redirect_url_for(response.token)
end
def new
#transaction = Transaction.new(:express_token => params[:token])
end
I get:
Any help will be more than welcome. Thank you!
I red this post very,very carefully
setting tax amount in Active Merchant / PayPal Express Checkout
and I understood my mistakes. Here is my corrected transaction_controller:
# to redirect to PayPay site
def express_checkout
pr = nil
tp = nil
items =[]
postage_r=[]
total_p = []
order_items =[]
postage_rate=nil
item = Hash.new
#order = Order.find(session[:order_id])
#receipts = Receipt.where(:order_id=>#order.id)
#receipts.each do |r|
total_p << r.total_price
postage_r << r.postage_rate
end
tp = total_p.inject{|sum,x| sum + x }
pr = postage_r.inject{|sum,x| sum + x }
#cart = Cart.find(#order.cart_id)
#cart.cart_items.each do |i|
#product = Product.find(i.product_id)
item = {
name: #product.product_name,
quantity: i.amount,
description: "ORDER_ID: #{#order.id}",
amount: #product.selling_price * 100 ,
}
order_items << item
end
price_in_cents = (#order.total_p_pr * 100).round(2)
options = {
:subtotal => tp * 100,
:shipping => pr * 100,
:handling => 0,
:tax => 0,
:ip => request.remote_ip,
:return_url => url_for(:action=>:new, :only_path => false),
:cancel_return_url => catalogs_traders_url,
:currency => "USD",
:allow_guest_checkout=> true,
:items => order_items
}
#passing the cost of the order
response = EXPRESS_GATEWAY.setup_purchase(price_in_cents,options )
redirect_to EXPRESS_GATEWAY.redirect_url_for(response.token)
end
It worked. I hope my post will be useful for someone who want to integrate Express Checkout button. Thank you for all your help!

Prawn generating PDF on localhost but not on server

I am working on an Ruby on Rails application which uses prawn to generate PDF's when a user clicks on a link. The functionality works when running the application on the localhost but when i deploy it to the server and click on the link the file doesnt load with the message "Failed to load PDF document".
I have done some research and seen references in other places regarding other documents generating gems but none specific to Prawn. Any requests for additional information will gladly be met.
I think showing the code might help so here goes. It is financial application where a page lists statements with a view button and a pdf icon to view them pdf format. When the user clicks on the pdf icon prawn then comes into play. Hope this help clarify my problem somewhat.
Controller Code:
require 'json'
InternetBanking.controllers :statements do
get :index do
#today = Time.now
render 'statements/listing'
end
get :listing do
#today = Time.now
render 'statements/listing'
end
get :activity, provides: [:json] do
content_type :json
#customer.accounts.map{|x| {x.name => x.activity}}.to_json
end
get :show, map: "/statements/:id", provides: [:html, :pdf] do
begin
m = params[:id].force_encoding('UTF-8').match(/(\d+)-(\w+)-(\d+)/)
accountno = m[1].to_i
month = Date::MONTHNAMES.index(m[2])
year = m[3].to_i
raise Exception if month.nil? or year.nil?
# Fetch account_id from logged in customer, prevent crafted URLs.
#account = #customer.fetch_account(accountno)
raise Exception if #account.nil?
#statement = #account.statement(year, month)
# TODO: Deal with no transactions
if content_type == :pdf
content_type 'application/pdf'
#statement.to_pdf
else # HTML
render 'statements/show'
end
rescue
flash[:error]= 'You cannot have an empty month field'
end
end
end
Model Code:
Statement.rb
class Statement
attr_accessor :opening_balance, :closing_balance
def initialize(acct, year = nil, month = nil)
#db = Database.instance
#account = acct
#year = year
#month = month
#month_name = Date::MONTHNAMES[#month]
end
def to_s
"#{#account.number}-#{#month_name}-#{#year}"
end
def to_pdf
title = #account.name
subtitle = "Account Statement: #{#month_name} #{#year}"
StatementPDF.new(title, subtitle, transactions).render
end
def transactions
return #transactions if #transactions
start = Date.civil(#year, #month).strftime('%Y-%m-%d')
finish = Date.civil(#year, #month, -1).strftime('%Y-%m-%d')
rows = #db.call('IBStatement2', #account.account_id, start, finish)
#transactions = rows.map {|txn| Transaction.new(txn)}
end
end
Statement_pdf.rb
require 'prawn'
class StatementPDF < Prawn::Document
BOX_MARGIN = 36
# Additional indentation to keep the line measure with a reasonable size
INNER_MARGIN = 30
# Vertical Rhythm settings
RHYTHM = 10
LEADING = 2
# Colors
BLACK = "000000"
LIGHT_GRAY = "F2F2F2"
GRAY = "DDDDDD"
DARK_GRAY = "333333"
BROWN = "A4441C"
ORANGE = "F28157"
LIGHT_GOLD = "FBFBBE"
DARK_GOLD = "EBE389"
BLUE = "0000D0"
GREY = "CCCCCC"
def initialize(title, subtitle, rows)
#rows = rows
#title = title
#subtitle = subtitle
super(page_size: 'A4') do
define_grid(columns: 4, rows: 16, gutter: 10)
header
transactions_table
footer
end
end
private
def header
grid([0,0],[1,0]).bounding_box do
image 'public/images/Statement/logo.png', width: 110
end
grid([0,1],[1,1]).bounding_box do
font_size 10
text 'Yada', font_weight: 'bold'
font_size 9
text 'Yada'
text 'Yada'
text 'Yada'
text 'Yada'
text 'Yada'
text 'Yada'
end
grid([0,2],[0,3]).bounding_box do
font_size(20)
text #title, color: '#0044AA', :align => :right
font_size(14)
text #subtitle, color: '#0044AA', :align => :right, :valign => :bottom
end
font_size(12)
end
def footer
grid([15,0],[15,3]).bounding_box do
image 'public/images/statement-logo.png', width: 100
end
end
def transactions_table
grid([2,0], [13,3]).bounding_box do
data = [%w(Date Description Amount Balance)]
data += #rows.map{|r| [r.value_date, r.description, r.amount, r.balance]}
options = { header: true, width: 520,
column_widths: {0 => 100, 2 => 100},
row_colors: ['EEEEEE', 'FFFFFF']}
table(data, options) do
cells.padding = 5
cells.border_width = 0.5
cells.border_color = GREY
row(0).font_weight = 'bold'
row(0).border_color = BLACK
row(1).border_top_color = BLACK
column(2).align = :right
column(3).align = :right
end
end
end
end
View Code:
- content_for :title, 'Full Statement Listing'
- content_for :toolbar do
%a.back(href='/balances') Back
%table
%thead
%tr
%th.year(rowspan=2) Year
%th.month(rowspan=2) Month
%th.accounts{colspan: #customer.accounts.size} Accounts
%tr
- #customer.accounts.each do |account|
%th= account.name
- #today.year.downto(#today.year - 3) do |year|
%tbody
- x = (#today.year == year) ? #today.month : 12
- x.downto(1) do |month|
%tr
- if month == x
%td{rowspan: x}= year
%td= Date::MONTHNAMES[month]
- #customer.accounts.each do |acct|
- if acct.activity[year] && acct.activity[year][month]
%td{'data-transactions' => acct.activity[year][month]}
- stmt = acct.statement(year, month)
= link_to 'View', url(:statements, :show, id: stmt.to_s)
= link_to image_tag('/icons/document-pdf.png', alt: 'Download PDF'), url(:statements, :show, id: stmt, format: :pdf), class: :pdf
- else
%td

Iteriate rows on prawn pdf

I want to create multiple rows that will generate several orderlines from an orderid on prawn pdf but only one row of the orderline gets populated.
class OrderPdf < Prawn::Document
def initialize(order, view)
super(top_margin: 70)
#order = order
#view = view
order_id
products
end
def logo
image_path = "#{Rails.root}/app/assets/images/logo.jpg"
end
def order_id
text "Invoice", size: 30, style: :bold, :align => :right
font("Helvetica", :size => 11, style: :bold) do
text "14077 Westheimer Rd"
text "Houston, TX, 77077"
text "Phone: 281-258-4940"
text "Fax: 281-258-4947"
text "AlphaOmegaFurniture.com"
move_up 40
text "INVOICE NO. ##{#order.id}", :align => :right
text "DATE: #{#order.OrderDate}", :align => :right
text "CUSTOMER ID: #{#order.Customer_id}", :align => :right
move_down 40
font_size 12
text "To: #{#order.Customer.fullName}"
text "Phone: #{#order.ShipToPhone}"
move_up 30
font_size 12
text "Ship To: Name: #{#order.Customer.fullName}", :indent_paragraphs => 200
text "Address: #{#order.ShipToAddress}", :indent_paragraphs => 260
text " #{#order.ShipToCity}, #{#order.ShipToState}, #{#order.ShipToZip}", :indent_paragraphs => 325
text "Phone: #{#order.ShipToPhone}", :indent_paragraphs => 260
end
move_down 20
font_size 8
data =[["SALES PERSON", "JOB", "SHIPPING METHOD", "SHIPPING TERMS", "DELIVERY DATE", "PAYMENT TERMS", "DUE DATE" ],
["#{#order.Employee.fullName}", "", "", "", "#{#order.EstimatedDeliveryDate}", "", "#{#order.OrderDate}"]
]
table(data, :position => :left, :row_colors => ["F0F0F0", "FFFFCC"] ) do
row(0).font_style = :bold
end
move_down 20
font_size 8
def products
data =["PROVIDER", "QTY", "ITEM #", "DESCRIPTION", "UNIT PRICE", "DISCOUNT", "LINE TOTAL" ]
data2 = data
OrderLine.all.each do |orderline|
data2 = ["", "", "#{orderline.Product_id}", "#{orderline.Order_id}", "#{orderline.Quantity}", "", ""]
end
data3 = [data,data2]
table(data3, :position => :left, :row_colors => ["F0F0F0", "FFFFCC"], :column_widths => [60,60,60,180,60,60,60] )
end
end
end
class OrdersController < ApplicationController
before_action :set_order, only: [:show, :edit, :update, :destroy]
# GET /orders
# GET /orders.json
def index
#orders = Order.all
end
# GET /orders/1
# GET /orders/1.json
def show
#order = Order.find(params[:id])
respond_to do |format|
format.html
format.pdf do
pdf = OrderPdf.new(#order, view_context)
send_data pdf.render, filename: "order_#{#order_id}.pdf",
type: "application/pdf",
disposition: "inline"
end
end
end
Here some comments:
1._ Its better to relate Order to has many OrderLines. And get OrderLine array from Order parent because you will print always all the order_lines of all the orders.
For example: #order.lines
2._ Like you write each loop it always overwrite data2 with the last value. Its better to use something like this:
#table_data ||= OrderLine.all.map { |orderline| ["", "", "#{orderline.Product_id}", "#{orderline.Order_id}", "#{orderline.Quantity}", "", ""] }
The method products may work with something like this:
def products
data =["PROVIDER", "QTY", "ITEM #", "DESCRIPTION", "UNIT PRICE", "DISCOUNT", "LINE TOTAL" ]
table_data ||= OrderLine.all.map { |orderline| ["", "", "#{orderline.Product_id}", "#{orderline.Order_id}", "#{orderline.Quantity}", "", ""] }
table(data, :position => :left, :row_colors => ["F0F0F0", "FFFFCC"], :column_widths => [60,60,60,180,60,60,60] )
table(table_data, :position => :left, :row_colors => ["F0F0F0", "FFFFCC"], :column_widths => [60,60,60,180,60,60,60] )
end
I haven't test it. But you may have the idea.

In Rails with the Prawn gem how to set the table width to 100%?

Im using the Prawn gem to generate a pdf table. I need to set the width of the table to 100%. How can I do this?
Here's my slmun_pdf.rb
class SlmunPdf < Prawn::Document
def initialize(slmunDbs, view, allcount)
if slmunDbs.table_name == "schools"
super(top_margin: 50)
if slmunDbs.size != allcount
#warn = " (Not all Schools)"
else
#all = " All #{slmunDbs.size} Schools"
end
text "Showing#{#all}", size: 18, style: :bold, align: :center, color: "636363"
text "#{#warn}", size: 11, align: :center, color: "858585"
#schools = slmunDbs
#view = view
school_list
end
end
def school_list
move_down 20
table school_list_rows, :cell_style => { :font => "Helvetica", :size => 9, :border_width => 0.5, :borders => [:top, :bottom], :border_color => "B0B0B0", :text_color => "737373"} do
self.row(0).align = :center
row(0).background_color = 'A0B046'
row(0).text_color = "FFFFFF"
self.row_colors = ["DDDDDD", "FFFFFF"]
self.header = true
end
end
def school_list_rows
[["Name", "Contact", "Country", "Pre Registration", "Full Registration", "Assigned Countries", "Total Delegates" ]] +
#schools.map do |school|
countries = ""
len = school.countries.count
school.countries.each_with_index do |country, index|
countries << "#{country.country }"
if index+1 != len
countries << ","
end
end
[school.name, school.contact, school.country, #view.yesno(school.prereg), #view.yesno(school.fullreg), countries, school.delegates.size ]
end
end
end
The existing documentations didn't help. I might be using a different method? I learnt this from rails cast!
You can use the width method of the Prawn::Document::BoundingBox class:
require 'prawn'
require 'prawn/table'
Prawn::Document.generate("hello.pdf") do
table_content = [["This table"], ["covers the"], ["whole page width"]]
table table_content, width: bounds.width
end

Prawn - how to generalize the common things in the pdf document into a separate module for code re-usage?

I am using the Rails Prawn to generate pdf files. Now that i am able to generate pdf with all necessary things that i need(eg. table, header, footer, logo, borders etc...). Now I need to use the common things(header, foooter, borders) in a method inside separate module and call this method from my original program?
My original program:
travel_report.rb
module TravelReport
include Header
def self.generate(start_ts, end_ts, format_type, obu_ids, interval)
Prawn::Document.generate("public/test.pdf") do
Borders
page_count.times do |i|
go_to_page(i+1)
Header.border
end
Header and Boundary Lines
page_count.times do |i|
go_to_page(i+1)
ask = "public/ashok.jpg"
image ask, :at => [15, 750], :width => 120
alert = "public/alert.jpg"
image alert, :at => [410, 740], :width => 120
end
footer
page_count.times do |i|
go_to_page(i+1)
lazy_bounding_box([bounds.left+30, bounds.bottom + 20], :width => 100) {
text "Bypass Report"
}.draw
end
end
Separate Module for Borders
module Header
#class Cheader < Prawn::Document::BoundingBox
#include Prawn
def self.border
pdf = Prawn::Document.new
pdf.bounding_box([5, 705], :width => 540, :height => 680) do
pdf.stroke_bounds
end
end
#end
end
This code doesnt creates any border... Any idea how to create separate module for this????
#create a separate module
#program
include HeaderFooter
Prawn::Document.generate("public/test.pdf") do |pdf|
pdf.page_count.times do |i|
pdf.go_to_page(i+1)
HeaderFooter.border(pdf)
#render :partial => 'header', :locals => {:ppdf => pdf}
end
#Header and Boundary Lines
pdf.page_count.times do |i|
pdf.go_to_page(i+1)
HeaderFooter.image(pdf)
end
#footer
pdf.page_count.times do |i|
pdf.go_to_page(i+1)
HeaderFooter.footer(pdf)
end
end
create a module to define the methods(header_footer.rb)
module HeaderFooter
#Method for border creation in pdf
def self.border(ppdf)
ppdf.bounding_box([5, 705], :width => 540, :height => 680) do
ppdf.stroke_bounds
end
end
#method to create the logos in the pdf
def self.image(ppdf)
ask = "public/ashok.jpg"
ppdf.image ask, :at => [15, 750], :width => 120
alert = "public/alert.jpg"
ppdf.image alert, :at => [410, 740], :width => 120
end
#method to print footer text in the pdf
def self.footer(ppdf)
ppdf.lazy_bounding_box([ppdf.bounds.left+30, ppdf.bounds.bottom + 20], :width => 100) {
ppdf.text "Bypass Report"
}.draw
end
end
This works fine...

Resources