Skip to content

Instantly share code, notes, and snippets.

@plusplus
Created September 29, 2010 04:30
Show Gist options
  • Save plusplus/602290 to your computer and use it in GitHub Desktop.
Save plusplus/602290 to your computer and use it in GitHub Desktop.
require 'date'
#
# My infinite monkeys just wrote the complete works of Shakespeare. Just can't find it
# amongst these infinite monkeys.
#
# That's right MONKEY PATCHING.....
#
class Date
def quarter
(((self.month + 5) % 12) / 3.0).floor + 1
end
end
module Enumerable
def sum( symbol = nil )
if symbol.nil?
self
else
self.collect {|i| i.send(symbol)}
end.inject( 0, :+ )
end
# Nicked from activesupport
def group_by
assoc = Hash.new
each do |element|
key = yield(element)
if assoc.has_key?(key)
assoc[key] << element
else
assoc[key] = [element]
end
end
assoc
end unless [].respond_to?(:group_by)
end
module Expensify
class Expense
attr_accessor :amount, :date, :category, :for, :gst, :income, :work
def quarter
date.quarter
end
def gst_amount
return 0 unless gst
(amount * (income / 100.0)) / 11.0
end
def income_amount
(amount * ((income || 0) / 100.0))
end
# The amount deductible as a work expense. Includes
# GST as you can't claim that proportion of the GST
# in a BAS.
def work_deductible
amount * (work / 100.0)
end
# The amount deductible as a business expense. Does NOT
# include GST.
def business_deductible
income_amount - gst_amount
end
# Total deductible amount for the expense (used mostly for
# calculating NET income).
def deductible
work_deductible + business_deductible
end
end
class Income
attr_accessor :amount, :date, :gst
def initialize( amount, date, gst = true )
@amount = amount
@date = date
@gst = gst
end
def gst_free
amount * (gst ? (10/11.0) : 1.0)
end
end
# This is the DSL such as it is for constructing the accounts object
class Builder
def initialize( accounts )
@accounts = accounts
@expense_defaults = {}
@income_defaults = {}
@expense_definitions = {}
end
def define_expense( name, options )
@expense_definitions[name] = options
end
def method_missing( method, *args )
if @expense_definitions[method] && args.size > 1
options = @expense_definitions[method]
options = options.merge(args[2]) if args.size > 2
expense args[0], args[1], options
else
super
end
end
def expense_defaults( defaults_hash )
@expense_defaults = defaults_hash
end
def income_defaults( defaults_hash )
@income_defaults = defaults_hash
end
def income( amount, date, details )
details = @income_defaults.merge( details )
i = Income.new( amount, date, details[:gst] )
@accounts.add_income(i )
end
def expense( amount, date, details )
e = Expense.new
e.amount = amount
e.date = date
details = @expense_defaults.merge( details )
e.income = details[:income] || 0
e.work = details[:work] || 0
e.category = details[:cat]
e.for = details[:for]
e.gst = details[:gst]
@accounts.add_expense( e )
end
def expenses
@expenses
end
end
class Accounts
def initialize
@expenses = []
@income = []
end
def add_expense(e)
@expenses << e
end
def add_income( i )
@income << i
end
def gst_report
quarters = {1 => [], 2 => [], 3 => [], 4 => []}
@expenses.each {|e| quarters[e.quarter] << e}
(1..4).each do |q|
expenses = quarters[q]
income = @income.select {|i| i.date.quarter == q}
g1 = income.sum :amount
income_without_gst = income.select {|i| !i.gst}
g2 = income_without_gst.sum :amount
g3 = 0
g4 = 0
g5 = g2 + g3 + g4
g6 = g1 - (g5)
g7 = 0.0
g8 = g6 + g7
g9 = g8 / 11
g10 = 0
g11 = expenses.sum( :income_amount )
g12 = g10 + g11
g13 = 0 # purchases for making input taxed sales
purchases_without_gst = expenses.select {|e| !e.gst}
g14 = purchases_without_gst.sum( :income_amount )
g15 = 0.0 # private use - already taken into account with income amount
g16 = g13 + g14 + g15
g17 = g12 - g16 # total purchases subject to GST
g18 = 0.0 # adjustments
g19 = g17 + g18 # total purchases subject to GST after adjustments
g20 = (g19 / 11)
puts "\n\nQ#{q}:"
report_line "G1", g1.floor, "Total Sales (including any GST)"
report_line "G2", g2.floor, "Export Sales"
report_line "G3", g3.floor, "Other GST-free Sales"
report_line "G10", g10.floor, "Capital Purchases (including any GST)"
report_line "G11", g11.floor, "Non-Capital Purchases (including any GST)"
report_line "G12", g12.floor, "G10 + G11"
report_line "G13", g13.floor, "Purchases for making input taxed sales"
report_line "G14", g14.floor, "Purchases without GST in the price"
report_line "G15", g15.floor, "Estimate purchases for private use on not income tax deductible"
report_line "G16", g16.floor, "G13 + G14 + G15"
report_line "G17", g17.floor, "Total Purchases subject to GST"
report_line "1A", g9.floor, "GST on Sales"
report_line "1B", g20.floor, "GST on purchases"
report_line "-", (g9 > g20) ? "PAY" : "REFUND", "Payment or refund?"
report_line "AMNT", (g9 - g20).floor.abs, "Amount"
end
end
def report_line( column, amount, description )
puts( "#{column.rjust(5)}: #{amount.to_s.rjust(6)} : #{description}")
end
def income_report_line( column, amount )
puts( "#{column.ljust(20)}: #{amount.to_s.rjust(6)}")
end
def income_report
total = @income.sum :gst_free
puts "\n\n\nINCOME"
income_report_line "Ex GST", total.floor
categories = @expenses.group_by { |e| e.category }
puts "\n\nWORK RELATED"
categories.keys.sort.each do |k|
total = categories[k].sum :work_deductible
income_report_line k, total.floor
end
puts "\n\nBUSINESS RELATED"
categories.keys.sort.each do |k|
total = categories[k].sum :business_deductible
income_report_line k, total.floor
end
puts
puts
income_report_line "TOTAL EXPENSES", @expenses.sum( :deductible ).floor
end
end
def self.expenses(&block)
accounts = Accounts.new
builder = Builder.new( accounts )
builder.instance_exec(&block)
accounts
end
end
# Sample
# require 'lib/expensify'
acc = Expensify::expenses do
expense_defaults :gst => true, :income => 100, :cat => "unknown"
income_defaults :gst => true
define_expense :internet, :for => 'Internode: Internet', :income => 70, :cat => 'internet'
define_expense :github, :for => 'Github', :gst => false, :cat => 'services'
define_expense :phone, :for => 'Telstra: iPhone4', :cat => 'phone'
income 1000000, Date.new(2010, 7, 13), :from => "Holding world to ransom"
expense 200.00, Date.new(2010, 8, 3), :for => 'Evil Corp: Shark head laser mounting kit', :cat => 'equipment'
expense 7.76, Date.new(2010, 8, 12), :for => 'Officeworks: Tudor Eco Notebook', :cat => 'stationary'
expense 5.99, Date.new(2010, 8, 12), :for => 'Officeworks: Sharpie pen', :cat => 'stationary'
expense 14.68, Date.new(2010, 8, 12), :for => 'Officeworks: Pk 300 System Cards', :cat => 'stationary'
internet 49.95, Date.new(2010, 7, 14)
internet 59.95, Date.new(2010, 8, 17)
internet 69.95, Date.new(2010, 9, 14)
github 8.08, Date.new( 2010, 7, 29 )
github 8.13, Date.new( 2010, 8, 27 )
phone 106.84, Date.new( 2010, 9, 15 )
end
acc.gst_report
acc.income_report
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment