Skip to content

Instantly share code, notes, and snippets.

@ajesler
Last active July 5, 2024 07:28
Show Gist options
  • Save ajesler/5b37945fbfd4bdc63888b985e29e527a to your computer and use it in GitHub Desktop.
Save ajesler/5b37945fbfd4bdc63888b985e29e527a to your computer and use it in GitHub Desktop.
PDF Invoice generation script
#!/usr/bin/env ruby
# Usage:
# Save this to a file invoice.rb, and set the values from L#185 on to your needs.
# Then run
# $ ruby invoice.rb
require 'bundler/inline'
require "date"
gemfile true do
source "https://rubygems.org"
gem 'prawn-table', "~> 0.2.2"
end
module TwoColumnHelper
def two_columns(ratio: 0.5, left_width: nil)
left_column_width = left_width || ratio * bounds.width
right_column_width = bounds.width - left_column_width
start_y = cursor
bounding_box([0, start_y], :width => left_column_width) do
yield :left
end
bounding_box([left_column_width, start_y], :width => right_column_width) do
yield :right
end
end
end
class LineItem
attr_reader :description, :rate, :quantity
def initialize(description:, rate:, quantity:)
@description = description
@rate = rate
@quantity = quantity
end
def amount
quantity * rate
end
end
BankDetails = Struct.new(:name, :account_number)
Supplier = Struct.new(:name, :address, :phone_number, :gst_number, :bank_account)
Recipient = Struct.new(:name, :address)
class Invoice
attr_reader :supplier, :recipient, :date, :id, :line_items, :extra_information
def initialize(supplier:, recipient:, date:, id:, line_items:, **extra_information)
@supplier = supplier
@recipient = recipient
@date = date
@id = id
@line_items = line_items
@extra_information = extra_information
end
def name
"#{recipient.name}-#{id}"
end
end
class InvoiceDocument
include Prawn::View
include TwoColumnHelper
attr_reader :invoice
def initialize(invoice)
@invoice = invoice
end
def build
text "TAX INVOICE", size: 20, align: :center
hr
supplier_details
hr
recipient_details
move_down 30
items
move_down 30
payment_info
end
private
def hr
move_down 10
horizontal_rule
move_down 10
end
def supplier_details
two_columns do |side|
if side == :left
text "GST Number: #{supplier.gst_number}"
text "Date: #{invoice.date.strftime('%e %b %Y')}"
text "Invoice Number: #{invoice.id}"
else
text supplier.name, align: :right
text supplier.address, align: :right
end
end
end
def recipient_details
two_columns(left_width: 40) do |side|
if side == :left
text "To:"
else
text recipient.name
text recipient.address
end
end
if invoice.extra_information.size > 0
move_down 20
invoice.extra_information.each { |name, value| text "#{name}: #{value}" }
end
end
def items
subtotal = invoice.line_items.sum(&:amount).round(2)
gst = (subtotal * 0.15).round(2)
total = (subtotal + gst).round(2)
data = [
["Description", "Hours", "Rate", "Amount"],
*invoice.line_items.map { |li| [li.description, right_aligned(li.quantity), right_aligned(li.rate), right_aligned(li.amount)] },
[nil]*4,
[nil, {content: "Subtotal", colspan: 2, align: :right, font_style: :bold}, right_aligned("%.2f" % subtotal)],
[nil, {content: "GST", colspan: 2, align: :right, font_style: :bold}, right_aligned("%.2f" % gst)],
[nil, {content: "Total Due (NZD)", colspan: 2, align: :right, font_style: :bold}, right_aligned("%.2f" % total)],
]
column_widths = [0.55, 0.15, 0.15, 0.15].map { |r| r * bounds.width }
items_offset = invoice.line_items.count + 1
table(data, column_widths: column_widths) do
cells.padding = 5
row(0).font_style = :bold
row(items_offset).borders = []
rows(items_offset..(items_offset + 4)).borders = []
row(items_offset+3).columns(1..3).borders = [:top]
end
end
def right_aligned(content)
{ content: content.to_s, align: :right }
end
def payment_info
if (bank_account = invoice.supplier.bank_account)
text "Please pay by direct credit to"
move_down 5
text "Name: #{bank_account.name}"
text "Account Number: #{bank_account.account_number}"
end
end
def supplier
invoice.supplier
end
def recipient
invoice.recipient
end
end
if __FILE__ == $0
# Generate and save the invoice PDF"
invoice_date = Date.new(2024, 1, 31)
bank_details = BankDetails.new("J SMITH", "01-1234-0000567-000")
supplier = Supplier.new("Jean Smith", "1 Sample Ave\nSumner\nChristchurch", "021 111 1111", "000-000-000", bank_details)
recipient = Recipient.new("Company Being Billed Co", "PO Box 20 123\nAuckland")
id = invoice_date.strftime("%Y%m%d")
line_items = [
LineItem.new(
description: "Technical services (January 2024)",
quantity: 75,
rate: 50
)
]
invoice = Invoice.new(
supplier: supplier,
recipient: recipient,
date: invoice_date,
id: id,
line_items: line_items,
"Purchase Order Number": "1234-5678",
"Custom Attribute": "ABC123"
)
invoice_file_name = "invoice-#{invoice.id}.pdf"
invoice_document = InvoiceDocument.new(invoice)
invoice_document.build
invoice_document.save_as(invoice_file_name)
puts "\nGenerated #{invoice_file_name}"
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment