# Copyright (c) 2006, Chris Parsons (chris@edendevelopment.co.uk) # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # * Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # * Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # * Neither the name of Chris Parsons nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE # POSSIBILITY OF SUCH DAMAGE. require 'time' require 'currency' require 'get_set_method' def new_invoice(&block) inv = Invoice.new inv.instance_eval &block Invoice.invoices << inv inv end class Invoice module TimeMethods # Time functions def second; 1; end; alias :seconds :second def minute; second * 60; end; alias :minutes :minute def hour; minute * 60; end; alias :hours :hour def day; hour * 8; end; alias :days :day def week; day * 5; end; alias :weeks :week def month; week * 4; end; alias :months :month end module WhatMethods def on(what) { :count => self, :what => what } end alias :to :on alias :for :on alias :of :on end module Currency class Amount include WhatMethods end end include TimeMethods include GetSetMethod MILEAGE_RATE = 0.40 # Current UK IR mileage rate attr_reader :items, :expenses def initialize @expenses ||= [] @items ||= [] @vat = 17.5 @date = Time.now @default_time_interval = seconds end def self.invoices(newinv = nil) @@invoices ||= [] @@invoices << newinv unless newinv.nil? @@invoices end get_set_method :code, :address, :vat, :currency def expense(hash) @expenses << Invoice::Expense.new(hash) end def date(dt = nil) if dt.nil? return @date else @date = dt.class == Time ? dt : Time.parse(dt) end end def work_at(amt, &block) ig = Invoice::ItemGroup.new ig.rate = amt # Discern the rate from a currency amount's operation value if amt.class == Invoice::Currency::Amount && !amt.last_operation_value.nil? ig.default_time_interval = amt.last_operation_value else ig.default_time_interval = 1 end ig.instance_eval &block @items.push *(ig.expand) end alias :mileage_at :work_at def add(hash) @items << Item.new(hash[:rate], hash[:count], hash[:what], hash[:date]) end def total t = total_items @expenses.map { |e| t += e.amount } t end def total_items t = 0 @items.map { |i| t += i.total } to_currency(t) end def to_currency(amt) currency ? eval("amt.in.#{currency}") : amt end def total_vat total_items * @vat/100 end class Item attr_reader :rate, :count, :what, :date, :default_time_interval def initialize(rate, count, what, date = nil, default_time_interval = 1) @rate = rate @count = count @what = what @date = date @default_time_interval = default_time_interval end def rate @rate * @default_time_interval end def count @count / @default_time_interval end def total @rate * @count end end class Expense attr_reader :amount, :what def initialize(hash) @amount = hash[:count] @what = hash[:what] end end class ItemGroup attr_accessor :rate, :default_time_interval def spent(hash) @items ||= [] @items << hash end def drove(hash) hash[:what] = "Mileage: #{hash[:what]}" spent(hash) end def expand @items.collect { |i| Invoice::Item.new(@rate, i[:count], i[:what], i[:date], @default_time_interval) } end end end class Numeric include Invoice::WhatMethods include Invoice::TimeMethods # Redefining the 'second' value adjusts all # the other methods to return the correct value also def second; self; end end class Hash def on(datetext) self[:date] = Time.parse(datetext) self end def costing(rate) self[:rate] = rate.class == String ? rate.to_f : rate self end end