Created
September 29, 2010 04:30
-
-
Save plusplus/602290 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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