paperless_to_xero 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/README.rdoc +5 -0
 - data/Rakefile +139 -0
 - data/bin/paperless_to_xero +30 -0
 - data/lib/paperless_to_xero.rb +2 -0
 - data/lib/paperless_to_xero/converter.rb +135 -0
 - data/lib/paperless_to_xero/decimal_helpers.rb +25 -0
 - data/lib/paperless_to_xero/invoice.rb +50 -0
 - data/lib/paperless_to_xero/invoice_item.rb +88 -0
 - data/lib/paperless_to_xero/version.rb +12 -0
 - data/spec/fixtures/end_to_end-input.csv +7 -0
 - data/spec/fixtures/end_to_end-output.csv +12 -0
 - data/spec/fixtures/multi-ex-vat.csv +2 -0
 - data/spec/fixtures/multi-foreign.csv +2 -0
 - data/spec/fixtures/multi-item.csv +2 -0
 - data/spec/fixtures/single-1000.csv +2 -0
 - data/spec/fixtures/single-basic.csv +2 -0
 - data/spec/fixtures/single-dkk.csv +2 -0
 - data/spec/fixtures/single-foreign.csv +2 -0
 - data/spec/fixtures/single-no-vat.csv +2 -0
 - data/spec/fixtures/single-zero_rated.csv +2 -0
 - data/spec/paperless_to_xero/converter_spec.rb +218 -0
 - data/spec/paperless_to_xero/invoice_item_spec.rb +162 -0
 - data/spec/paperless_to_xero/invoice_spec.rb +131 -0
 - data/spec/spec.opts +2 -0
 - data/spec/spec_helper.rb +26 -0
 - metadata +94 -0
 
    
        data/README.rdoc
    ADDED
    
    | 
         @@ -0,0 +1,5 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            = Paperless-to-Xero
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            A simple translator which takes a CSV file from Mariner's Paperless receipt/document management software and makes a Xero accounts payable invoice CSV, for import into Xero.
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            Formatting in Paperless is very important, so you probably want to wait until I've written the docs
         
     | 
    
        data/Rakefile
    ADDED
    
    | 
         @@ -0,0 +1,139 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'rake'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'rake/rdoctask'
         
     | 
| 
      
 3 
     | 
    
         
            +
            gem 'rspec'
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'spec/rake/spectask'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'lib/paperless_to_xero/version.rb'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            desc 'Generate documentation for Paperless to Xero.'
         
     | 
| 
      
 8 
     | 
    
         
            +
            Rake::RDocTask.new(:rdoc) do |rdoc|
         
     | 
| 
      
 9 
     | 
    
         
            +
              rdoc.rdoc_dir = 'rdoc'
         
     | 
| 
      
 10 
     | 
    
         
            +
              rdoc.title    = 'Paperless to Xero'
         
     | 
| 
      
 11 
     | 
    
         
            +
              rdoc.options << '--line-numbers' << '--inline-source'
         
     | 
| 
      
 12 
     | 
    
         
            +
              rdoc.rdoc_files.include('README.rdoc')
         
     | 
| 
      
 13 
     | 
    
         
            +
              rdoc.rdoc_files.include('lib/**/*.rb')
         
     | 
| 
      
 14 
     | 
    
         
            +
            end
         
     | 
| 
      
 15 
     | 
    
         
            +
             
     | 
| 
      
 16 
     | 
    
         
            +
            task :default => :spec
         
     | 
| 
      
 17 
     | 
    
         
            +
             
     | 
| 
      
 18 
     | 
    
         
            +
            desc "Run all specs in spec directory (excluding plugin specs)"
         
     | 
| 
      
 19 
     | 
    
         
            +
            Spec::Rake::SpecTask.new(:spec) do |t|
         
     | 
| 
      
 20 
     | 
    
         
            +
              t.spec_opts = ['--options', "\"#{Rake.original_dir}/spec/spec.opts\""]
         
     | 
| 
      
 21 
     | 
    
         
            +
              t.spec_files = FileList['spec/**/*_spec.rb']
         
     | 
| 
      
 22 
     | 
    
         
            +
            end
         
     | 
| 
      
 23 
     | 
    
         
            +
             
     | 
| 
      
 24 
     | 
    
         
            +
            namespace :spec do
         
     | 
| 
      
 25 
     | 
    
         
            +
              desc "Run all specs in spec directory with RCov (excluding plugin specs)"
         
     | 
| 
      
 26 
     | 
    
         
            +
              Spec::Rake::SpecTask.new(:rcov) do |t|
         
     | 
| 
      
 27 
     | 
    
         
            +
                t.spec_opts = ['--options', "\"#{Rake.original_dir}/spec/spec.opts\""]
         
     | 
| 
      
 28 
     | 
    
         
            +
                t.spec_files = FileList['spec/**/*_spec.rb']
         
     | 
| 
      
 29 
     | 
    
         
            +
                t.rcov = true
         
     | 
| 
      
 30 
     | 
    
         
            +
                t.rcov_opts = lambda do
         
     | 
| 
      
 31 
     | 
    
         
            +
                  IO.readlines("#{Rake.original_dir}/spec/rcov.opts").map {|l| l.chomp.split " "}.flatten
         
     | 
| 
      
 32 
     | 
    
         
            +
                end
         
     | 
| 
      
 33 
     | 
    
         
            +
              end
         
     | 
| 
      
 34 
     | 
    
         
            +
              
         
     | 
| 
      
 35 
     | 
    
         
            +
              desc "Print Specdoc for all specs (excluding plugin specs)"
         
     | 
| 
      
 36 
     | 
    
         
            +
              Spec::Rake::SpecTask.new(:doc) do |t|
         
     | 
| 
      
 37 
     | 
    
         
            +
                t.spec_opts = ["--format", "specdoc", "--dry-run"]
         
     | 
| 
      
 38 
     | 
    
         
            +
                t.spec_files = FileList['spec/**/*_spec.rb']
         
     | 
| 
      
 39 
     | 
    
         
            +
              end
         
     | 
| 
      
 40 
     | 
    
         
            +
            end
         
     | 
| 
      
 41 
     | 
    
         
            +
             
     | 
| 
      
 42 
     | 
    
         
            +
            require "rubygems"
         
     | 
| 
      
 43 
     | 
    
         
            +
            require "rake/gempackagetask"
         
     | 
| 
      
 44 
     | 
    
         
            +
             
     | 
| 
      
 45 
     | 
    
         
            +
            # This builds the actual gem. For details of what all these options
         
     | 
| 
      
 46 
     | 
    
         
            +
            # mean, and other ones you can add, check the documentation here:
         
     | 
| 
      
 47 
     | 
    
         
            +
            #
         
     | 
| 
      
 48 
     | 
    
         
            +
            #   http://rubygems.org/read/chapter/20
         
     | 
| 
      
 49 
     | 
    
         
            +
            #
         
     | 
| 
      
 50 
     | 
    
         
            +
            spec = Gem::Specification.new do |s|
         
     | 
| 
      
 51 
     | 
    
         
            +
              
         
     | 
| 
      
 52 
     | 
    
         
            +
              # Change these as appropriate
         
     | 
| 
      
 53 
     | 
    
         
            +
              s.name              = "paperless_to_xero"
         
     | 
| 
      
 54 
     | 
    
         
            +
              s.version           = PaperlessToXero::Version()
         
     | 
| 
      
 55 
     | 
    
         
            +
              s.summary           = "Convert Paperless CSV exports to Xero invoice import CSV"
         
     | 
| 
      
 56 
     | 
    
         
            +
              s.description       = File.read('README.rdoc')
         
     | 
| 
      
 57 
     | 
    
         
            +
              s.author            = "Matt Patterson"
         
     | 
| 
      
 58 
     | 
    
         
            +
              s.email             = "matt@reprocessed.org"
         
     | 
| 
      
 59 
     | 
    
         
            +
              s.homepage          = "http://reprocessed.org/"
         
     | 
| 
      
 60 
     | 
    
         
            +
             
     | 
| 
      
 61 
     | 
    
         
            +
              s.has_rdoc          = true
         
     | 
| 
      
 62 
     | 
    
         
            +
              s.extra_rdoc_files  = %w(README.rdoc)
         
     | 
| 
      
 63 
     | 
    
         
            +
              s.rdoc_options      = %w(--main README.rdoc)
         
     | 
| 
      
 64 
     | 
    
         
            +
             
     | 
| 
      
 65 
     | 
    
         
            +
              # Add any extra files to include in the gem
         
     | 
| 
      
 66 
     | 
    
         
            +
              s.files             = %w(Rakefile README.rdoc) + Dir.glob("{bin,spec,lib}/**/*")
         
     | 
| 
      
 67 
     | 
    
         
            +
              s.executables       = FileList["bin/**"].map { |f| File.basename(f) }
         
     | 
| 
      
 68 
     | 
    
         
            +
               
         
     | 
| 
      
 69 
     | 
    
         
            +
              s.require_paths     = ["lib"]
         
     | 
| 
      
 70 
     | 
    
         
            +
              
         
     | 
| 
      
 71 
     | 
    
         
            +
              # If you want to depend on other gems, add them here, along with any
         
     | 
| 
      
 72 
     | 
    
         
            +
              # relevant versions
         
     | 
| 
      
 73 
     | 
    
         
            +
              
         
     | 
| 
      
 74 
     | 
    
         
            +
              s.add_development_dependency("rspec") # add any other gems for testing/development
         
     | 
| 
      
 75 
     | 
    
         
            +
             
     | 
| 
      
 76 
     | 
    
         
            +
              # If you want to publish automatically to rubyforge, you'll may need
         
     | 
| 
      
 77 
     | 
    
         
            +
              # to tweak this, and the publishing task below too.
         
     | 
| 
      
 78 
     | 
    
         
            +
              s.rubyforge_project = "paperless_to_xero"
         
     | 
| 
      
 79 
     | 
    
         
            +
            end
         
     | 
| 
      
 80 
     | 
    
         
            +
             
     | 
| 
      
 81 
     | 
    
         
            +
            # This task actually builds the gem. We also regenerate a static 
         
     | 
| 
      
 82 
     | 
    
         
            +
            # .gemspec file, which is useful if something (i.e. GitHub) will
         
     | 
| 
      
 83 
     | 
    
         
            +
            # be automatically building a gem for this project. If you're not
         
     | 
| 
      
 84 
     | 
    
         
            +
            # using GitHub, edit as appropriate.
         
     | 
| 
      
 85 
     | 
    
         
            +
            Rake::GemPackageTask.new(spec) do |pkg|
         
     | 
| 
      
 86 
     | 
    
         
            +
              pkg.gem_spec = spec
         
     | 
| 
      
 87 
     | 
    
         
            +
              
         
     | 
| 
      
 88 
     | 
    
         
            +
              # Generate the gemspec file for github.
         
     | 
| 
      
 89 
     | 
    
         
            +
              file = File.dirname(__FILE__) + "/#{spec.name}.gemspec"
         
     | 
| 
      
 90 
     | 
    
         
            +
              File.open(file, "w") {|f| f << spec.to_ruby }
         
     | 
| 
      
 91 
     | 
    
         
            +
            end
         
     | 
| 
      
 92 
     | 
    
         
            +
             
     | 
| 
      
 93 
     | 
    
         
            +
            desc 'Clear out RDoc and generated packages'
         
     | 
| 
      
 94 
     | 
    
         
            +
            task :clean => [:clobber_rdoc, :clobber_package] do
         
     | 
| 
      
 95 
     | 
    
         
            +
              rm "#{spec.name}.gemspec"
         
     | 
| 
      
 96 
     | 
    
         
            +
            end
         
     | 
| 
      
 97 
     | 
    
         
            +
             
     | 
| 
      
 98 
     | 
    
         
            +
            # If you want to publish to RubyForge automatically, here's a simple 
         
     | 
| 
      
 99 
     | 
    
         
            +
            # task to help do that. If you don't, just get rid of this.
         
     | 
| 
      
 100 
     | 
    
         
            +
            # Be sure to set up your Rubyforge account details with the Rubyforge
         
     | 
| 
      
 101 
     | 
    
         
            +
            # gem; you'll need to run `rubyforge setup` and `rubyforge config` at
         
     | 
| 
      
 102 
     | 
    
         
            +
            # the very least.
         
     | 
| 
      
 103 
     | 
    
         
            +
            begin
         
     | 
| 
      
 104 
     | 
    
         
            +
              require "rake/contrib/sshpublisher"
         
     | 
| 
      
 105 
     | 
    
         
            +
              namespace :rubyforge do
         
     | 
| 
      
 106 
     | 
    
         
            +
                
         
     | 
| 
      
 107 
     | 
    
         
            +
                desc "Release gem and RDoc documentation to RubyForge"
         
     | 
| 
      
 108 
     | 
    
         
            +
                task :release => ["rubyforge:release:gem", "rubyforge:release:docs"]
         
     | 
| 
      
 109 
     | 
    
         
            +
                
         
     | 
| 
      
 110 
     | 
    
         
            +
                namespace :release do
         
     | 
| 
      
 111 
     | 
    
         
            +
                  desc "Release a new version of this gem"
         
     | 
| 
      
 112 
     | 
    
         
            +
                  task :gem => [:package] do
         
     | 
| 
      
 113 
     | 
    
         
            +
                    require 'rubyforge'
         
     | 
| 
      
 114 
     | 
    
         
            +
                    rubyforge = RubyForge.new
         
     | 
| 
      
 115 
     | 
    
         
            +
                    rubyforge.configure
         
     | 
| 
      
 116 
     | 
    
         
            +
                    rubyforge.login
         
     | 
| 
      
 117 
     | 
    
         
            +
                    rubyforge.userconfig['release_notes'] = spec.summary
         
     | 
| 
      
 118 
     | 
    
         
            +
                    path_to_gem = File.join(File.dirname(__FILE__), "pkg", "#{spec.name}-#{spec.version}.gem")
         
     | 
| 
      
 119 
     | 
    
         
            +
                    puts "Publishing #{spec.name}-#{spec.version.to_s} to Rubyforge..."
         
     | 
| 
      
 120 
     | 
    
         
            +
                    rubyforge.add_release(spec.rubyforge_project, spec.name, spec.version.to_s, path_to_gem)
         
     | 
| 
      
 121 
     | 
    
         
            +
                  end
         
     | 
| 
      
 122 
     | 
    
         
            +
                  
         
     | 
| 
      
 123 
     | 
    
         
            +
                  desc "Publish RDoc to RubyForge."
         
     | 
| 
      
 124 
     | 
    
         
            +
                  task :docs => [:rdoc] do
         
     | 
| 
      
 125 
     | 
    
         
            +
                    config = YAML.load(
         
     | 
| 
      
 126 
     | 
    
         
            +
                        File.read(File.expand_path('~/.rubyforge/user-config.yml'))
         
     | 
| 
      
 127 
     | 
    
         
            +
                    )
         
     | 
| 
      
 128 
     | 
    
         
            +
                    
         
     | 
| 
      
 129 
     | 
    
         
            +
                    host = "#{config['username']}@rubyforge.org"
         
     | 
| 
      
 130 
     | 
    
         
            +
                    remote_dir = "/var/www/gforge-projects/coop_to_ofx/" # Should be the same as the rubyforge project name
         
     | 
| 
      
 131 
     | 
    
         
            +
                    local_dir = 'rdoc'
         
     | 
| 
      
 132 
     | 
    
         
            +
                    
         
     | 
| 
      
 133 
     | 
    
         
            +
                    Rake::SshDirPublisher.new(host, remote_dir, local_dir).upload
         
     | 
| 
      
 134 
     | 
    
         
            +
                  end
         
     | 
| 
      
 135 
     | 
    
         
            +
                end
         
     | 
| 
      
 136 
     | 
    
         
            +
              end
         
     | 
| 
      
 137 
     | 
    
         
            +
            rescue LoadError
         
     | 
| 
      
 138 
     | 
    
         
            +
              puts "Rake SshDirPublisher is unavailable or your rubyforge environment is not configured."
         
     | 
| 
      
 139 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,30 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            #!/usr/bin/env ruby
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            $:.push(File.expand_path(File.dirname(__FILE__) + '/../lib'))
         
     | 
| 
      
 4 
     | 
    
         
            +
            require 'optparse'
         
     | 
| 
      
 5 
     | 
    
         
            +
            require 'paperless_to_xero'
         
     | 
| 
      
 6 
     | 
    
         
            +
             
     | 
| 
      
 7 
     | 
    
         
            +
            OptionParser.new do |opts|
         
     | 
| 
      
 8 
     | 
    
         
            +
              opts.banner = "Usage: paperless_to_xero [opts] /path/to/input.csv /path/to/output.csv"
         
     | 
| 
      
 9 
     | 
    
         
            +
              opts.separator ""
         
     | 
| 
      
 10 
     | 
    
         
            +
              opts.separator "Common options:"
         
     | 
| 
      
 11 
     | 
    
         
            +
              
         
     | 
| 
      
 12 
     | 
    
         
            +
              opts.on_tail("-h", "--help", "Show this message") do
         
     | 
| 
      
 13 
     | 
    
         
            +
                puts opts
         
     | 
| 
      
 14 
     | 
    
         
            +
                exit
         
     | 
| 
      
 15 
     | 
    
         
            +
              end
         
     | 
| 
      
 16 
     | 
    
         
            +
              
         
     | 
| 
      
 17 
     | 
    
         
            +
              opts.on_tail("--version", "Show version") do
         
     | 
| 
      
 18 
     | 
    
         
            +
                require 'paperless_to_xero/version'
         
     | 
| 
      
 19 
     | 
    
         
            +
                puts PaperlessToXero::Version()
         
     | 
| 
      
 20 
     | 
    
         
            +
                puts "Copyright (c) 2009, Matt Patterson. Released under the MIT license"
         
     | 
| 
      
 21 
     | 
    
         
            +
                exit
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
            end.parse!
         
     | 
| 
      
 24 
     | 
    
         
            +
             
     | 
| 
      
 25 
     | 
    
         
            +
            input = ARGV[0]
         
     | 
| 
      
 26 
     | 
    
         
            +
            output = ARGV[1]
         
     | 
| 
      
 27 
     | 
    
         
            +
             
     | 
| 
      
 28 
     | 
    
         
            +
            converter = PaperlessToXero::Converter.new(input, output)
         
     | 
| 
      
 29 
     | 
    
         
            +
            converter.convert!
         
     | 
| 
      
 30 
     | 
    
         
            +
             
     | 
| 
         @@ -0,0 +1,135 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'csv'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'date'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            module PaperlessToXero
         
     | 
| 
      
 5 
     | 
    
         
            +
              class Converter
         
     | 
| 
      
 6 
     | 
    
         
            +
                attr_reader :input_path, :output_path
         
     | 
| 
      
 7 
     | 
    
         
            +
                
         
     | 
| 
      
 8 
     | 
    
         
            +
                def initialize(input_path, output_path)
         
     | 
| 
      
 9 
     | 
    
         
            +
                  @input_path, @output_path = input_path, output_path
         
     | 
| 
      
 10 
     | 
    
         
            +
                end
         
     | 
| 
      
 11 
     | 
    
         
            +
                
         
     | 
| 
      
 12 
     | 
    
         
            +
                def invoices
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @invoices ||= []
         
     | 
| 
      
 14 
     | 
    
         
            +
                end
         
     | 
| 
      
 15 
     | 
    
         
            +
                
         
     | 
| 
      
 16 
     | 
    
         
            +
                def parse
         
     | 
| 
      
 17 
     | 
    
         
            +
                  input_csv = CSV.read(input_path)
         
     | 
| 
      
 18 
     | 
    
         
            +
                  # remove Paperless header row
         
     | 
| 
      
 19 
     | 
    
         
            +
                  input_csv.shift
         
     | 
| 
      
 20 
     | 
    
         
            +
                  
         
     | 
| 
      
 21 
     | 
    
         
            +
                  input_csv.each do |row|
         
     | 
| 
      
 22 
     | 
    
         
            +
                    date, merchant, paperless_currency, amount, vat, category, payment_method, notes_field, description, reference, status, *extras = row
         
     | 
| 
      
 23 
     | 
    
         
            +
                    negative = amount.index('--') == 0
         
     | 
| 
      
 24 
     | 
    
         
            +
                    category = category[0..2] unless category.nil?
         
     | 
| 
      
 25 
     | 
    
         
            +
                    unless negative # negative stuff ought to be a credit note. not sure if that works...
         
     | 
| 
      
 26 
     | 
    
         
            +
                      # process amounts for commas added by Paperless
         
     | 
| 
      
 27 
     | 
    
         
            +
                      amount = amount.tr(',', '') unless amount.nil?
         
     | 
| 
      
 28 
     | 
    
         
            +
                      vat = vat.tr(',', '') unless vat.nil?
         
     | 
| 
      
 29 
     | 
    
         
            +
                      notes = extract_notes(notes_field)
         
     | 
| 
      
 30 
     | 
    
         
            +
                      total_vat = vat.nil? ? "0.00" : vat
         
     | 
| 
      
 31 
     | 
    
         
            +
                      invoice = PaperlessToXero::Invoice.new(extract_date(date), merchant, reference, amount, total_vat, inc_vat?(notes), extract_currency(notes))
         
     | 
| 
      
 32 
     | 
    
         
            +
                      if extras.empty?
         
     | 
| 
      
 33 
     | 
    
         
            +
                        invoice.add_item(description, amount, vat, category, extract_vat_note(vat, notes))
         
     | 
| 
      
 34 
     | 
    
         
            +
                      else
         
     | 
| 
      
 35 
     | 
    
         
            +
                        raise RangeError, "input CSV row is badly formatted" unless extras.size % 6 == 0
         
     | 
| 
      
 36 
     | 
    
         
            +
                        items = chunk_extras(extras)
         
     | 
| 
      
 37 
     | 
    
         
            +
                        items.each do |item|
         
     | 
| 
      
 38 
     | 
    
         
            +
                          description, paperless_currency, amount, unknown, category, notes_field = item
         
     | 
| 
      
 39 
     | 
    
         
            +
                          category = category[0..2]
         
     | 
| 
      
 40 
     | 
    
         
            +
                          notes = extract_notes(notes_field)
         
     | 
| 
      
 41 
     | 
    
         
            +
                          vat_amount = extract_vat_amount(notes)
         
     | 
| 
      
 42 
     | 
    
         
            +
                          vat_note = extract_vat_note(vat_amount, notes)
         
     | 
| 
      
 43 
     | 
    
         
            +
                          invoice.add_item(description, amount, vat_amount, category, vat_note)
         
     | 
| 
      
 44 
     | 
    
         
            +
                        end
         
     | 
| 
      
 45 
     | 
    
         
            +
                      end
         
     | 
| 
      
 46 
     | 
    
         
            +
                      invoices << invoice
         
     | 
| 
      
 47 
     | 
    
         
            +
                      
         
     | 
| 
      
 48 
     | 
    
         
            +
                      # currency fudging
         
     | 
| 
      
 49 
     | 
    
         
            +
                      # actual_currency_match = notes.nil? ? nil : notes.match(/(\$|€|DKK|USD|EUR)/)
         
     | 
| 
      
 50 
     | 
    
         
            +
                      # actual_currency = actual_currency_match.nil? ? nil : actual_currency_match[1]
         
     | 
| 
      
 51 
     | 
    
         
            +
                      # 
         
     | 
| 
      
 52 
     | 
    
         
            +
                      # description = description + " (#{actual_currency})" unless actual_currency.nil?
         
     | 
| 
      
 53 
     | 
    
         
            +
                    end
         
     | 
| 
      
 54 
     | 
    
         
            +
                  end
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
                
         
     | 
| 
      
 57 
     | 
    
         
            +
                def convert!
         
     | 
| 
      
 58 
     | 
    
         
            +
                  # grab the input
         
     | 
| 
      
 59 
     | 
    
         
            +
                  parse
         
     | 
| 
      
 60 
     | 
    
         
            +
                  # open the output CSV
         
     | 
| 
      
 61 
     | 
    
         
            +
                  CSV.open(output_path, 'w') do |writer|
         
     | 
| 
      
 62 
     | 
    
         
            +
                    # Xero header row
         
     | 
| 
      
 63 
     | 
    
         
            +
                    writer << ['ContactName','InvoiceNumber','InvoiceDate','DueDate','SubTotal',
         
     | 
| 
      
 64 
     | 
    
         
            +
                               'TotalTax','Total','Description','Quantity','UnitAmount','AccountCode','TaxType','TaxAmount',
         
     | 
| 
      
 65 
     | 
    
         
            +
                               'TrackingName1','TrackingOption1','TrackingName2','TrackingOption2']
         
     | 
| 
      
 66 
     | 
    
         
            +
                    
         
     | 
| 
      
 67 
     | 
    
         
            +
                    # body rows
         
     | 
| 
      
 68 
     | 
    
         
            +
                    invoices.each do |invoice|
         
     | 
| 
      
 69 
     | 
    
         
            +
                      invoice.serialise_to_csv(writer)
         
     | 
| 
      
 70 
     | 
    
         
            +
                    end
         
     | 
| 
      
 71 
     | 
    
         
            +
                  end
         
     | 
| 
      
 72 
     | 
    
         
            +
                end
         
     | 
| 
      
 73 
     | 
    
         
            +
                
         
     | 
| 
      
 74 
     | 
    
         
            +
                private
         
     | 
| 
      
 75 
     | 
    
         
            +
                
         
     | 
| 
      
 76 
     | 
    
         
            +
                def inc_vat?(notes)
         
     | 
| 
      
 77 
     | 
    
         
            +
                  notes.each do |item|
         
     | 
| 
      
 78 
     | 
    
         
            +
                    return false if item.match(/^Ex[ -]?VAT$/i)
         
     | 
| 
      
 79 
     | 
    
         
            +
                  end
         
     | 
| 
      
 80 
     | 
    
         
            +
                  true
         
     | 
| 
      
 81 
     | 
    
         
            +
                end
         
     | 
| 
      
 82 
     | 
    
         
            +
                
         
     | 
| 
      
 83 
     | 
    
         
            +
                def extract_date(date_string)
         
     | 
| 
      
 84 
     | 
    
         
            +
                  ds, day, month, year = date_string.match(/([0-9]{2})\/([0-9]{2})\/([0-9]{4})/).to_a
         
     | 
| 
      
 85 
     | 
    
         
            +
                  Date.parse("#{year}-#{month}-#{day}")
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
                
         
     | 
| 
      
 88 
     | 
    
         
            +
                def chunk_extras(extras)
         
     | 
| 
      
 89 
     | 
    
         
            +
                  duped_extras = extras.dup
         
     | 
| 
      
 90 
     | 
    
         
            +
                  (1..(extras.size / 6)).inject([]) do |chunked, i|
         
     | 
| 
      
 91 
     | 
    
         
            +
                    chunked << duped_extras.slice!(0..5)
         
     | 
| 
      
 92 
     | 
    
         
            +
                  end
         
     | 
| 
      
 93 
     | 
    
         
            +
                end
         
     | 
| 
      
 94 
     | 
    
         
            +
                
         
     | 
| 
      
 95 
     | 
    
         
            +
                def extract_notes(notes_field)
         
     | 
| 
      
 96 
     | 
    
         
            +
                  notes = notes_field.nil? ? [] : notes_field.split(';')
         
     | 
| 
      
 97 
     | 
    
         
            +
                  notes.collect { |item| item.strip }
         
     | 
| 
      
 98 
     | 
    
         
            +
                end
         
     | 
| 
      
 99 
     | 
    
         
            +
                
         
     | 
| 
      
 100 
     | 
    
         
            +
                def extract_currency(notes)
         
     | 
| 
      
 101 
     | 
    
         
            +
                  notes.each do |item|
         
     | 
| 
      
 102 
     | 
    
         
            +
                    return item if item.match(/^[A-Z]{3}$/)
         
     | 
| 
      
 103 
     | 
    
         
            +
                    case item
         
     | 
| 
      
 104 
     | 
    
         
            +
                    when "€"
         
     | 
| 
      
 105 
     | 
    
         
            +
                      return "EUR"
         
     | 
| 
      
 106 
     | 
    
         
            +
                    when "$"
         
     | 
| 
      
 107 
     | 
    
         
            +
                      return "USD"
         
     | 
| 
      
 108 
     | 
    
         
            +
                    end
         
     | 
| 
      
 109 
     | 
    
         
            +
                  end
         
     | 
| 
      
 110 
     | 
    
         
            +
                  "GBP"
         
     | 
| 
      
 111 
     | 
    
         
            +
                end
         
     | 
| 
      
 112 
     | 
    
         
            +
                
         
     | 
| 
      
 113 
     | 
    
         
            +
                def extract_vat_amount(notes)
         
     | 
| 
      
 114 
     | 
    
         
            +
                  notes.each do |item|
         
     | 
| 
      
 115 
     | 
    
         
            +
                    return item if item.match(/^[0-9]+\.[0-9]{1,2}$/)
         
     | 
| 
      
 116 
     | 
    
         
            +
                  end
         
     | 
| 
      
 117 
     | 
    
         
            +
                  nil
         
     | 
| 
      
 118 
     | 
    
         
            +
                end
         
     | 
| 
      
 119 
     | 
    
         
            +
                
         
     | 
| 
      
 120 
     | 
    
         
            +
                def extract_vat_note(vat_amount, notes)
         
     | 
| 
      
 121 
     | 
    
         
            +
                  notes.each do |item|
         
     | 
| 
      
 122 
     | 
    
         
            +
                    return item if item.match(/^VAT/)
         
     | 
| 
      
 123 
     | 
    
         
            +
                  end
         
     | 
| 
      
 124 
     | 
    
         
            +
                  
         
     | 
| 
      
 125 
     | 
    
         
            +
                  case vat_amount
         
     | 
| 
      
 126 
     | 
    
         
            +
                  when "0.00"
         
     | 
| 
      
 127 
     | 
    
         
            +
                    'VAT - 0%'
         
     | 
| 
      
 128 
     | 
    
         
            +
                  when nil
         
     | 
| 
      
 129 
     | 
    
         
            +
                    'No VAT'
         
     | 
| 
      
 130 
     | 
    
         
            +
                  else
         
     | 
| 
      
 131 
     | 
    
         
            +
                    'VAT - 15%'
         
     | 
| 
      
 132 
     | 
    
         
            +
                  end
         
     | 
| 
      
 133 
     | 
    
         
            +
                end
         
     | 
| 
      
 134 
     | 
    
         
            +
              end
         
     | 
| 
      
 135 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,25 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module PaperlessToXero
         
     | 
| 
      
 2 
     | 
    
         
            +
              module DecimalHelpers
         
     | 
| 
      
 3 
     | 
    
         
            +
                def amounts_when_vat_inclusive(decimal_inc_vat_amount, decimal_vat_amount)
         
     | 
| 
      
 4 
     | 
    
         
            +
                  vat_inclusive_amount = formatted_decimal(decimal_inc_vat_amount)
         
     | 
| 
      
 5 
     | 
    
         
            +
                  vat_amount = formatted_decimal(decimal_vat_amount)
         
     | 
| 
      
 6 
     | 
    
         
            +
                  decimal_ex_vat_amount = decimal_inc_vat_amount - decimal_vat_amount
         
     | 
| 
      
 7 
     | 
    
         
            +
                  vat_exclusive_amount = formatted_decimal(decimal_ex_vat_amount)
         
     | 
| 
      
 8 
     | 
    
         
            +
                  [vat_exclusive_amount, vat_amount, vat_inclusive_amount]
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
                
         
     | 
| 
      
 11 
     | 
    
         
            +
                def amounts_when_vat_exclusive(decimal_ex_vat_amount, decimal_vat_amount)
         
     | 
| 
      
 12 
     | 
    
         
            +
                  vat_exclusive_amount = formatted_decimal(decimal_ex_vat_amount)
         
     | 
| 
      
 13 
     | 
    
         
            +
                  vat_amount = formatted_decimal(decimal_vat_amount)
         
     | 
| 
      
 14 
     | 
    
         
            +
                  decimal_inc_vat_amount = decimal_ex_vat_amount + decimal_vat_amount
         
     | 
| 
      
 15 
     | 
    
         
            +
                  vat_inclusive_amount = formatted_decimal(decimal_inc_vat_amount)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  [vat_exclusive_amount, vat_amount, vat_inclusive_amount]
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
                
         
     | 
| 
      
 19 
     | 
    
         
            +
                def formatted_decimal(value)
         
     | 
| 
      
 20 
     | 
    
         
            +
                  value = value.to_s('F')
         
     | 
| 
      
 21 
     | 
    
         
            +
                  value = value + '0' unless value.index('.') < value.size - 2
         
     | 
| 
      
 22 
     | 
    
         
            +
                  value
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
              end
         
     | 
| 
      
 25 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,50 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'bigdecimal'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'paperless_to_xero/decimal_helpers'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'paperless_to_xero/invoice_item'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module PaperlessToXero
         
     | 
| 
      
 6 
     | 
    
         
            +
              class Invoice
         
     | 
| 
      
 7 
     | 
    
         
            +
                include PaperlessToXero::DecimalHelpers
         
     | 
| 
      
 8 
     | 
    
         
            +
                
         
     | 
| 
      
 9 
     | 
    
         
            +
                attr_reader :date, :merchant, :reference_id, :currency, :total, :ex_vat_total, :inc_vat_total, :vat_total
         
     | 
| 
      
 10 
     | 
    
         
            +
                
         
     | 
| 
      
 11 
     | 
    
         
            +
                def initialize(date, merchant, reference_id, total, vat, vat_inclusive = true, currency = 'GBP')
         
     | 
| 
      
 12 
     | 
    
         
            +
                  @date, @merchant, @reference_id = date, merchant, reference_id
         
     | 
| 
      
 13 
     | 
    
         
            +
                  @total, @vat_inclusive, @currency = total, vat_inclusive, currency
         
     | 
| 
      
 14 
     | 
    
         
            +
                  decimal_total = BigDecimal.new(total)
         
     | 
| 
      
 15 
     | 
    
         
            +
                  decimal_vat = BigDecimal.new(vat)
         
     | 
| 
      
 16 
     | 
    
         
            +
                  @ex_vat_total, @vat_total, @inc_vat_total = amounts_when_vat_inclusive(decimal_total, decimal_vat)
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
                
         
     | 
| 
      
 19 
     | 
    
         
            +
                def items
         
     | 
| 
      
 20 
     | 
    
         
            +
                  @items ||= []
         
     | 
| 
      
 21 
     | 
    
         
            +
                end
         
     | 
| 
      
 22 
     | 
    
         
            +
                
         
     | 
| 
      
 23 
     | 
    
         
            +
                def vat_inclusive?
         
     | 
| 
      
 24 
     | 
    
         
            +
                  @vat_inclusive
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
                
         
     | 
| 
      
 27 
     | 
    
         
            +
                def add_item(description, amount, vat_amount, category, vat_note)
         
     | 
| 
      
 28 
     | 
    
         
            +
                  items << PaperlessToXero::InvoiceItem.new(description, amount, vat_amount, category, vat_note, @vat_inclusive)
         
     | 
| 
      
 29 
     | 
    
         
            +
                end
         
     | 
| 
      
 30 
     | 
    
         
            +
                
         
     | 
| 
      
 31 
     | 
    
         
            +
                def serialise_to_csv(csv)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  serialising_items = items.dup
         
     | 
| 
      
 33 
     | 
    
         
            +
                  first_item = serialising_items.shift
         
     | 
| 
      
 34 
     | 
    
         
            +
                  
         
     | 
| 
      
 35 
     | 
    
         
            +
                  marked_merchant = currency != 'GBP' ? merchant + " (#{currency})" : merchant
         
     | 
| 
      
 36 
     | 
    
         
            +
                  unless first_item.nil?
         
     | 
| 
      
 37 
     | 
    
         
            +
                    csv << [marked_merchant, reference_id, date.strftime('%d/%m/%Y'), date.strftime('%d/%m/%Y'), 
         
     | 
| 
      
 38 
     | 
    
         
            +
                            ex_vat_total, vat_total, inc_vat_total, first_item.description, '1', 
         
     | 
| 
      
 39 
     | 
    
         
            +
                            first_item.vat_exclusive_amount, first_item.category, first_item.vat_type, first_item.vat_amount, 
         
     | 
| 
      
 40 
     | 
    
         
            +
                            nil, nil, nil, nil]
         
     | 
| 
      
 41 
     | 
    
         
            +
                  end
         
     | 
| 
      
 42 
     | 
    
         
            +
                  serialising_items.each do |item|
         
     | 
| 
      
 43 
     | 
    
         
            +
                    csv << [nil, reference_id, nil, nil, 
         
     | 
| 
      
 44 
     | 
    
         
            +
                            nil, nil, nil, item.description, '1', 
         
     | 
| 
      
 45 
     | 
    
         
            +
                            item.vat_exclusive_amount, item.category, item.vat_type, item.vat_amount, 
         
     | 
| 
      
 46 
     | 
    
         
            +
                            nil, nil, nil, nil]
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
              end
         
     | 
| 
      
 50 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,88 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'bigdecimal'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'bigdecimal/util'
         
     | 
| 
      
 3 
     | 
    
         
            +
            require 'paperless_to_xero/decimal_helpers'
         
     | 
| 
      
 4 
     | 
    
         
            +
             
     | 
| 
      
 5 
     | 
    
         
            +
            module PaperlessToXero
         
     | 
| 
      
 6 
     | 
    
         
            +
              class InvoiceItem
         
     | 
| 
      
 7 
     | 
    
         
            +
                include PaperlessToXero::DecimalHelpers
         
     | 
| 
      
 8 
     | 
    
         
            +
                
         
     | 
| 
      
 9 
     | 
    
         
            +
                class << self
         
     | 
| 
      
 10 
     | 
    
         
            +
                  def fetch_vat_rate(vat_type)
         
     | 
| 
      
 11 
     | 
    
         
            +
                    @vat_rates ||= {
         
     | 
| 
      
 12 
     | 
    
         
            +
                      '5.5% (France, VAT on expenses)' => 1.055.to_d,
         
     | 
| 
      
 13 
     | 
    
         
            +
                      '19.6% (France, VAT on expenses)' => 1.196.to_d,
         
     | 
| 
      
 14 
     | 
    
         
            +
                      '7% (Germany, VAT on expenses)' => 1.07.to_d,
         
     | 
| 
      
 15 
     | 
    
         
            +
                      '19% (Germany, VAT on expenses)' => 1.19.to_d,
         
     | 
| 
      
 16 
     | 
    
         
            +
                      '25% (Denmark, VAT on expenses)' => 1.25.to_d,
         
     | 
| 
      
 17 
     | 
    
         
            +
                      '21.5% (Ireland, VAT on expenses)' => 1.215.to_d,
         
     | 
| 
      
 18 
     | 
    
         
            +
                      '15% (EU VAT ID)' => 1.15.to_d,
         
     | 
| 
      
 19 
     | 
    
         
            +
                      '15% (VAT on expenses)' => 1.15.to_d,
         
     | 
| 
      
 20 
     | 
    
         
            +
                      'Zero Rated Expenses' => 0,
         
     | 
| 
      
 21 
     | 
    
         
            +
                      '15% (Luxembourg, VAT on expenses)' => 1.15.to_d
         
     | 
| 
      
 22 
     | 
    
         
            +
                    }
         
     | 
| 
      
 23 
     | 
    
         
            +
                    @vat_rates[vat_type]
         
     | 
| 
      
 24 
     | 
    
         
            +
                  end
         
     | 
| 
      
 25 
     | 
    
         
            +
                end
         
     | 
| 
      
 26 
     | 
    
         
            +
                
         
     | 
| 
      
 27 
     | 
    
         
            +
                attr_reader :description, :amount, :category, :vat_inclusive, :vat_inclusive_amount, :vat_exclusive_amount, :vat_amount, :vat_type
         
     | 
| 
      
 28 
     | 
    
         
            +
                
         
     | 
| 
      
 29 
     | 
    
         
            +
                def initialize(description, amount, vat_amount, category, vat_note = 'VAT - 15%', vat_inclusive = true)
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @amount, @vat_amount, @description, @category, @vat_inclusive = amount, vat_amount, description, category, vat_inclusive
         
     | 
| 
      
 31 
     | 
    
         
            +
                  @vat_type = extract_vat_type(vat_note)
         
     | 
| 
      
 32 
     | 
    
         
            +
                  
         
     | 
| 
      
 33 
     | 
    
         
            +
                  vat_rate = fetch_vat_rate(vat_type)
         
     | 
| 
      
 34 
     | 
    
         
            +
                  case vat_rate
         
     | 
| 
      
 35 
     | 
    
         
            +
                  when BigDecimal
         
     | 
| 
      
 36 
     | 
    
         
            +
                    decimal_amount = BigDecimal.new(@amount)
         
     | 
| 
      
 37 
     | 
    
         
            +
                    decimal_vat_amount = @vat_amount.nil? ? nil : BigDecimal.new(@vat_amount)
         
     | 
| 
      
 38 
     | 
    
         
            +
                    amounts_method = vat_inclusive ? :amounts_when_vat_inclusive : :amounts_when_vat_exclusive
         
     | 
| 
      
 39 
     | 
    
         
            +
                    @vat_exclusive_amount, @vat_amount, @vat_inclusive_amount = self.send(amounts_method, decimal_amount, decimal_vat_amount)
         
     | 
| 
      
 40 
     | 
    
         
            +
                  else
         
     | 
| 
      
 41 
     | 
    
         
            +
                    amounts_when_zero_rated_or_non_vat(vat_rate)
         
     | 
| 
      
 42 
     | 
    
         
            +
                  end
         
     | 
| 
      
 43 
     | 
    
         
            +
                end
         
     | 
| 
      
 44 
     | 
    
         
            +
                
         
     | 
| 
      
 45 
     | 
    
         
            +
                private
         
     | 
| 
      
 46 
     | 
    
         
            +
                
         
     | 
| 
      
 47 
     | 
    
         
            +
                def amounts_when_zero_rated_or_non_vat(vat_rate)
         
     | 
| 
      
 48 
     | 
    
         
            +
                  @vat_inclusive_amount = @amount
         
     | 
| 
      
 49 
     | 
    
         
            +
                  @vat_exclusive_amount = @amount
         
     | 
| 
      
 50 
     | 
    
         
            +
                  @vat_amount = vat_rate.nil? ? nil : "0.00"
         
     | 
| 
      
 51 
     | 
    
         
            +
                end
         
     | 
| 
      
 52 
     | 
    
         
            +
                
         
     | 
| 
      
 53 
     | 
    
         
            +
                def fetch_vat_rate(vat_type)
         
     | 
| 
      
 54 
     | 
    
         
            +
                  self.class.fetch_vat_rate(vat_type)
         
     | 
| 
      
 55 
     | 
    
         
            +
                end
         
     | 
| 
      
 56 
     | 
    
         
            +
                
         
     | 
| 
      
 57 
     | 
    
         
            +
                def extract_vat_type(vat_note)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  case vat_note
         
     | 
| 
      
 59 
     | 
    
         
            +
                  when 'VAT - France - 5.5%'
         
     | 
| 
      
 60 
     | 
    
         
            +
                    '5.5% (France, VAT on expenses)'
         
     | 
| 
      
 61 
     | 
    
         
            +
                  when /Fr/
         
     | 
| 
      
 62 
     | 
    
         
            +
                    '19.6% (France, VAT on expenses)'
         
     | 
| 
      
 63 
     | 
    
         
            +
                  when 'VAT - Germany - 7%'
         
     | 
| 
      
 64 
     | 
    
         
            +
                    '7% (Germany, VAT on expenses)'
         
     | 
| 
      
 65 
     | 
    
         
            +
                  when /Germany/
         
     | 
| 
      
 66 
     | 
    
         
            +
                    '19% (Germany, VAT on expenses)'
         
     | 
| 
      
 67 
     | 
    
         
            +
                  when /Den/
         
     | 
| 
      
 68 
     | 
    
         
            +
                    '25% (Denmark, VAT on expenses)'
         
     | 
| 
      
 69 
     | 
    
         
            +
                  when /Irel/
         
     | 
| 
      
 70 
     | 
    
         
            +
                    '21.5% (Ireland, VAT on expenses)'
         
     | 
| 
      
 71 
     | 
    
         
            +
                  when /Sweden/
         
     | 
| 
      
 72 
     | 
    
         
            +
                    '25% (Sweden, VAT on expenses)'
         
     | 
| 
      
 73 
     | 
    
         
            +
                  when /Lux/
         
     | 
| 
      
 74 
     | 
    
         
            +
                    '15% (Luxembourg, VAT on expenses)'
         
     | 
| 
      
 75 
     | 
    
         
            +
                  when /VAT - EU/
         
     | 
| 
      
 76 
     | 
    
         
            +
                    '15% (EU VAT ID)'
         
     | 
| 
      
 77 
     | 
    
         
            +
                  when 'VAT - 15%'
         
     | 
| 
      
 78 
     | 
    
         
            +
                    '15% (VAT on expenses)'
         
     | 
| 
      
 79 
     | 
    
         
            +
                  when 'VAT - 0%'
         
     | 
| 
      
 80 
     | 
    
         
            +
                    'Zero Rated Expenses'
         
     | 
| 
      
 81 
     | 
    
         
            +
                  when 'No VAT'
         
     | 
| 
      
 82 
     | 
    
         
            +
                    'No VAT'
         
     | 
| 
      
 83 
     | 
    
         
            +
                  when 'VAT'
         
     | 
| 
      
 84 
     | 
    
         
            +
                    '15% (VAT on expenses)'
         
     | 
| 
      
 85 
     | 
    
         
            +
                  end
         
     | 
| 
      
 86 
     | 
    
         
            +
                end
         
     | 
| 
      
 87 
     | 
    
         
            +
              end
         
     | 
| 
      
 88 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,12 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            module PaperlessToXero
         
     | 
| 
      
 2 
     | 
    
         
            +
              def self.Version
         
     | 
| 
      
 3 
     | 
    
         
            +
                PaperlessToXero::Version::FULL
         
     | 
| 
      
 4 
     | 
    
         
            +
              end
         
     | 
| 
      
 5 
     | 
    
         
            +
              
         
     | 
| 
      
 6 
     | 
    
         
            +
              module Version
         
     | 
| 
      
 7 
     | 
    
         
            +
                MAJOR = 1
         
     | 
| 
      
 8 
     | 
    
         
            +
                MINOR = 1
         
     | 
| 
      
 9 
     | 
    
         
            +
                POINT = 1
         
     | 
| 
      
 10 
     | 
    
         
            +
                FULL = [PaperlessToXero::Version::MAJOR, PaperlessToXero::Version::MINOR, PaperlessToXero::Version::POINT].join('.')
         
     | 
| 
      
 11 
     | 
    
         
            +
              end
         
     | 
| 
      
 12 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,7 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            "Date","Merchant","Currency","Amount","Tax","Category","Payment Method","Notes","Description","Reference #","Status"
         
     | 
| 
      
 2 
     | 
    
         
            +
            22/06/2009,"Geberer","£","2.80","0.33","494 - Travel International","Cash","€ ","Food & Coffee","2009-06-22-02",,"Coffee","£","1.50",,"494 - Travel International","0.24; VAT - Germany - 19%","Food","£","1.30",,"494 - Travel International","0.09; VAT - Germany - 7%"
         
     | 
| 
      
 3 
     | 
    
         
            +
            29/05/2009,"FDIH","£","250.00","50.00","480 - Staff Training","Credit Card","€; VAT - Denmark - 25%","Reboot 11 ticket","2009-05-29-02",
         
     | 
| 
      
 4 
     | 
    
         
            +
            18/05/2009,"Apple Store, Regent Street","£","617.95","80.60",,"Debit Card",,"Mac Mini, iWork, VMWare Fusion","2009-05-18-09",,"Mac Mini","£","499.00",,"720 - Computer Equipment","65.09; VAT - 15%","iWork 09","£","70.00",,"463 - IT Software and Consumables","9.13; VAT - 15%","VMWare Fusion","£","48.95",,"463 - IT Software and Consumables","6.38; VAT - 15%"
         
     | 
| 
      
 5 
     | 
    
         
            +
            18/05/2009,"Apple Store, Regent Street","£","14.95","1.95","429 - General Expenses","Debit Card",,"Phone case","2009-05-18-05",
         
     | 
| 
      
 6 
     | 
    
         
            +
            23/04/2009,"Rexel Senate","£","81.42","10.62","325 - Direct Expenses",,,"Cat 6 modules","2009-04-23-04",
         
     | 
| 
      
 7 
     | 
    
         
            +
            18/04/2009,"Apple Store, Regent Street","£","617.95","80.60",,"Debit Card","Ex VAT","Mac Mini, iWork, VMWare Fusion","2009-04-18-09",,"Mac Mini","£","433.91",,"720 - Computer Equipment","65.09; VAT - 15%","iWork 09","£","60.87",,"463 - IT Software and Consumables","9.13; VAT - 15%","VMWare Fusion","£","42.57",,"463 - IT Software and Consumables","6.38; VAT - 15%"
         
     | 
| 
         @@ -0,0 +1,12 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            ContactName,InvoiceNumber,InvoiceDate,DueDate,SubTotal,TotalTax,Total,Description,Quantity,UnitAmount,AccountCode,TaxType,TaxAmount,TrackingName1,TrackingOption1,TrackingName2,TrackingOption2
         
     | 
| 
      
 2 
     | 
    
         
            +
            Geberer (EUR),2009-06-22-02,22/06/2009,22/06/2009,2.47,0.33,2.80,Coffee,1,1.26,494,"19% (Germany, VAT on expenses)",0.24,,,,
         
     | 
| 
      
 3 
     | 
    
         
            +
            ,2009-06-22-02,,,,,,Food,1,1.21,494,"7% (Germany, VAT on expenses)",0.09,,,,
         
     | 
| 
      
 4 
     | 
    
         
            +
            FDIH (EUR),2009-05-29-02,29/05/2009,29/05/2009,200.00,50.00,250.00,Reboot 11 ticket,1,200.00,480,"25% (Denmark, VAT on expenses)",50.00,,,,
         
     | 
| 
      
 5 
     | 
    
         
            +
            "Apple Store, Regent Street",2009-05-18-09,18/05/2009,18/05/2009,537.35,80.60,617.95,Mac Mini,1,433.91,720,15% (VAT on expenses),65.09,,,,
         
     | 
| 
      
 6 
     | 
    
         
            +
            ,2009-05-18-09,,,,,,iWork 09,1,60.87,463,15% (VAT on expenses),9.13,,,,
         
     | 
| 
      
 7 
     | 
    
         
            +
            ,2009-05-18-09,,,,,,VMWare Fusion,1,42.57,463,15% (VAT on expenses),6.38,,,,
         
     | 
| 
      
 8 
     | 
    
         
            +
            "Apple Store, Regent Street",2009-05-18-05,18/05/2009,18/05/2009,13.00,1.95,14.95,Phone case,1,13.00,429,15% (VAT on expenses),1.95,,,,
         
     | 
| 
      
 9 
     | 
    
         
            +
            Rexel Senate,2009-04-23-04,23/04/2009,23/04/2009,70.80,10.62,81.42,Cat 6 modules,1,70.80,325,15% (VAT on expenses),10.62,,,,
         
     | 
| 
      
 10 
     | 
    
         
            +
            "Apple Store, Regent Street",2009-04-18-09,18/04/2009,18/04/2009,537.35,80.60,617.95,Mac Mini,1,433.91,720,15% (VAT on expenses),65.09,,,,
         
     | 
| 
      
 11 
     | 
    
         
            +
            ,2009-04-18-09,,,,,,iWork 09,1,60.87,463,15% (VAT on expenses),9.13,,,,
         
     | 
| 
      
 12 
     | 
    
         
            +
            ,2009-04-18-09,,,,,,VMWare Fusion,1,42.57,463,15% (VAT on expenses),6.38,,,,
         
     | 
| 
         @@ -0,0 +1,2 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            "Date","Merchant","Currency","Amount","Tax","Category","Payment Method","Notes","Description","Reference #","Status"
         
     | 
| 
      
 2 
     | 
    
         
            +
            18/05/2009,"Apple Store, Regent Street","£","617.95","80.60",,"Debit Card","Ex VAT","Mac Mini, iWork, VMWare Fusion","2009-05-18-09",,"Mac Mini","£","433.91",,"720 - Computer Equipment","65.09; VAT - 15%","iWork 09","£","60.87",,"463 - IT Software and Consumables","9.13; VAT - 15%","VMWare Fusion","£","42.57",,"463 - IT Software and Consumables","6.38; VAT - 15%"
         
     | 
| 
         @@ -0,0 +1,2 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            "Date","Merchant","Currency","Amount","Tax","Category","Payment Method","Notes","Description","Reference #","Status"
         
     | 
| 
      
 2 
     | 
    
         
            +
            22/06/2009,"Geberer","£","2.80","0.33","494 - Travel International","Cash","€ ","Food & Coffee","2009-06-22-02",,"Coffee","£","1.50",,"494 - Travel International","0.24; VAT - Germany - 19%","Food","£","1.30",,"494 - Travel International","0.09; VAT - Germany - 7%"
         
     | 
| 
         @@ -0,0 +1,2 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            "Date","Merchant","Currency","Amount","Tax","Category","Payment Method","Notes","Description","Reference #","Status"
         
     | 
| 
      
 2 
     | 
    
         
            +
            18/05/2009,"Apple Store, Regent Street","£","617.95","80.60",,"Debit Card",,"Mac Mini, iWork, VMWare Fusion","2009-05-18-09",,"Mac Mini","£","499.00",,"720 - Computer Equipment","65.09; VAT - 15%","iWork 09","£","70.00",,"463 - IT Software and Consumables","9.13; VAT - 15%","VMWare Fusion","£","48.95",,"463 - IT Software and Consumables","6.38; VAT - 15%"
         
     | 
| 
         @@ -0,0 +1,2 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            "Date","Merchant","Currency","Amount","Tax","Category","Payment Method","Notes","Description","Reference #","Status"
         
     | 
| 
      
 2 
     | 
    
         
            +
            31/05/2009,"Bertrams Hotel Guldsmeden","£","2,235.00","447.00","494 - Travel International","Debit Card","VAT - Denmark - 25%","Reboot hotel booking","2009-05-31-02",
         
     | 
| 
         @@ -0,0 +1,218 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'tempfile'
         
     | 
| 
      
 3 
     | 
    
         
            +
             
     | 
| 
      
 4 
     | 
    
         
            +
            Spec::Matchers.define :have_detail_matching do |key, value|
         
     | 
| 
      
 5 
     | 
    
         
            +
              match do |object|
         
     | 
| 
      
 6 
     | 
    
         
            +
                object.send(key) == value
         
     | 
| 
      
 7 
     | 
    
         
            +
              end
         
     | 
| 
      
 8 
     | 
    
         
            +
              failure_message_for_should do |object|
         
     | 
| 
      
 9 
     | 
    
         
            +
                "Expected <#{object.class.name}>.#{key} to match '#{value}'. Instead, it was '#{object.send(key)}'"
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
              failure_message_for_should_not do |object|
         
     | 
| 
      
 12 
     | 
    
         
            +
                "Expected <#{object.class.name}>.#{key} NOT to match '#{value}'"
         
     | 
| 
      
 13 
     | 
    
         
            +
              end
         
     | 
| 
      
 14 
     | 
    
         
            +
              description do
         
     | 
| 
      
 15 
     | 
    
         
            +
                "have detail #{key.inspect} matching '#{value}'"
         
     | 
| 
      
 16 
     | 
    
         
            +
              end
         
     | 
| 
      
 17 
     | 
    
         
            +
            end
         
     | 
| 
      
 18 
     | 
    
         
            +
             
     | 
| 
      
 19 
     | 
    
         
            +
            describe PaperlessToXero::Converter do
         
     | 
| 
      
 20 
     | 
    
         
            +
              before(:each) do
         
     | 
| 
      
 21 
     | 
    
         
            +
                @converter = PaperlessToXero::Converter.new('/input/path', '/output/path')
         
     | 
| 
      
 22 
     | 
    
         
            +
              end
         
     | 
| 
      
 23 
     | 
    
         
            +
              
         
     | 
| 
      
 24 
     | 
    
         
            +
              def fixture_path(name)
         
     | 
| 
      
 25 
     | 
    
         
            +
                File.expand_path(File.dirname(__FILE__) + "/../fixtures/#{name}.csv")
         
     | 
| 
      
 26 
     | 
    
         
            +
              end
         
     | 
| 
      
 27 
     | 
    
         
            +
              
         
     | 
| 
      
 28 
     | 
    
         
            +
              def verify_invoice_details(details)
         
     | 
| 
      
 29 
     | 
    
         
            +
                invoice = @converter.invoices.first
         
     | 
| 
      
 30 
     | 
    
         
            +
                invoice_details = details[:invoice]
         
     | 
| 
      
 31 
     | 
    
         
            +
                
         
     | 
| 
      
 32 
     | 
    
         
            +
                invoice_details.each do |key, value|
         
     | 
| 
      
 33 
     | 
    
         
            +
                  invoice.should have_detail_matching(key, value)
         
     | 
| 
      
 34 
     | 
    
         
            +
                end
         
     | 
| 
      
 35 
     | 
    
         
            +
                
         
     | 
| 
      
 36 
     | 
    
         
            +
                invoice.should be_vat_inclusive     if details[:vat_inclusive]
         
     | 
| 
      
 37 
     | 
    
         
            +
                invoice.should_not be_vat_inclusive if details[:vat_exclusive]
         
     | 
| 
      
 38 
     | 
    
         
            +
                
         
     | 
| 
      
 39 
     | 
    
         
            +
                line_items_details = {:description => 'Phone case', :category => '429', :vat_inclusive_amount => '14.95', :vat_exclusive_amount => '13.00', :vat_amount => '1.95', :vat_type => '15% (VAT on expenses)'}
         
     | 
| 
      
 40 
     | 
    
         
            +
                line_items = invoice.items.dup
         
     | 
| 
      
 41 
     | 
    
         
            +
                if details[:line_items]
         
     | 
| 
      
 42 
     | 
    
         
            +
                  details[:line_items].each do |line_item_details|
         
     | 
| 
      
 43 
     | 
    
         
            +
                    line_item = line_items.shift
         
     | 
| 
      
 44 
     | 
    
         
            +
                    line_item_details.each do |key, value|
         
     | 
| 
      
 45 
     | 
    
         
            +
                      line_item.should have_detail_matching(key, value)
         
     | 
| 
      
 46 
     | 
    
         
            +
                    end
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
                end
         
     | 
| 
      
 49 
     | 
    
         
            +
              end
         
     | 
| 
      
 50 
     | 
    
         
            +
              
         
     | 
| 
      
 51 
     | 
    
         
            +
              describe "single item inputs" do
         
     | 
| 
      
 52 
     | 
    
         
            +
                it "should able to create an invoice for a basic single-item invoice" do
         
     | 
| 
      
 53 
     | 
    
         
            +
                  @converter.stubs(:input_path).returns(fixture_path('single-basic'))
         
     | 
| 
      
 54 
     | 
    
         
            +
                  @converter.parse
         
     | 
| 
      
 55 
     | 
    
         
            +
                  
         
     | 
| 
      
 56 
     | 
    
         
            +
                  verify_invoice_details(
         
     | 
| 
      
 57 
     | 
    
         
            +
                    :invoice => {:date => Date.parse('2009-05-18'), :merchant => 'Apple Store, Regent Street', 
         
     | 
| 
      
 58 
     | 
    
         
            +
                                 :reference_id => '2009-05-18-05', :inc_vat_total => '14.95', :vat_total => '1.95', 
         
     | 
| 
      
 59 
     | 
    
         
            +
                                 :ex_vat_total => '13.00', :currency => 'GBP'},
         
     | 
| 
      
 60 
     | 
    
         
            +
                    :vat_inclusive => true,
         
     | 
| 
      
 61 
     | 
    
         
            +
                    :line_items => [
         
     | 
| 
      
 62 
     | 
    
         
            +
                      {:description => 'Phone case', :category => '429', :vat_type => '15% (VAT on expenses)',
         
     | 
| 
      
 63 
     | 
    
         
            +
                       :vat_inclusive_amount => '14.95', :vat_exclusive_amount => '13.00', :vat_amount => '1.95'}
         
     | 
| 
      
 64 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 65 
     | 
    
         
            +
                  )
         
     | 
| 
      
 66 
     | 
    
         
            +
                end
         
     | 
| 
      
 67 
     | 
    
         
            +
                
         
     | 
| 
      
 68 
     | 
    
         
            +
                it "should able to create an invoice for a single-item invoice with an amount over 1,000 (Paperless likes to add the commas)" do
         
     | 
| 
      
 69 
     | 
    
         
            +
                  @converter.stubs(:input_path).returns(fixture_path('single-1000'))
         
     | 
| 
      
 70 
     | 
    
         
            +
                  @converter.parse
         
     | 
| 
      
 71 
     | 
    
         
            +
                  verify_invoice_details(
         
     | 
| 
      
 72 
     | 
    
         
            +
                    :invoice => {:inc_vat_total => '2235.00'},
         
     | 
| 
      
 73 
     | 
    
         
            +
                    :line_items => [
         
     | 
| 
      
 74 
     | 
    
         
            +
                      {:vat_inclusive_amount => '2235.00'}
         
     | 
| 
      
 75 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 76 
     | 
    
         
            +
                  )
         
     | 
| 
      
 77 
     | 
    
         
            +
                end
         
     | 
| 
      
 78 
     | 
    
         
            +
                
         
     | 
| 
      
 79 
     | 
    
         
            +
                it "should able to create an invoice for a zero-rated single-item invoice" do
         
     | 
| 
      
 80 
     | 
    
         
            +
                  @converter.stubs(:input_path).returns(fixture_path('single-zero_rated'))
         
     | 
| 
      
 81 
     | 
    
         
            +
                  @converter.parse
         
     | 
| 
      
 82 
     | 
    
         
            +
                  
         
     | 
| 
      
 83 
     | 
    
         
            +
                  verify_invoice_details(
         
     | 
| 
      
 84 
     | 
    
         
            +
                    :invoice => {:merchant => 'Transport For London', :inc_vat_total => '20.00', :vat_total => '0.00', 
         
     | 
| 
      
 85 
     | 
    
         
            +
                                 :ex_vat_total => '20.00'},
         
     | 
| 
      
 86 
     | 
    
         
            +
                    :vat_inclusive => true,
         
     | 
| 
      
 87 
     | 
    
         
            +
                    :line_items => [
         
     | 
| 
      
 88 
     | 
    
         
            +
                      {:vat_type => 'Zero Rated Expenses', :vat_inclusive_amount => '20.00', :vat_exclusive_amount => '20.00', 
         
     | 
| 
      
 89 
     | 
    
         
            +
                       :vat_amount => '0.00'}
         
     | 
| 
      
 90 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 91 
     | 
    
         
            +
                  )
         
     | 
| 
      
 92 
     | 
    
         
            +
                end
         
     | 
| 
      
 93 
     | 
    
         
            +
                
         
     | 
| 
      
 94 
     | 
    
         
            +
                it "should able to create an invoice for a foreign currency single-item invoice" do
         
     | 
| 
      
 95 
     | 
    
         
            +
                  @converter.stubs(:input_path).returns(fixture_path('single-foreign'))
         
     | 
| 
      
 96 
     | 
    
         
            +
                  @converter.parse
         
     | 
| 
      
 97 
     | 
    
         
            +
                  
         
     | 
| 
      
 98 
     | 
    
         
            +
                  verify_invoice_details(
         
     | 
| 
      
 99 
     | 
    
         
            +
                    :invoice => {:currency => 'EUR', :inc_vat_total => '250.00', :vat_total => '50.00', 
         
     | 
| 
      
 100 
     | 
    
         
            +
                                 :ex_vat_total => '200.00'},
         
     | 
| 
      
 101 
     | 
    
         
            +
                    :vat_inclusive => true,
         
     | 
| 
      
 102 
     | 
    
         
            +
                    :line_items => [
         
     | 
| 
      
 103 
     | 
    
         
            +
                      {:vat_type => '25% (Denmark, VAT on expenses)', :vat_inclusive_amount => '250.00', :vat_exclusive_amount => '200.00', 
         
     | 
| 
      
 104 
     | 
    
         
            +
                       :vat_amount => '50.00'}
         
     | 
| 
      
 105 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 106 
     | 
    
         
            +
                  )
         
     | 
| 
      
 107 
     | 
    
         
            +
                end
         
     | 
| 
      
 108 
     | 
    
         
            +
                
         
     | 
| 
      
 109 
     | 
    
         
            +
                it "should able to create an invoice for a foreign currency (not € or $) single-item invoice" do
         
     | 
| 
      
 110 
     | 
    
         
            +
                  @converter.stubs(:input_path).returns(fixture_path('single-dkk'))
         
     | 
| 
      
 111 
     | 
    
         
            +
                  @converter.parse
         
     | 
| 
      
 112 
     | 
    
         
            +
                  
         
     | 
| 
      
 113 
     | 
    
         
            +
                  verify_invoice_details(
         
     | 
| 
      
 114 
     | 
    
         
            +
                    :invoice => {:merchant => 'Halvandet', :currency => 'DKK'},
         
     | 
| 
      
 115 
     | 
    
         
            +
                    :vat_inclusive => true,
         
     | 
| 
      
 116 
     | 
    
         
            +
                    :line_items => [
         
     | 
| 
      
 117 
     | 
    
         
            +
                      {:vat_type => '25% (Denmark, VAT on expenses)', :vat_inclusive_amount => '73.00', :vat_exclusive_amount => '58.40', 
         
     | 
| 
      
 118 
     | 
    
         
            +
                       :vat_amount => '14.60'}
         
     | 
| 
      
 119 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 120 
     | 
    
         
            +
                  )
         
     | 
| 
      
 121 
     | 
    
         
            +
                end
         
     | 
| 
      
 122 
     | 
    
         
            +
                
         
     | 
| 
      
 123 
     | 
    
         
            +
                it "should cope with a single item invoice with no VAT" do
         
     | 
| 
      
 124 
     | 
    
         
            +
                  @converter.stubs(:input_path).returns(fixture_path('single-no-vat'))
         
     | 
| 
      
 125 
     | 
    
         
            +
                  @converter.parse
         
     | 
| 
      
 126 
     | 
    
         
            +
                  
         
     | 
| 
      
 127 
     | 
    
         
            +
                  verify_invoice_details(
         
     | 
| 
      
 128 
     | 
    
         
            +
                    :invoice => {:inc_vat_total => '4.50', :vat_total => '0.00', :ex_vat_total => '4.50'},
         
     | 
| 
      
 129 
     | 
    
         
            +
                    :vat_inclusive => true,
         
     | 
| 
      
 130 
     | 
    
         
            +
                    :line_items => [
         
     | 
| 
      
 131 
     | 
    
         
            +
                      {:vat_type => 'No VAT', :vat_inclusive_amount => '4.50', :vat_exclusive_amount => '4.50', 
         
     | 
| 
      
 132 
     | 
    
         
            +
                       :vat_amount => nil}
         
     | 
| 
      
 133 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 134 
     | 
    
         
            +
                  )
         
     | 
| 
      
 135 
     | 
    
         
            +
                end
         
     | 
| 
      
 136 
     | 
    
         
            +
              end
         
     | 
| 
      
 137 
     | 
    
         
            +
              
         
     | 
| 
      
 138 
     | 
    
         
            +
              describe "multi-item inputs" do
         
     | 
| 
      
 139 
     | 
    
         
            +
                it "should able to create an Invoice for a basic multi-item invoice" do
         
     | 
| 
      
 140 
     | 
    
         
            +
                  @converter.stubs(:input_path).returns(fixture_path('multi-item'))
         
     | 
| 
      
 141 
     | 
    
         
            +
                  @converter.parse
         
     | 
| 
      
 142 
     | 
    
         
            +
                  
         
     | 
| 
      
 143 
     | 
    
         
            +
                  verify_invoice_details(
         
     | 
| 
      
 144 
     | 
    
         
            +
                    :invoice => {:date => Date.parse('2009-05-18'), :merchant => 'Apple Store, Regent Street', 
         
     | 
| 
      
 145 
     | 
    
         
            +
                                 :reference_id => '2009-05-18-09', :inc_vat_total => '617.95', :vat_total => '80.60', 
         
     | 
| 
      
 146 
     | 
    
         
            +
                                 :ex_vat_total => '537.35', :currency => 'GBP'},
         
     | 
| 
      
 147 
     | 
    
         
            +
                    :vat_inclusive => true,
         
     | 
| 
      
 148 
     | 
    
         
            +
                    :line_items => [
         
     | 
| 
      
 149 
     | 
    
         
            +
                      {:description => 'Mac Mini', :category => '720', :vat_type => '15% (VAT on expenses)',
         
     | 
| 
      
 150 
     | 
    
         
            +
                       :vat_inclusive_amount => '499.00', :vat_exclusive_amount => '433.91', :vat_amount => '65.09'},
         
     | 
| 
      
 151 
     | 
    
         
            +
                      {:description => 'iWork 09', :category => '463', :vat_type => '15% (VAT on expenses)',
         
     | 
| 
      
 152 
     | 
    
         
            +
                       :vat_inclusive_amount => '70.00', :vat_exclusive_amount => '60.87', :vat_amount => '9.13'},
         
     | 
| 
      
 153 
     | 
    
         
            +
                      {:description => 'VMWare Fusion', :category => '463', :vat_type => '15% (VAT on expenses)',
         
     | 
| 
      
 154 
     | 
    
         
            +
                       :vat_inclusive_amount => '48.95', :vat_exclusive_amount => '42.57', :vat_amount => '6.38'}
         
     | 
| 
      
 155 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 156 
     | 
    
         
            +
                  )
         
     | 
| 
      
 157 
     | 
    
         
            +
                end
         
     | 
| 
      
 158 
     | 
    
         
            +
                
         
     | 
| 
      
 159 
     | 
    
         
            +
                it "should able to create an Invoice for a foreign currency multi-item invoice" do
         
     | 
| 
      
 160 
     | 
    
         
            +
                  @converter.stubs(:input_path).returns(fixture_path('multi-foreign'))
         
     | 
| 
      
 161 
     | 
    
         
            +
                  @converter.parse
         
     | 
| 
      
 162 
     | 
    
         
            +
                  
         
     | 
| 
      
 163 
     | 
    
         
            +
                  verify_invoice_details(
         
     | 
| 
      
 164 
     | 
    
         
            +
                    :invoice => {:currency => 'EUR', :inc_vat_total => '2.80', :vat_total => '0.33', 
         
     | 
| 
      
 165 
     | 
    
         
            +
                                 :ex_vat_total => '2.47'},
         
     | 
| 
      
 166 
     | 
    
         
            +
                    :vat_inclusive => true,
         
     | 
| 
      
 167 
     | 
    
         
            +
                    :line_items => [
         
     | 
| 
      
 168 
     | 
    
         
            +
                      {:description => 'Coffee', :vat_type => '19% (Germany, VAT on expenses)', 
         
     | 
| 
      
 169 
     | 
    
         
            +
                       :vat_inclusive_amount => '1.50', :vat_amount => '0.24'},
         
     | 
| 
      
 170 
     | 
    
         
            +
                      {:description => 'Food', :vat_type => '7% (Germany, VAT on expenses)', 
         
     | 
| 
      
 171 
     | 
    
         
            +
                       :vat_inclusive_amount => '1.30', :vat_amount => '0.09'}
         
     | 
| 
      
 172 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 173 
     | 
    
         
            +
                  )
         
     | 
| 
      
 174 
     | 
    
         
            +
                end
         
     | 
| 
      
 175 
     | 
    
         
            +
                
         
     | 
| 
      
 176 
     | 
    
         
            +
                it "should cope with a VAT-exclusive invoice" do
         
     | 
| 
      
 177 
     | 
    
         
            +
                  @converter.stubs(:input_path).returns(fixture_path('multi-ex-vat'))
         
     | 
| 
      
 178 
     | 
    
         
            +
                  @converter.parse
         
     | 
| 
      
 179 
     | 
    
         
            +
                  
         
     | 
| 
      
 180 
     | 
    
         
            +
                  verify_invoice_details(
         
     | 
| 
      
 181 
     | 
    
         
            +
                    :invoice => {:date => Date.parse('2009-05-18'), :merchant => 'Apple Store, Regent Street', 
         
     | 
| 
      
 182 
     | 
    
         
            +
                                 :reference_id => '2009-05-18-09', :inc_vat_total => '617.95', :vat_total => '80.60', 
         
     | 
| 
      
 183 
     | 
    
         
            +
                                 :ex_vat_total => '537.35', :currency => 'GBP'},
         
     | 
| 
      
 184 
     | 
    
         
            +
                    :vat_inclusive => false,
         
     | 
| 
      
 185 
     | 
    
         
            +
                    :line_items => [
         
     | 
| 
      
 186 
     | 
    
         
            +
                      {:description => 'Mac Mini', :category => '720', :vat_type => '15% (VAT on expenses)',
         
     | 
| 
      
 187 
     | 
    
         
            +
                       :vat_inclusive_amount => '499.00', :vat_exclusive_amount => '433.91', :vat_amount => '65.09'},
         
     | 
| 
      
 188 
     | 
    
         
            +
                      {:description => 'iWork 09', :category => '463', :vat_type => '15% (VAT on expenses)',
         
     | 
| 
      
 189 
     | 
    
         
            +
                       :vat_inclusive_amount => '70.00', :vat_exclusive_amount => '60.87', :vat_amount => '9.13'},
         
     | 
| 
      
 190 
     | 
    
         
            +
                      {:description => 'VMWare Fusion', :category => '463', :vat_type => '15% (VAT on expenses)',
         
     | 
| 
      
 191 
     | 
    
         
            +
                       :vat_inclusive_amount => '48.95', :vat_exclusive_amount => '42.57', :vat_amount => '6.38'}
         
     | 
| 
      
 192 
     | 
    
         
            +
                    ]
         
     | 
| 
      
 193 
     | 
    
         
            +
                  )
         
     | 
| 
      
 194 
     | 
    
         
            +
                end
         
     | 
| 
      
 195 
     | 
    
         
            +
              end
         
     | 
| 
      
 196 
     | 
    
         
            +
              
         
     | 
| 
      
 197 
     | 
    
         
            +
              describe "end-to-end" do
         
     | 
| 
      
 198 
     | 
    
         
            +
                before(:each) do
         
     | 
| 
      
 199 
     | 
    
         
            +
                  @tempfile_path = Tempfile.new(['output', 'csv']).path
         
     | 
| 
      
 200 
     | 
    
         
            +
                end
         
     | 
| 
      
 201 
     | 
    
         
            +
                
         
     | 
| 
      
 202 
     | 
    
         
            +
                it "should produce exactly the output we expect" do
         
     | 
| 
      
 203 
     | 
    
         
            +
                  converter = PaperlessToXero::Converter.new(fixture_path('end_to_end-input'), @tempfile_path)
         
     | 
| 
      
 204 
     | 
    
         
            +
                  converter.convert!
         
     | 
| 
      
 205 
     | 
    
         
            +
                  
         
     | 
| 
      
 206 
     | 
    
         
            +
                  expected = File.readlines(fixture_path('end_to_end-output'))
         
     | 
| 
      
 207 
     | 
    
         
            +
                  actual = File.readlines(@tempfile_path)
         
     | 
| 
      
 208 
     | 
    
         
            +
                  
         
     | 
| 
      
 209 
     | 
    
         
            +
                  (0..expected.size).each do |i|
         
     | 
| 
      
 210 
     | 
    
         
            +
                    actual[i].should == expected[i]
         
     | 
| 
      
 211 
     | 
    
         
            +
                  end
         
     | 
| 
      
 212 
     | 
    
         
            +
                end
         
     | 
| 
      
 213 
     | 
    
         
            +
                
         
     | 
| 
      
 214 
     | 
    
         
            +
                after(:each) do
         
     | 
| 
      
 215 
     | 
    
         
            +
                  File.unlink(@tempfile_path)
         
     | 
| 
      
 216 
     | 
    
         
            +
                end
         
     | 
| 
      
 217 
     | 
    
         
            +
              end
         
     | 
| 
      
 218 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,162 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe PaperlessToXero::InvoiceItem do
         
     | 
| 
      
 4 
     | 
    
         
            +
              describe "the creation basics" do
         
     | 
| 
      
 5 
     | 
    
         
            +
                it "should be able to be instantiated" do
         
     | 
| 
      
 6 
     | 
    
         
            +
                  # amount, vat, category, payment_method, notes, description, reference, status
         
     | 
| 
      
 7 
     | 
    
         
            +
                  PaperlessToXero::InvoiceItem.new('description', '34.50', '4.50', '123 - Some stuff', 'VAT - 15%', true).
         
     | 
| 
      
 8 
     | 
    
         
            +
                    should be_instance_of(PaperlessToXero::InvoiceItem)
         
     | 
| 
      
 9 
     | 
    
         
            +
                end
         
     | 
| 
      
 10 
     | 
    
         
            +
              end
         
     | 
| 
      
 11 
     | 
    
         
            +
              
         
     | 
| 
      
 12 
     | 
    
         
            +
              describe "instances" do
         
     | 
| 
      
 13 
     | 
    
         
            +
                before(:each) do
         
     | 
| 
      
 14 
     | 
    
         
            +
                  @item = PaperlessToXero::InvoiceItem.new('description', '34.50', '4.50', '123 - Some stuff', 'VAT - 15%', true)
         
     | 
| 
      
 15 
     | 
    
         
            +
                end
         
     | 
| 
      
 16 
     | 
    
         
            +
                
         
     | 
| 
      
 17 
     | 
    
         
            +
                it "should be able to report their description" do
         
     | 
| 
      
 18 
     | 
    
         
            +
                  @item.description.should == 'description'
         
     | 
| 
      
 19 
     | 
    
         
            +
                end
         
     | 
| 
      
 20 
     | 
    
         
            +
                
         
     | 
| 
      
 21 
     | 
    
         
            +
                it "should be able to report their amount" do
         
     | 
| 
      
 22 
     | 
    
         
            +
                  @item.amount.should == "34.50"
         
     | 
| 
      
 23 
     | 
    
         
            +
                end
         
     | 
| 
      
 24 
     | 
    
         
            +
                
         
     | 
| 
      
 25 
     | 
    
         
            +
                it "should be able to report their VAT amount" do
         
     | 
| 
      
 26 
     | 
    
         
            +
                  @item.vat_amount.should == "4.50"
         
     | 
| 
      
 27 
     | 
    
         
            +
                end
         
     | 
| 
      
 28 
     | 
    
         
            +
                
         
     | 
| 
      
 29 
     | 
    
         
            +
                it "should be able to report their VAT rate" do
         
     | 
| 
      
 30 
     | 
    
         
            +
                  @item.vat_type.should == '15% (VAT on expenses)'
         
     | 
| 
      
 31 
     | 
    
         
            +
                end
         
     | 
| 
      
 32 
     | 
    
         
            +
                
         
     | 
| 
      
 33 
     | 
    
         
            +
                it "should be able to report whether the amount is VAT inclusive" do
         
     | 
| 
      
 34 
     | 
    
         
            +
                  @item.vat_inclusive.should be_true
         
     | 
| 
      
 35 
     | 
    
         
            +
                end
         
     | 
| 
      
 36 
     | 
    
         
            +
                
         
     | 
| 
      
 37 
     | 
    
         
            +
                it "should be able to report whether their category" do
         
     | 
| 
      
 38 
     | 
    
         
            +
                  @item.category.should == "123 - Some stuff"
         
     | 
| 
      
 39 
     | 
    
         
            +
                end
         
     | 
| 
      
 40 
     | 
    
         
            +
                
         
     | 
| 
      
 41 
     | 
    
         
            +
                describe "where items are VAT inclusive" do
         
     | 
| 
      
 42 
     | 
    
         
            +
                  it "should be able to report the amount of VAT" do
         
     | 
| 
      
 43 
     | 
    
         
            +
                    @item.vat_amount.should == "4.50"
         
     | 
| 
      
 44 
     | 
    
         
            +
                  end
         
     | 
| 
      
 45 
     | 
    
         
            +
                  
         
     | 
| 
      
 46 
     | 
    
         
            +
                  it "should be able to report the VAT inclusive amount" do
         
     | 
| 
      
 47 
     | 
    
         
            +
                    @item.vat_inclusive_amount.should == "34.50"
         
     | 
| 
      
 48 
     | 
    
         
            +
                  end
         
     | 
| 
      
 49 
     | 
    
         
            +
                  
         
     | 
| 
      
 50 
     | 
    
         
            +
                  it "should be able to report the VAT exclusive amount" do
         
     | 
| 
      
 51 
     | 
    
         
            +
                    @item.vat_exclusive_amount.should == "30.00"
         
     | 
| 
      
 52 
     | 
    
         
            +
                  end
         
     | 
| 
      
 53 
     | 
    
         
            +
                end
         
     | 
| 
      
 54 
     | 
    
         
            +
                
         
     | 
| 
      
 55 
     | 
    
         
            +
                describe "where items are VAT exclusive" do
         
     | 
| 
      
 56 
     | 
    
         
            +
                  before(:each) do
         
     | 
| 
      
 57 
     | 
    
         
            +
                    @item = PaperlessToXero::InvoiceItem.new('description', '30.00', '4.50', '123 - Some stuff', 'VAT - 15%', false)
         
     | 
| 
      
 58 
     | 
    
         
            +
                  end
         
     | 
| 
      
 59 
     | 
    
         
            +
                  
         
     | 
| 
      
 60 
     | 
    
         
            +
                  it "should be able to report the amount of VAT" do
         
     | 
| 
      
 61 
     | 
    
         
            +
                    @item.vat_amount.should == "4.50"
         
     | 
| 
      
 62 
     | 
    
         
            +
                  end
         
     | 
| 
      
 63 
     | 
    
         
            +
                  
         
     | 
| 
      
 64 
     | 
    
         
            +
                  it "should be able to report the VAT inclusive amount" do
         
     | 
| 
      
 65 
     | 
    
         
            +
                    @item.vat_inclusive_amount.should == "34.50"
         
     | 
| 
      
 66 
     | 
    
         
            +
                  end
         
     | 
| 
      
 67 
     | 
    
         
            +
                  
         
     | 
| 
      
 68 
     | 
    
         
            +
                  it "should be able to report the VAT exclusive amount" do
         
     | 
| 
      
 69 
     | 
    
         
            +
                    @item.vat_exclusive_amount.should == "30.00"
         
     | 
| 
      
 70 
     | 
    
         
            +
                  end
         
     | 
| 
      
 71 
     | 
    
         
            +
                end
         
     | 
| 
      
 72 
     | 
    
         
            +
                
         
     | 
| 
      
 73 
     | 
    
         
            +
                describe "where items are zero-rated for VAT" do
         
     | 
| 
      
 74 
     | 
    
         
            +
                  describe "and £0.00 VAT is reported for them" do
         
     | 
| 
      
 75 
     | 
    
         
            +
                    before(:each) do
         
     | 
| 
      
 76 
     | 
    
         
            +
                      @item = PaperlessToXero::InvoiceItem.new('description', '30.00', '0.00', '123 - Some stuff', 'VAT - 0%', false)
         
     | 
| 
      
 77 
     | 
    
         
            +
                    end
         
     | 
| 
      
 78 
     | 
    
         
            +
                    
         
     | 
| 
      
 79 
     | 
    
         
            +
                    it "should be able to report the amount of VAT" do
         
     | 
| 
      
 80 
     | 
    
         
            +
                      @item.vat_amount.should == "0.00"
         
     | 
| 
      
 81 
     | 
    
         
            +
                    end
         
     | 
| 
      
 82 
     | 
    
         
            +
                    
         
     | 
| 
      
 83 
     | 
    
         
            +
                    it "should be able to report the VAT inclusive amount" do
         
     | 
| 
      
 84 
     | 
    
         
            +
                      @item.vat_inclusive_amount.should == "30.00"
         
     | 
| 
      
 85 
     | 
    
         
            +
                    end
         
     | 
| 
      
 86 
     | 
    
         
            +
                    
         
     | 
| 
      
 87 
     | 
    
         
            +
                    it "should be able to report the VAT exclusive amount" do
         
     | 
| 
      
 88 
     | 
    
         
            +
                      @item.vat_exclusive_amount.should == "30.00"
         
     | 
| 
      
 89 
     | 
    
         
            +
                    end
         
     | 
| 
      
 90 
     | 
    
         
            +
                  end
         
     | 
| 
      
 91 
     | 
    
         
            +
                  
         
     | 
| 
      
 92 
     | 
    
         
            +
                  describe "and no VAT is reported for them" do
         
     | 
| 
      
 93 
     | 
    
         
            +
                    before(:each) do
         
     | 
| 
      
 94 
     | 
    
         
            +
                      @item = PaperlessToXero::InvoiceItem.new('description', '30.00', nil, '123 - Some stuff', 'VAT - 0%', false)
         
     | 
| 
      
 95 
     | 
    
         
            +
                    end
         
     | 
| 
      
 96 
     | 
    
         
            +
                    
         
     | 
| 
      
 97 
     | 
    
         
            +
                    it "should be able to report the amount of VAT" do
         
     | 
| 
      
 98 
     | 
    
         
            +
                      @item.vat_amount.should == "0.00"
         
     | 
| 
      
 99 
     | 
    
         
            +
                    end
         
     | 
| 
      
 100 
     | 
    
         
            +
                    
         
     | 
| 
      
 101 
     | 
    
         
            +
                    it "should be able to report the VAT inclusive amount" do
         
     | 
| 
      
 102 
     | 
    
         
            +
                      @item.vat_inclusive_amount.should == "30.00"
         
     | 
| 
      
 103 
     | 
    
         
            +
                    end
         
     | 
| 
      
 104 
     | 
    
         
            +
                    
         
     | 
| 
      
 105 
     | 
    
         
            +
                    it "should be able to report the VAT exclusive amount" do
         
     | 
| 
      
 106 
     | 
    
         
            +
                      @item.vat_exclusive_amount.should == "30.00"
         
     | 
| 
      
 107 
     | 
    
         
            +
                    end
         
     | 
| 
      
 108 
     | 
    
         
            +
                  end
         
     | 
| 
      
 109 
     | 
    
         
            +
                end
         
     | 
| 
      
 110 
     | 
    
         
            +
                
         
     | 
| 
      
 111 
     | 
    
         
            +
                describe "where items do not have a VAT receipt" do
         
     | 
| 
      
 112 
     | 
    
         
            +
                  before(:each) do
         
     | 
| 
      
 113 
     | 
    
         
            +
                    @item = PaperlessToXero::InvoiceItem.new('description', '30.00', nil, '123 - Some stuff', 'No VAT', false)
         
     | 
| 
      
 114 
     | 
    
         
            +
                  end
         
     | 
| 
      
 115 
     | 
    
         
            +
                  
         
     | 
| 
      
 116 
     | 
    
         
            +
                  it "should be able to report the amount of VAT" do
         
     | 
| 
      
 117 
     | 
    
         
            +
                    @item.vat_amount.should == nil
         
     | 
| 
      
 118 
     | 
    
         
            +
                  end
         
     | 
| 
      
 119 
     | 
    
         
            +
                  
         
     | 
| 
      
 120 
     | 
    
         
            +
                  it "should be able to report the VAT inclusive amount" do
         
     | 
| 
      
 121 
     | 
    
         
            +
                    @item.vat_inclusive_amount.should == "30.00"
         
     | 
| 
      
 122 
     | 
    
         
            +
                  end
         
     | 
| 
      
 123 
     | 
    
         
            +
                  
         
     | 
| 
      
 124 
     | 
    
         
            +
                  it "should be able to report the VAT exclusive amount" do
         
     | 
| 
      
 125 
     | 
    
         
            +
                    @item.vat_exclusive_amount.should == "30.00"
         
     | 
| 
      
 126 
     | 
    
         
            +
                  end
         
     | 
| 
      
 127 
     | 
    
         
            +
                end
         
     | 
| 
      
 128 
     | 
    
         
            +
                
         
     | 
| 
      
 129 
     | 
    
         
            +
                describe "VAT extraction" do
         
     | 
| 
      
 130 
     | 
    
         
            +
                  def self.vat_pairs
         
     | 
| 
      
 131 
     | 
    
         
            +
                    {'VAT - Germany - 7%'     => '7% (Germany, VAT on expenses)',
         
     | 
| 
      
 132 
     | 
    
         
            +
                     'VAT - Germany - 19%'    => '19% (Germany, VAT on expenses)',
         
     | 
| 
      
 133 
     | 
    
         
            +
                     'VAT - Germany'          => '19% (Germany, VAT on expenses)',
         
     | 
| 
      
 134 
     | 
    
         
            +
                     'VAT - France - 5.5%'    => '5.5% (France, VAT on expenses)',
         
     | 
| 
      
 135 
     | 
    
         
            +
                     'VAT - France - 19.6%'   => '19.6% (France, VAT on expenses)',
         
     | 
| 
      
 136 
     | 
    
         
            +
                     'VAT - France'           => '19.6% (France, VAT on expenses)',
         
     | 
| 
      
 137 
     | 
    
         
            +
                     'VAT - Denmark - 25%'    => '25% (Denmark, VAT on expenses)',
         
     | 
| 
      
 138 
     | 
    
         
            +
                     'VAT - Denmark'          => '25% (Denmark, VAT on expenses)',
         
     | 
| 
      
 139 
     | 
    
         
            +
                     'VAT - Sweden - 25%'     => '25% (Sweden, VAT on expenses)',
         
     | 
| 
      
 140 
     | 
    
         
            +
                     'VAT - Sweden'           => '25% (Sweden, VAT on expenses)',
         
     | 
| 
      
 141 
     | 
    
         
            +
                     'VAT - Ireland - 21.5%'  => '21.5% (Ireland, VAT on expenses)',
         
     | 
| 
      
 142 
     | 
    
         
            +
                     'VAT - Ireland'          => '21.5% (Ireland, VAT on expenses)',
         
     | 
| 
      
 143 
     | 
    
         
            +
                     'VAT - Luxembourg - 15%' => '15% (Luxembourg, VAT on expenses)',
         
     | 
| 
      
 144 
     | 
    
         
            +
                     'VAT - Luxembourg'       => '15% (Luxembourg, VAT on expenses)',
         
     | 
| 
      
 145 
     | 
    
         
            +
                     'VAT - EU'               => '15% (EU VAT ID)',
         
     | 
| 
      
 146 
     | 
    
         
            +
                     'VAT - EU - EU372000063' => '15% (EU VAT ID)',
         
     | 
| 
      
 147 
     | 
    
         
            +
                     'VAT - 15%'              => '15% (VAT on expenses)',
         
     | 
| 
      
 148 
     | 
    
         
            +
                     'VAT - 0%'               => 'Zero Rated Expenses',
         
     | 
| 
      
 149 
     | 
    
         
            +
                     'VAT'                    => '15% (VAT on expenses)',
         
     | 
| 
      
 150 
     | 
    
         
            +
                     'No VAT'                 => 'No VAT'}
         
     | 
| 
      
 151 
     | 
    
         
            +
                  end
         
     | 
| 
      
 152 
     | 
    
         
            +
                  
         
     | 
| 
      
 153 
     | 
    
         
            +
                  vat_pairs.each do |input, expected|
         
     | 
| 
      
 154 
     | 
    
         
            +
                    it "should convert '#{input}' to '#{expected}'" do
         
     | 
| 
      
 155 
     | 
    
         
            +
                      PaperlessToXero::InvoiceItem.publicize_methods do
         
     | 
| 
      
 156 
     | 
    
         
            +
                        @item.extract_vat_type(input).should == expected
         
     | 
| 
      
 157 
     | 
    
         
            +
                      end
         
     | 
| 
      
 158 
     | 
    
         
            +
                    end
         
     | 
| 
      
 159 
     | 
    
         
            +
                  end
         
     | 
| 
      
 160 
     | 
    
         
            +
                end
         
     | 
| 
      
 161 
     | 
    
         
            +
              end
         
     | 
| 
      
 162 
     | 
    
         
            +
            end
         
     | 
| 
         @@ -0,0 +1,131 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require File.expand_path(File.dirname(__FILE__) + '/../spec_helper')
         
     | 
| 
      
 2 
     | 
    
         
            +
             
     | 
| 
      
 3 
     | 
    
         
            +
            describe PaperlessToXero::Invoice do
         
     | 
| 
      
 4 
     | 
    
         
            +
              describe "the creation basics" do
         
     | 
| 
      
 5 
     | 
    
         
            +
                it "should be able to be instantiated" do
         
     | 
| 
      
 6 
     | 
    
         
            +
                  PaperlessToXero::Invoice.new(Date.parse("2009-07-20"), 'Merchant', 'reference UID', '23.00', '3.00', true, 'GBP').
         
     | 
| 
      
 7 
     | 
    
         
            +
                    should be_instance_of(PaperlessToXero::Invoice)
         
     | 
| 
      
 8 
     | 
    
         
            +
                end
         
     | 
| 
      
 9 
     | 
    
         
            +
              end
         
     | 
| 
      
 10 
     | 
    
         
            +
              
         
     | 
| 
      
 11 
     | 
    
         
            +
              describe "instances" do
         
     | 
| 
      
 12 
     | 
    
         
            +
                describe "basics" do
         
     | 
| 
      
 13 
     | 
    
         
            +
                  before(:each) do
         
     | 
| 
      
 14 
     | 
    
         
            +
                    @invoice = PaperlessToXero::Invoice.new(Date.parse("2009-07-20"), 'Merchant', 'reference UID', '23.00', '3.00', true, 'GBP')
         
     | 
| 
      
 15 
     | 
    
         
            +
                  end
         
     | 
| 
      
 16 
     | 
    
         
            +
                  
         
     | 
| 
      
 17 
     | 
    
         
            +
                  it "should be able to report their merchant" do
         
     | 
| 
      
 18 
     | 
    
         
            +
                    @invoice.merchant.should == 'Merchant'
         
     | 
| 
      
 19 
     | 
    
         
            +
                  end
         
     | 
| 
      
 20 
     | 
    
         
            +
                  
         
     | 
| 
      
 21 
     | 
    
         
            +
                  it "should be able to report their currency" do
         
     | 
| 
      
 22 
     | 
    
         
            +
                    @invoice.currency.should == 'GBP'
         
     | 
| 
      
 23 
     | 
    
         
            +
                  end
         
     | 
| 
      
 24 
     | 
    
         
            +
                  
         
     | 
| 
      
 25 
     | 
    
         
            +
                  it "should be able to report their reference UID" do
         
     | 
| 
      
 26 
     | 
    
         
            +
                    @invoice.reference_id.should == 'reference UID'
         
     | 
| 
      
 27 
     | 
    
         
            +
                  end
         
     | 
| 
      
 28 
     | 
    
         
            +
                  
         
     | 
| 
      
 29 
     | 
    
         
            +
                  it "should be able to report their date" do
         
     | 
| 
      
 30 
     | 
    
         
            +
                    @invoice.date.should == Date.parse("2009-07-20")
         
     | 
| 
      
 31 
     | 
    
         
            +
                  end
         
     | 
| 
      
 32 
     | 
    
         
            +
                  
         
     | 
| 
      
 33 
     | 
    
         
            +
                  it "should be able to report their total" do
         
     | 
| 
      
 34 
     | 
    
         
            +
                    @invoice.total.should == '23.00'
         
     | 
| 
      
 35 
     | 
    
         
            +
                  end
         
     | 
| 
      
 36 
     | 
    
         
            +
                  
         
     | 
| 
      
 37 
     | 
    
         
            +
                  it "should be able to report VAT" do
         
     | 
| 
      
 38 
     | 
    
         
            +
                    @invoice.vat_total.should == '3.00'
         
     | 
| 
      
 39 
     | 
    
         
            +
                  end
         
     | 
| 
      
 40 
     | 
    
         
            +
                  
         
     | 
| 
      
 41 
     | 
    
         
            +
                  it "should be able to report their ex-VAT total" do
         
     | 
| 
      
 42 
     | 
    
         
            +
                    @invoice.ex_vat_total.should == '20.00'
         
     | 
| 
      
 43 
     | 
    
         
            +
                  end
         
     | 
| 
      
 44 
     | 
    
         
            +
                  
         
     | 
| 
      
 45 
     | 
    
         
            +
                  it "should be able to report their inc-VAT total" do
         
     | 
| 
      
 46 
     | 
    
         
            +
                    @invoice.inc_vat_total.should == '23.00'
         
     | 
| 
      
 47 
     | 
    
         
            +
                  end
         
     | 
| 
      
 48 
     | 
    
         
            +
                  
         
     | 
| 
      
 49 
     | 
    
         
            +
                  it "should be able to report that it's VAT inclusive" do
         
     | 
| 
      
 50 
     | 
    
         
            +
                    @invoice.vat_inclusive?.should be_true
         
     | 
| 
      
 51 
     | 
    
         
            +
                  end
         
     | 
| 
      
 52 
     | 
    
         
            +
                  
         
     | 
| 
      
 53 
     | 
    
         
            +
                  describe "adding items to an invoice" do
         
     | 
| 
      
 54 
     | 
    
         
            +
                    it "should be able to add an item" do
         
     | 
| 
      
 55 
     | 
    
         
            +
                      PaperlessToXero::InvoiceItem.expects(:new).with('description', '30.00', nil, '123 - Some stuff', 'No VAT', true).returns(:item)
         
     | 
| 
      
 56 
     | 
    
         
            +
                      @invoice.add_item('description', '30.00', nil, '123 - Some stuff', 'No VAT')
         
     | 
| 
      
 57 
     | 
    
         
            +
                      
         
     | 
| 
      
 58 
     | 
    
         
            +
                      @invoice.items.should == [:item]
         
     | 
| 
      
 59 
     | 
    
         
            +
                    end
         
     | 
| 
      
 60 
     | 
    
         
            +
                  end
         
     | 
| 
      
 61 
     | 
    
         
            +
                  
         
     | 
| 
      
 62 
     | 
    
         
            +
                  describe "ex-VAT invoices" do
         
     | 
| 
      
 63 
     | 
    
         
            +
                    before(:each) do
         
     | 
| 
      
 64 
     | 
    
         
            +
                      @invoice = PaperlessToXero::Invoice.new(Date.parse("2009-07-20"), 'Merchant', 'reference UID', '23.00', '3.00', false, 'GBP')
         
     | 
| 
      
 65 
     | 
    
         
            +
                    end
         
     | 
| 
      
 66 
     | 
    
         
            +
                    
         
     | 
| 
      
 67 
     | 
    
         
            +
                    it "should be able to report VAT" do
         
     | 
| 
      
 68 
     | 
    
         
            +
                      @invoice.vat_total.should == '3.00'
         
     | 
| 
      
 69 
     | 
    
         
            +
                    end
         
     | 
| 
      
 70 
     | 
    
         
            +
                    
         
     | 
| 
      
 71 
     | 
    
         
            +
                    it "should be able to report their ex-VAT total" do
         
     | 
| 
      
 72 
     | 
    
         
            +
                      @invoice.ex_vat_total.should == '20.00'
         
     | 
| 
      
 73 
     | 
    
         
            +
                    end
         
     | 
| 
      
 74 
     | 
    
         
            +
                    
         
     | 
| 
      
 75 
     | 
    
         
            +
                    it "should be able to report their inc-VAT total" do
         
     | 
| 
      
 76 
     | 
    
         
            +
                      @invoice.inc_vat_total.should == '23.00'
         
     | 
| 
      
 77 
     | 
    
         
            +
                    end
         
     | 
| 
      
 78 
     | 
    
         
            +
                    
         
     | 
| 
      
 79 
     | 
    
         
            +
                    it "should be pass on its ex-vat-ness to invoice items" do
         
     | 
| 
      
 80 
     | 
    
         
            +
                      PaperlessToXero::InvoiceItem.expects(:new).with('description', '30.00', nil, '123 - Some stuff', 'No VAT', false).returns(:item)
         
     | 
| 
      
 81 
     | 
    
         
            +
                      @invoice.add_item('description', '30.00', nil, '123 - Some stuff', 'No VAT')
         
     | 
| 
      
 82 
     | 
    
         
            +
                    end
         
     | 
| 
      
 83 
     | 
    
         
            +
                  end
         
     | 
| 
      
 84 
     | 
    
         
            +
                end
         
     | 
| 
      
 85 
     | 
    
         
            +
                
         
     | 
| 
      
 86 
     | 
    
         
            +
                describe "serializing an invoice" do
         
     | 
| 
      
 87 
     | 
    
         
            +
                  before(:each) do
         
     | 
| 
      
 88 
     | 
    
         
            +
                    @fake_csv = mock()
         
     | 
| 
      
 89 
     | 
    
         
            +
                  end
         
     | 
| 
      
 90 
     | 
    
         
            +
                  
         
     | 
| 
      
 91 
     | 
    
         
            +
                  def make_invoice(total, vat, currency = 'GBP')
         
     | 
| 
      
 92 
     | 
    
         
            +
                    PaperlessToXero::Invoice.new(Date.parse("2009-07-20"), 'Merchant', 'reference UID', total, vat, true, currency)
         
     | 
| 
      
 93 
     | 
    
         
            +
                  end
         
     | 
| 
      
 94 
     | 
    
         
            +
                  
         
     | 
| 
      
 95 
     | 
    
         
            +
                  describe "single-item invoices" do
         
     | 
| 
      
 96 
     | 
    
         
            +
                    it "should produce sensible Xero-pleasing output" do
         
     | 
| 
      
 97 
     | 
    
         
            +
                      invoice = make_invoice('45.00', '5.00')
         
     | 
| 
      
 98 
     | 
    
         
            +
                      invoice.add_item('description', '30.00', nil, '123 - Some stuff', 'No VAT')
         
     | 
| 
      
 99 
     | 
    
         
            +
                      
         
     | 
| 
      
 100 
     | 
    
         
            +
                      @fake_csv.expects(:<<).with(['Merchant', 'reference UID', '20/07/2009', '20/07/2009', '40.00', '5.00', '45.00', 'description', '1', '30.00', '123 - Some stuff', 'No VAT', nil, nil, nil, nil, nil])
         
     | 
| 
      
 101 
     | 
    
         
            +
                      
         
     | 
| 
      
 102 
     | 
    
         
            +
                      invoice.serialise_to_csv(@fake_csv)
         
     | 
| 
      
 103 
     | 
    
         
            +
                    end
         
     | 
| 
      
 104 
     | 
    
         
            +
                  end
         
     | 
| 
      
 105 
     | 
    
         
            +
                  
         
     | 
| 
      
 106 
     | 
    
         
            +
                  describe "multi-item invoices" do
         
     | 
| 
      
 107 
     | 
    
         
            +
                    it "should produce sensible Xero-pleasing output" do
         
     | 
| 
      
 108 
     | 
    
         
            +
                      invoice = make_invoice('75.00', '5.00')
         
     | 
| 
      
 109 
     | 
    
         
            +
                      invoice.add_item('thing', '30.00', nil, '123 - Some stuff', 'No VAT')
         
     | 
| 
      
 110 
     | 
    
         
            +
                      invoice.add_item('other thing', '23.00', '3.00', '234 - Some other stuff', 'VAT - 15%')
         
     | 
| 
      
 111 
     | 
    
         
            +
                      
         
     | 
| 
      
 112 
     | 
    
         
            +
                      @fake_csv.expects(:<<).with(['Merchant', 'reference UID', '20/07/2009', '20/07/2009', '70.00', '5.00', '75.00', 'thing', '1', '30.00', '123 - Some stuff', 'No VAT', nil, nil, nil, nil, nil])
         
     | 
| 
      
 113 
     | 
    
         
            +
                      @fake_csv.expects(:<<).with([nil, 'reference UID', nil, nil, nil, nil, nil, 'other thing', '1', '20.00', '234 - Some other stuff', '15% (VAT on expenses)', '3.00', nil, nil, nil, nil])
         
     | 
| 
      
 114 
     | 
    
         
            +
                      
         
     | 
| 
      
 115 
     | 
    
         
            +
                      invoice.serialise_to_csv(@fake_csv)
         
     | 
| 
      
 116 
     | 
    
         
            +
                    end
         
     | 
| 
      
 117 
     | 
    
         
            +
                  end
         
     | 
| 
      
 118 
     | 
    
         
            +
                  
         
     | 
| 
      
 119 
     | 
    
         
            +
                  describe "foreign-currency invoices" do
         
     | 
| 
      
 120 
     | 
    
         
            +
                    it "should stick the currency after the merchant so they can be picked out after import" do
         
     | 
| 
      
 121 
     | 
    
         
            +
                      invoice = make_invoice('45.00', '5.00', 'EUR')
         
     | 
| 
      
 122 
     | 
    
         
            +
                      invoice.add_item('description', '30.00', nil, '123 - Some stuff', 'No VAT')
         
     | 
| 
      
 123 
     | 
    
         
            +
                      
         
     | 
| 
      
 124 
     | 
    
         
            +
                      @fake_csv.expects(:<<).with(['Merchant (EUR)', 'reference UID', '20/07/2009', '20/07/2009', '40.00', '5.00', '45.00', 'description', '1', '30.00', '123 - Some stuff', 'No VAT', nil, nil, nil, nil, nil])
         
     | 
| 
      
 125 
     | 
    
         
            +
                      
         
     | 
| 
      
 126 
     | 
    
         
            +
                      invoice.serialise_to_csv(@fake_csv)
         
     | 
| 
      
 127 
     | 
    
         
            +
                    end
         
     | 
| 
      
 128 
     | 
    
         
            +
                  end
         
     | 
| 
      
 129 
     | 
    
         
            +
                end
         
     | 
| 
      
 130 
     | 
    
         
            +
              end
         
     | 
| 
      
 131 
     | 
    
         
            +
            end
         
     | 
    
        data/spec/spec.opts
    ADDED
    
    
    
        data/spec/spec_helper.rb
    ADDED
    
    | 
         @@ -0,0 +1,26 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            require 'rubygems'
         
     | 
| 
      
 2 
     | 
    
         
            +
            require 'mocha'
         
     | 
| 
      
 3 
     | 
    
         
            +
            $:.push(File.expand_path(File.dirname(__FILE__) + '/../lib'))
         
     | 
| 
      
 4 
     | 
    
         
            +
            Spec::Runner.configure do |config|
         
     | 
| 
      
 5 
     | 
    
         
            +
              config.mock_with :mocha
         
     | 
| 
      
 6 
     | 
    
         
            +
            end
         
     | 
| 
      
 7 
     | 
    
         
            +
             
     | 
| 
      
 8 
     | 
    
         
            +
            require 'paperless_to_xero'
         
     | 
| 
      
 9 
     | 
    
         
            +
             
     | 
| 
      
 10 
     | 
    
         
            +
            class Class
         
     | 
| 
      
 11 
     | 
    
         
            +
              def publicize_methods
         
     | 
| 
      
 12 
     | 
    
         
            +
                saved_private_instance_methods = self.private_instance_methods
         
     | 
| 
      
 13 
     | 
    
         
            +
                saved_protected_instance_methods = self.protected_instance_methods
         
     | 
| 
      
 14 
     | 
    
         
            +
                self.class_eval do
         
     | 
| 
      
 15 
     | 
    
         
            +
                  public *saved_private_instance_methods
         
     | 
| 
      
 16 
     | 
    
         
            +
                  public *saved_protected_instance_methods
         
     | 
| 
      
 17 
     | 
    
         
            +
                end
         
     | 
| 
      
 18 
     | 
    
         
            +
                
         
     | 
| 
      
 19 
     | 
    
         
            +
                yield
         
     | 
| 
      
 20 
     | 
    
         
            +
                
         
     | 
| 
      
 21 
     | 
    
         
            +
                self.class_eval do
         
     | 
| 
      
 22 
     | 
    
         
            +
                  private *saved_private_instance_methods
         
     | 
| 
      
 23 
     | 
    
         
            +
                  protected *saved_protected_instance_methods
         
     | 
| 
      
 24 
     | 
    
         
            +
                end
         
     | 
| 
      
 25 
     | 
    
         
            +
              end
         
     | 
| 
      
 26 
     | 
    
         
            +
            end
         
     | 
    
        metadata
    ADDED
    
    | 
         @@ -0,0 +1,94 @@ 
     | 
|
| 
      
 1 
     | 
    
         
            +
            --- !ruby/object:Gem::Specification 
         
     | 
| 
      
 2 
     | 
    
         
            +
            name: paperless_to_xero
         
     | 
| 
      
 3 
     | 
    
         
            +
            version: !ruby/object:Gem::Version 
         
     | 
| 
      
 4 
     | 
    
         
            +
              version: 1.1.1
         
     | 
| 
      
 5 
     | 
    
         
            +
            platform: ruby
         
     | 
| 
      
 6 
     | 
    
         
            +
            authors: 
         
     | 
| 
      
 7 
     | 
    
         
            +
            - Matt Patterson
         
     | 
| 
      
 8 
     | 
    
         
            +
            autorequire: 
         
     | 
| 
      
 9 
     | 
    
         
            +
            bindir: bin
         
     | 
| 
      
 10 
     | 
    
         
            +
            cert_chain: []
         
     | 
| 
      
 11 
     | 
    
         
            +
             
     | 
| 
      
 12 
     | 
    
         
            +
            date: 2009-07-22 00:00:00 +01:00
         
     | 
| 
      
 13 
     | 
    
         
            +
            default_executable: 
         
     | 
| 
      
 14 
     | 
    
         
            +
            dependencies: 
         
     | 
| 
      
 15 
     | 
    
         
            +
            - !ruby/object:Gem::Dependency 
         
     | 
| 
      
 16 
     | 
    
         
            +
              name: rspec
         
     | 
| 
      
 17 
     | 
    
         
            +
              type: :development
         
     | 
| 
      
 18 
     | 
    
         
            +
              version_requirement: 
         
     | 
| 
      
 19 
     | 
    
         
            +
              version_requirements: !ruby/object:Gem::Requirement 
         
     | 
| 
      
 20 
     | 
    
         
            +
                requirements: 
         
     | 
| 
      
 21 
     | 
    
         
            +
                - - ">="
         
     | 
| 
      
 22 
     | 
    
         
            +
                  - !ruby/object:Gem::Version 
         
     | 
| 
      
 23 
     | 
    
         
            +
                    version: "0"
         
     | 
| 
      
 24 
     | 
    
         
            +
                version: 
         
     | 
| 
      
 25 
     | 
    
         
            +
            description: |-
         
     | 
| 
      
 26 
     | 
    
         
            +
              = Paperless-to-Xero
         
     | 
| 
      
 27 
     | 
    
         
            +
              
         
     | 
| 
      
 28 
     | 
    
         
            +
              A simple translator which takes a CSV file from Mariner's Paperless receipt/document management software and makes a Xero accounts payable invoice CSV, for import into Xero.
         
     | 
| 
      
 29 
     | 
    
         
            +
              
         
     | 
| 
      
 30 
     | 
    
         
            +
              Formatting in Paperless is very important, so you probably want to wait until I've written the docs
         
     | 
| 
      
 31 
     | 
    
         
            +
            email: matt@reprocessed.org
         
     | 
| 
      
 32 
     | 
    
         
            +
            executables: 
         
     | 
| 
      
 33 
     | 
    
         
            +
            - paperless_to_xero
         
     | 
| 
      
 34 
     | 
    
         
            +
            extensions: []
         
     | 
| 
      
 35 
     | 
    
         
            +
             
     | 
| 
      
 36 
     | 
    
         
            +
            extra_rdoc_files: 
         
     | 
| 
      
 37 
     | 
    
         
            +
            - README.rdoc
         
     | 
| 
      
 38 
     | 
    
         
            +
            files: 
         
     | 
| 
      
 39 
     | 
    
         
            +
            - Rakefile
         
     | 
| 
      
 40 
     | 
    
         
            +
            - README.rdoc
         
     | 
| 
      
 41 
     | 
    
         
            +
            - bin/paperless_to_xero
         
     | 
| 
      
 42 
     | 
    
         
            +
            - spec/fixtures/end_to_end-input.csv
         
     | 
| 
      
 43 
     | 
    
         
            +
            - spec/fixtures/end_to_end-output.csv
         
     | 
| 
      
 44 
     | 
    
         
            +
            - spec/fixtures/multi-ex-vat.csv
         
     | 
| 
      
 45 
     | 
    
         
            +
            - spec/fixtures/multi-foreign.csv
         
     | 
| 
      
 46 
     | 
    
         
            +
            - spec/fixtures/multi-item.csv
         
     | 
| 
      
 47 
     | 
    
         
            +
            - spec/fixtures/single-1000.csv
         
     | 
| 
      
 48 
     | 
    
         
            +
            - spec/fixtures/single-basic.csv
         
     | 
| 
      
 49 
     | 
    
         
            +
            - spec/fixtures/single-dkk.csv
         
     | 
| 
      
 50 
     | 
    
         
            +
            - spec/fixtures/single-foreign.csv
         
     | 
| 
      
 51 
     | 
    
         
            +
            - spec/fixtures/single-no-vat.csv
         
     | 
| 
      
 52 
     | 
    
         
            +
            - spec/fixtures/single-zero_rated.csv
         
     | 
| 
      
 53 
     | 
    
         
            +
            - spec/paperless_to_xero/converter_spec.rb
         
     | 
| 
      
 54 
     | 
    
         
            +
            - spec/paperless_to_xero/invoice_item_spec.rb
         
     | 
| 
      
 55 
     | 
    
         
            +
            - spec/paperless_to_xero/invoice_spec.rb
         
     | 
| 
      
 56 
     | 
    
         
            +
            - spec/spec.opts
         
     | 
| 
      
 57 
     | 
    
         
            +
            - spec/spec_helper.rb
         
     | 
| 
      
 58 
     | 
    
         
            +
            - lib/paperless_to_xero/converter.rb
         
     | 
| 
      
 59 
     | 
    
         
            +
            - lib/paperless_to_xero/decimal_helpers.rb
         
     | 
| 
      
 60 
     | 
    
         
            +
            - lib/paperless_to_xero/invoice.rb
         
     | 
| 
      
 61 
     | 
    
         
            +
            - lib/paperless_to_xero/invoice_item.rb
         
     | 
| 
      
 62 
     | 
    
         
            +
            - lib/paperless_to_xero/version.rb
         
     | 
| 
      
 63 
     | 
    
         
            +
            - lib/paperless_to_xero.rb
         
     | 
| 
      
 64 
     | 
    
         
            +
            has_rdoc: true
         
     | 
| 
      
 65 
     | 
    
         
            +
            homepage: http://reprocessed.org/
         
     | 
| 
      
 66 
     | 
    
         
            +
            licenses: []
         
     | 
| 
      
 67 
     | 
    
         
            +
             
     | 
| 
      
 68 
     | 
    
         
            +
            post_install_message: 
         
     | 
| 
      
 69 
     | 
    
         
            +
            rdoc_options: 
         
     | 
| 
      
 70 
     | 
    
         
            +
            - --main
         
     | 
| 
      
 71 
     | 
    
         
            +
            - README.rdoc
         
     | 
| 
      
 72 
     | 
    
         
            +
            require_paths: 
         
     | 
| 
      
 73 
     | 
    
         
            +
            - lib
         
     | 
| 
      
 74 
     | 
    
         
            +
            required_ruby_version: !ruby/object:Gem::Requirement 
         
     | 
| 
      
 75 
     | 
    
         
            +
              requirements: 
         
     | 
| 
      
 76 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 77 
     | 
    
         
            +
                - !ruby/object:Gem::Version 
         
     | 
| 
      
 78 
     | 
    
         
            +
                  version: "0"
         
     | 
| 
      
 79 
     | 
    
         
            +
              version: 
         
     | 
| 
      
 80 
     | 
    
         
            +
            required_rubygems_version: !ruby/object:Gem::Requirement 
         
     | 
| 
      
 81 
     | 
    
         
            +
              requirements: 
         
     | 
| 
      
 82 
     | 
    
         
            +
              - - ">="
         
     | 
| 
      
 83 
     | 
    
         
            +
                - !ruby/object:Gem::Version 
         
     | 
| 
      
 84 
     | 
    
         
            +
                  version: "0"
         
     | 
| 
      
 85 
     | 
    
         
            +
              version: 
         
     | 
| 
      
 86 
     | 
    
         
            +
            requirements: []
         
     | 
| 
      
 87 
     | 
    
         
            +
             
     | 
| 
      
 88 
     | 
    
         
            +
            rubyforge_project: paperless_to_xero
         
     | 
| 
      
 89 
     | 
    
         
            +
            rubygems_version: 1.3.4
         
     | 
| 
      
 90 
     | 
    
         
            +
            signing_key: 
         
     | 
| 
      
 91 
     | 
    
         
            +
            specification_version: 3
         
     | 
| 
      
 92 
     | 
    
         
            +
            summary: Convert Paperless CSV exports to Xero invoice import CSV
         
     | 
| 
      
 93 
     | 
    
         
            +
            test_files: []
         
     | 
| 
      
 94 
     | 
    
         
            +
             
     |