sie 1.0.0

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/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,2 @@
1
+ --color
2
+ --format progress
data/.rvmrc ADDED
@@ -0,0 +1 @@
1
+ rvm ruby-1.9.3-p194-falcon@sie --create
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in sie.gemspec
4
+ gemspec
data/README.md ADDED
@@ -0,0 +1,64 @@
1
+ # Sie
2
+
3
+ ## Installation
4
+
5
+ Add this line to your application's Gemfile:
6
+
7
+ gem 'sie'
8
+
9
+ And then execute:
10
+
11
+ $ bundle
12
+
13
+ Or install it yourself as:
14
+
15
+ $ gem install sie
16
+
17
+ ## Developing
18
+
19
+ First time setup:
20
+
21
+ script/bootstrap
22
+
23
+ Running tests:
24
+
25
+ rake
26
+
27
+ Getting the latest code and gems:
28
+
29
+ script/refresh
30
+
31
+ ## Usage
32
+
33
+
34
+ ## Contributing
35
+
36
+ 1. Fork it
37
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
38
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
39
+ 4. Push to the branch (`git push origin my-new-feature`)
40
+ 5. Create new Pull Request
41
+
42
+ ## Credits and license
43
+
44
+ By [Barsoom](http://barsoom.se) under the MIT license:
45
+
46
+ > Copyright (c) 2013 Barsoom AB
47
+ >
48
+ > Permission is hereby granted, free of charge, to any person obtaining a copy
49
+ > of this software and associated documentation files (the "Software"), to deal
50
+ > in the Software without restriction, including without limitation the rights
51
+ > to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
52
+ > copies of the Software, and to permit persons to whom the Software is
53
+ > furnished to do so, subject to the following conditions:
54
+ >
55
+ > The above copyright notice and this permission notice shall be included in
56
+ > all copies or substantial portions of the Software.
57
+ >
58
+ > THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
59
+ > IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
60
+ > FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
61
+ > AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
62
+ > LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
63
+ > OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
64
+ > THE SOFTWARE.
data/Rakefile ADDED
@@ -0,0 +1,15 @@
1
+ require "bundler/gem_tasks"
2
+
3
+ namespace :spec do
4
+ task :unit do
5
+ puts "Running unit tests:"
6
+ system("rspec spec/unit/**_spec.rb") || exit(1)
7
+ end
8
+
9
+ task :integration do
10
+ puts "Running integrated tests:"
11
+ system("rspec spec/integration/**_spec.rb") || exit(1)
12
+ end
13
+ end
14
+
15
+ task :default => [ :"spec:unit", :"spec:integration" ]
data/lib/sie.rb ADDED
@@ -0,0 +1,6 @@
1
+ require "sie/version"
2
+ require "sie/parser"
3
+ require "sie/document"
4
+
5
+ module Sie
6
+ end
@@ -0,0 +1,107 @@
1
+ require "attr_extras"
2
+ require "sie/document/financial_years"
3
+ require "sie/document/voucher_series"
4
+ require "sie/document/renderer"
5
+ require "active_support/core_ext/module/delegation"
6
+
7
+ class Sie::Document
8
+ pattr_initialize :data_source
9
+
10
+ def render
11
+ add_header
12
+ add_financial_years
13
+ add_accounts
14
+ add_balances
15
+ add_vouchers
16
+
17
+ renderer.render
18
+ end
19
+
20
+ private
21
+
22
+ delegate :program, :program_version, :generated_on, :company_name,
23
+ :accounts, :balance_account_numbers, :closing_account_numbers,
24
+ :balance_before, :each_voucher, :from_date, :to_date, :financial_year_start_month,
25
+ to: :data_source
26
+
27
+ def add_header
28
+ add_line("FLAGGA", 0)
29
+ add_line("PROGRAM", program, program_version)
30
+ add_line("FORMAT", "PC8")
31
+ add_line("GEN", generated_on)
32
+ add_line("SIETYP", 4)
33
+ add_line("FNAMN", company_name)
34
+ end
35
+
36
+ def add_financial_years
37
+ financial_years.each_with_index do |date_range, index|
38
+ add_line("RAR", -index, date_range.begin, date_range.end)
39
+ end
40
+ end
41
+
42
+ def add_accounts
43
+ accounts.each do |account|
44
+ number = account.fetch(:number)
45
+ description = account.fetch(:description)
46
+
47
+ add_line("KONTO", number, description)
48
+ end
49
+ end
50
+
51
+ def add_balances
52
+ financial_years.each_with_index do |date_range, index|
53
+ add_balance_rows("IB", -index, balance_account_numbers, date_range.begin)
54
+ add_balance_rows("UB", -index, balance_account_numbers, date_range.end)
55
+ add_balance_rows("RES", -index, closing_account_numbers, date_range.end)
56
+ end
57
+ end
58
+
59
+ def add_balance_rows(label, year_index, account_numbers, date, &block)
60
+ account_numbers.each do |account_number|
61
+ balance = balance_before(account_number, date)
62
+ add_line(label, year_index, account_number, balance)
63
+ end
64
+ end
65
+
66
+ def add_vouchers
67
+ each_voucher do |voucher|
68
+ add_voucher(voucher)
69
+ end
70
+ end
71
+
72
+ def add_voucher(opts)
73
+ creditor = opts.fetch(:creditor)
74
+ type = opts.fetch(:type)
75
+ number = opts.fetch(:number)
76
+ booked_on = opts.fetch(:booked_on)
77
+ description = opts.fetch(:description)
78
+ voucher_lines = opts.fetch(:voucher_lines)
79
+
80
+ add_line("VER", voucher_series(creditor, type), number, booked_on, description)
81
+
82
+ add_array do
83
+ voucher_lines.each do |line|
84
+ account_number = line.fetch(:account_number)
85
+ amount = line.fetch(:amount)
86
+ booked_on = line.fetch(:booked_on)
87
+ description = line.fetch(:description)
88
+
89
+ add_line("TRANS", account_number, Renderer::EMPTY_ARRAY, amount, booked_on, description)
90
+ end
91
+ end
92
+ end
93
+
94
+ delegate :add_line, :add_array, to: :renderer
95
+
96
+ def renderer
97
+ @renderer ||= Renderer.new
98
+ end
99
+
100
+ def financial_years
101
+ FinancialYears.between(financial_year_start_month, from_date, to_date).reverse
102
+ end
103
+
104
+ def voucher_series(creditor, type)
105
+ VoucherSeries.for(creditor, type)
106
+ end
107
+ end
@@ -0,0 +1,24 @@
1
+ class Sie::Document
2
+ class FinancialYears
3
+ def self.between(start_month, from_date, to_date)
4
+ result = []
5
+
6
+ from_date.upto(to_date) do |date|
7
+ next if result.any? { |r| r.cover?(date) }
8
+
9
+ start_of_year = Date.new(date.year, start_month, 1)
10
+ start_of_year = start_of_year <= date ? start_of_year : (start_of_year << 12)
11
+
12
+ a_year_later = start_of_year >> 11
13
+ end_of_year = Date.new(a_year_later.year, a_year_later.month, -1)
14
+
15
+ first_date = [start_of_year, from_date].max
16
+ last_date = [end_of_year, to_date].min
17
+
18
+ result << (first_date..last_date)
19
+ end
20
+
21
+ result
22
+ end
23
+ end
24
+ end
@@ -0,0 +1,48 @@
1
+ class Sie::Document::Renderer
2
+ EMPTY_ARRAY = :empty_array
3
+
4
+ def initialize
5
+ @io = StringIO.new
6
+ @io.set_encoding(Encoding::CP437)
7
+ end
8
+
9
+ def add_line(label, *values)
10
+ append ["##{ label }", *format_values(values)].join(" ")
11
+ end
12
+
13
+ def add_array
14
+ append "{"
15
+ yield
16
+ append "}"
17
+ end
18
+
19
+ def render
20
+ io.rewind
21
+ io.read
22
+ end
23
+
24
+ attr_private :io
25
+
26
+ private
27
+
28
+ def append(text)
29
+ io.puts(text)
30
+ end
31
+
32
+ def format_values(values)
33
+ values.map { |value| format_value(value) }
34
+ end
35
+
36
+ def format_value(value)
37
+ case value
38
+ when Date
39
+ value.strftime("%Y%m%d")
40
+ when EMPTY_ARRAY
41
+ "{}"
42
+ when Numeric
43
+ value.to_s
44
+ else
45
+ '"' + value.to_s.gsub('"', '\"') + '"'
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,20 @@
1
+ class Sie::Document
2
+ class VoucherSeries
3
+ DEBTOR_INVOICE = "KF"
4
+ DEBTOR_PAYMENT = "KI"
5
+ SUPPLIER_INVOICE = "LF"
6
+ SUPPLIER_PAYMENT = "KB"
7
+ OTHER = "LV"
8
+
9
+ def self.for(creditor, type)
10
+ case type
11
+ when :invoice
12
+ creditor ? SUPPLIER_INVOICE : DEBTOR_INVOICE
13
+ when :payment
14
+ creditor ? SUPPLIER_PAYMENT : DEBTOR_PAYMENT
15
+ else
16
+ OTHER
17
+ end
18
+ end
19
+ end
20
+ end
data/lib/sie/parser.rb ADDED
@@ -0,0 +1,40 @@
1
+ require "attr_extras"
2
+ require "sie/parser/line_parser"
3
+
4
+ module Sie
5
+ class Parser
6
+ # TODO: Could this format knowledge be centrailized somewhere, some
7
+ # of this is duplicated in Character.
8
+ BEGINNING_OF_ARRAY = "{"
9
+ END_OF_ARRAY = "}"
10
+ ENTRY = /^#/
11
+
12
+ def parse(io)
13
+ stack = []
14
+ sie_file = SieFile.new
15
+ current = sie_file
16
+
17
+ io.each_line do |line|
18
+ line = line.chomp
19
+
20
+ case line
21
+ when BEGINNING_OF_ARRAY
22
+ stack << current
23
+ current = current.entries.last
24
+ when END_OF_ARRAY
25
+ current = stack.pop
26
+ when ENTRY
27
+ current.entries << parse_line(line)
28
+ end
29
+ end
30
+
31
+ sie_file
32
+ end
33
+
34
+ private
35
+
36
+ def parse_line(line)
37
+ LineParser.new(line).parse
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,15 @@
1
+ module Sie
2
+ class Parser
3
+ class Entry
4
+ attr_reader :label
5
+ attr_accessor :attributes
6
+ attr_accessor :entries
7
+
8
+ def initialize(label)
9
+ @label = label
10
+ @attributes = {}
11
+ @entries = []
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,41 @@
1
+ module Sie
2
+ class Parser
3
+ ENTRY_TYPES = {
4
+ "adress" => [ 'kontakt', 'utdelningsadr', 'postadr', 'tel' ],
5
+ "bkod" => [ 'SNI-kod' ],
6
+ "dim" => [ 'dimensionsnr', 'namn' ],
7
+ "enhet" => [ 'kontonr', 'enhet' ],
8
+ "flagga" => [ 'x' ],
9
+ "fnamn" => [ 'foretagsnamn' ],
10
+ "fnr" => [ 'foretagsid' ],
11
+ "format" => [ 'PC8' ],
12
+ "ftyp" => [ 'foretagstyp' ],
13
+ "gen" => [ 'datum', 'sign' ],
14
+ "ib" => [ 'arsnr', 'konto', 'saldo', 'kvantitet' ],
15
+ "konto" => [ 'kontonr', 'kontonamn' ],
16
+ "kptyp" => [ 'typ' ],
17
+ "ktyp" => [ 'kontonr', 'kontotyp' ],
18
+ "objekt" => [ 'dimensionsnr', 'objektnr', 'objektnamn' ],
19
+ "oib" => [ 'arsnr', 'konto', { name: 'objekt', type: [ 'dimensionsnr', 'objektnr' ] }, 'saldo', 'kvantitet' ],
20
+ "omfattn" => [ 'datum' ],
21
+ "orgnr" => [ 'orgnr', 'forvnr', 'verknr' ],
22
+ "oub" => [ 'arsnr', 'konto', { name: 'objekt', type: [ 'dimensionsnr', 'objektnr' ] }, 'saldo', 'kvantitet' ],
23
+ "pbudget" => [ 'arsnr', 'period', 'konto', { name: 'objekt', type: [ 'dimensionsnr', 'objektnr' ] }, 'saldo', 'kvantitet' ],
24
+ "program" => [ 'programnamn', 'version' ],
25
+ "prosa" => [ 'text' ],
26
+ "psaldo" => [ 'arsnr', 'period', 'konto', { name: 'objekt', type: [ 'dimensionsnr', 'objektnr' ] }, 'saldo', 'kvantitet' ],
27
+ "rar" => [ 'arsnr', 'start', 'slut' ],
28
+ "res" => [ 'ars', 'konto', 'saldo', 'kvantitet' ],
29
+ "sietyp" => [ 'typnr' ],
30
+ "sru" => [ 'konto', 'SRU-kod' ],
31
+ "taxar" => [ 'ar' ],
32
+ "trans" => [ 'kontonr', { name: 'objektlista', type: [ 'dimensionsnr', 'objektnr' ], many: true }, 'belopp', 'transdat', 'transtext', 'kvantitet', 'sign' ],
33
+ "rtrans" => [ 'kontonr', { name: 'objektlista', type: [ 'dimensionsnr', 'objektnr' ], many: true }, 'belopp', 'transdat', 'transtext', 'kvantitet', 'sign' ],
34
+ "btrans" => [ 'kontonr', { name: 'objektlista', type: [ 'dimensionsnr', 'objektnr' ], many: true }, 'belopp', 'transdat', 'transtext', 'kvantitet', 'sign' ],
35
+ "ub" => [ 'arsnr', 'konto', 'saldo', 'kvantitet' ],
36
+ "underdim" => [ 'dimensionsnr', 'namn', 'superdimension' ],
37
+ "valuta" => [ 'valutakod' ],
38
+ "ver" => [ 'serie', 'vernr', 'verdatum', 'vertext', 'regdatum', 'sign' ]
39
+ }
40
+ end
41
+ end
@@ -0,0 +1,48 @@
1
+ require "sie/parser/tokenizer"
2
+ require "sie/parser/entry"
3
+ require "sie/parser/sie_file"
4
+
5
+ module Sie
6
+ class Parser
7
+ class LineParser
8
+ pattr_initialize :line
9
+
10
+ def parse
11
+ tokens = tokenize(line)
12
+ first_token = tokens.shift
13
+
14
+ entry = Entry.new(first_token.label)
15
+ entry_type = first_token.entry_type
16
+
17
+ entry_type.each_with_index do |entry_type, i|
18
+ break if i >= tokens.size
19
+
20
+ if entry_type.is_a?(Hash)
21
+ skip_array(tokens, i)
22
+ next
23
+ else
24
+ label = entry_type
25
+ entry.attributes[label] = tokens[i].value
26
+ end
27
+ end
28
+
29
+ entry
30
+ end
31
+
32
+ private
33
+
34
+ def tokenize(line)
35
+ Tokenizer.new(line).tokenize
36
+ end
37
+
38
+ def skip_array(tokens, i)
39
+ if tokens[i].is_a?(Tokenizer::BeginArrayToken) &&
40
+ !tokens[i+1].is_a?(Tokenizer::EndArrayToken)
41
+ raise "We currently don't support metadata within entries as we haven't had a need for it yet (the data between {} in #{line})."
42
+ end
43
+
44
+ tokens.reject! { |token| token.is_a?(Tokenizer::EndArrayToken) }
45
+ end
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,13 @@
1
+ module Sie
2
+ class Parser
3
+ class SieFile
4
+ def entries_with_label(label)
5
+ entries.select { |entry| entry.label == label }
6
+ end
7
+
8
+ def entries
9
+ @entries ||= []
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,90 @@
1
+ require "strscan"
2
+ require "sie/parser/tokenizer/token"
3
+ require "sie/parser/tokenizer/character"
4
+
5
+ module Sie
6
+ class Parser
7
+ class Tokenizer
8
+ pattr_initialize :line
9
+
10
+ def tokenize
11
+ @tokens = []
12
+ @consume = false
13
+ @quoted = false
14
+
15
+ loop do
16
+ move_to_next_character
17
+ break unless current_character.value
18
+
19
+ if consume?
20
+ if quoted?
21
+ consume_quoted_value
22
+ else
23
+ consume_unquoted_value
24
+ end
25
+ else
26
+ add_new_token
27
+ end
28
+ end
29
+
30
+ tokens
31
+ end
32
+
33
+ private
34
+
35
+ attr_query :consume?, :quoted?
36
+ attr_private :consume, :quoted, :tokens, :current_character
37
+
38
+ def move_to_next_character
39
+ @current_character = Character.new(scanner.getch)
40
+ end
41
+
42
+ def consume_quoted_value
43
+ if current_character.quote?
44
+ @quoted = false
45
+ else
46
+ add_to_current_token current_character
47
+ end
48
+ end
49
+
50
+ def consume_unquoted_value
51
+ if current_character.unquoted_data?
52
+ add_to_current_token current_character
53
+ else
54
+ @consume = false
55
+ end
56
+ end
57
+
58
+ def add_new_token
59
+ if current_character.entry?
60
+ @consume = true
61
+ add_token EntryToken.new
62
+ elsif current_character.beginning_of_array?
63
+ add_token BeginArrayToken.new
64
+ elsif current_character.end_of_array?
65
+ add_token EndArrayToken.new
66
+ elsif current_character.quote?
67
+ @consume = @quoted = true
68
+ add_token StringToken.new
69
+ elsif current_character.non_whitespace?
70
+ @consume = true
71
+ add_token StringToken.new(current_character.value)
72
+ elsif current_character.value != " "
73
+ raise "Unhandled character: #{current_character.value}"
74
+ end
75
+ end
76
+
77
+ def add_token(token)
78
+ tokens << token
79
+ end
80
+
81
+ def add_to_current_token(character)
82
+ tokens.last.value += character.value
83
+ end
84
+
85
+ def scanner
86
+ @scanner ||= StringScanner.new(line)
87
+ end
88
+ end
89
+ end
90
+ end
@@ -0,0 +1,34 @@
1
+ module Sie
2
+ class Parser
3
+ class Tokenizer
4
+ class Character
5
+ pattr_initialize :value
6
+ attr_reader :value
7
+
8
+ def unquoted_data?
9
+ non_whitespace? && !end_of_array?
10
+ end
11
+
12
+ def entry?
13
+ value == "#"
14
+ end
15
+
16
+ def beginning_of_array?
17
+ value == "{"
18
+ end
19
+
20
+ def end_of_array?
21
+ value == "}"
22
+ end
23
+
24
+ def quote?
25
+ value == '"'
26
+ end
27
+
28
+ def non_whitespace?
29
+ value != " " && value != "\t"
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,29 @@
1
+ require "sie/parser/entry_types"
2
+
3
+ module Sie
4
+ class Parser
5
+ class Tokenizer
6
+ class Token
7
+ attr_accessor :value
8
+
9
+ def initialize(value = "")
10
+ @value = value
11
+ end
12
+
13
+ def entry_type
14
+ Sie::Parser::ENTRY_TYPES.fetch(label)
15
+ end
16
+
17
+ def label
18
+ value.sub(/^#/, '').downcase
19
+ end
20
+ end
21
+
22
+ class EntryToken < Token; end
23
+ class BeginArrayToken < Token; end
24
+ class EndArrayToken < Token; end
25
+ class StringToken < Token; end
26
+ class ArrayToken < Token; end
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,3 @@
1
+ module Sie
2
+ VERSION = "1.0.0"
3
+ end
data/script/bootstrap ADDED
@@ -0,0 +1,3 @@
1
+ gem install bundler --no-ri --no-rdoc &&
2
+ bundle &&
3
+ rake
@@ -0,0 +1,6 @@
1
+ #!/bin/bash
2
+
3
+ export PATH=/opt/app_ruby/bin:$PATH
4
+ export GEM_HOME="$HOME/.gem"
5
+
6
+ bundle install --path /var/lib/jenkins/.bundle/sie 1> /dev/null
@@ -0,0 +1,5 @@
1
+ #!/bin/bash
2
+ set -e
3
+ source script/ci/support/env
4
+
5
+ notify_build "bundle exec rake"
data/script/refresh ADDED
@@ -0,0 +1,25 @@
1
+ #!/bin/bash
2
+
3
+ printf "\e[00;34mGetting the latest version\e[00m... "
4
+
5
+ (git status|grep 'nothing to commit' 1> /dev/null)
6
+
7
+ if [ ! $? -eq 0 ]
8
+ then
9
+ set -e
10
+ git stash 1> /dev/null
11
+ git pull --rebase 1> /dev/null
12
+ git stash pop 1> /dev/null
13
+ else
14
+ set -e
15
+ git pull --rebase 1> /dev/null
16
+ fi
17
+
18
+ echo "done"
19
+
20
+ printf "\e[00;34mBundling\e[00m... "
21
+ bundle 1> /dev/null
22
+ echo "done"
23
+ echo
24
+
25
+ rake
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Command to run the specs the correct way when triggered from the turbux vim plugin.
4
+
5
+ command = "rspec #{ARGV.first}"
6
+ exit(1) unless system(command)
data/sie.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'sie/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "sie"
8
+ spec.version = Sie::VERSION
9
+ spec.authors = ["Barsoom AB"]
10
+ spec.email = ["all@barsoom.se"]
11
+ spec.description = %q{SIE parser and generator}
12
+ spec.summary = %q{Parses and generates SIE files (http://sie.se/)}
13
+ spec.homepage = ""
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "attr_extras"
22
+ spec.add_dependency "activesupport"
23
+
24
+ spec.add_development_dependency "bundler", "~> 1.3"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rspec"
27
+ end
@@ -0,0 +1,28 @@
1
+ #FLAGGA 0
2
+ #PROGRAM "fooconomic" "1.0"
3
+ #FORMAT PC8
4
+ #GEN 20130101
5
+ #SIETYP 4
6
+ #ORGNR 555555-5555
7
+ #FNAMN "Foocorp"
8
+ #ADRESS "Foocorp" "" "12345 City" "555-12345"
9
+ #KONTO 1510 "Accounts receivable"
10
+ #KTYP 1510 T
11
+ #SRU 1510 204
12
+ #KONTO 1930 "Bank account"
13
+ #KTYP 1930 T
14
+ #SRU 1930 200
15
+ #KONTO 2440 Supplier
16
+ #KTYP 2440 S
17
+ #SRU 2440 300
18
+ #VER A 1 20130101 "Invoice" 20120105
19
+ {
20
+ #TRANS 1510 {} -200 20130101 "Coffee machine"
21
+ #TRANS 4100 {} 180 20130101 "Coffee machine"
22
+ #TRANS 2611 {} -20 20130101 "VAT"
23
+ }
24
+ #VER A 2 20130102 "Payment" 20120105
25
+ {
26
+ #TRANS 1510 {} -200 20130101 "Payment: Coffee machine"
27
+ #TRANS 1970 {} 200 20130101 "Payment: Coffee machine"
28
+ }
@@ -0,0 +1,24 @@
1
+ # encoding: utf-8
2
+
3
+ require "spec_helper"
4
+
5
+ describe Sie::Parser do
6
+ it "parses a file" do
7
+ parser = Sie::Parser.new
8
+
9
+ open_file("fixtures/sie_file.se") do |f|
10
+ sie_file = parser.parse(f)
11
+
12
+ expect(sie_file.entries_with_label("fnamn").first.attributes["foretagsnamn"]).to eq("Foocorp")
13
+
14
+ account = sie_file.entries_with_label("konto").first
15
+ expect(account.attributes["kontonr"]).to eq("1510")
16
+ expect(account.attributes["kontonamn"]).to eq("Accounts receivable")
17
+ expect(sie_file.entries_with_label("ver").size).to eq(2)
18
+ end
19
+ end
20
+
21
+ def open_file(fixture_file)
22
+ File.open(File.join(File.dirname(__FILE__), "../#{fixture_file}"))
23
+ end
24
+ end
@@ -0,0 +1,5 @@
1
+ require "sie"
2
+
3
+ RSpec.configure do |config|
4
+ config.order = 'random'
5
+ end
@@ -0,0 +1,38 @@
1
+ require "spec_helper"
2
+
3
+ describe Sie::Document::FinancialYears, ".between" do
4
+ it "gives us the financial years between from_date and to_date" do
5
+ Sie::Document::FinancialYears.between(
6
+ 1,
7
+ Date.new(2011, 9, 1),
8
+ Date.new(2012, 12, 31)
9
+ ).should == [
10
+ Date.new(2011, 9, 1)..Date.new(2011, 12, 31),
11
+ Date.new(2012, 1, 1)..Date.new(2012, 12, 31),
12
+ ]
13
+ end
14
+
15
+ it "gives us the financial years between from_date and to_date" do
16
+ Sie::Document::FinancialYears.between(
17
+ 1,
18
+ Date.new(2011, 9, 1),
19
+ Date.new(2013, 12, 31)
20
+ ).should == [
21
+ Date.new(2011, 9, 1)..Date.new(2011, 12, 31),
22
+ Date.new(2012, 1, 1)..Date.new(2012, 12, 31),
23
+ Date.new(2013, 1, 1)..Date.new(2013, 12, 31),
24
+ ]
25
+ end
26
+
27
+ it "gives us the financial years between from_date and to_date with a different financial year start month" do
28
+ Sie::Document::FinancialYears.between(
29
+ 5,
30
+ Date.new(2011, 9, 1),
31
+ Date.new(2014, 1, 10)
32
+ ).should == [
33
+ Date.new(2011, 9, 1)..Date.new(2012, 4, 30),
34
+ Date.new(2012, 5, 1)..Date.new(2013, 4, 30),
35
+ Date.new(2013, 5, 1)..Date.new(2014, 1, 10),
36
+ ]
37
+ end
38
+ end
@@ -0,0 +1,41 @@
1
+ require "spec_helper"
2
+
3
+ describe Sie::Document::VoucherSeries, ".for" do
4
+ subject(:series) { Sie::Document::VoucherSeries.for(creditor, type) }
5
+
6
+ let(:type) { :invoice }
7
+
8
+ context "when on the creditor side" do
9
+ let(:creditor) { true }
10
+
11
+ context "with an invoice" do
12
+ let(:type) { :invoice }
13
+ it { should == "LF" }
14
+ end
15
+
16
+ context "with a payment" do
17
+ let(:type) { :payment }
18
+ it { should == "KB" }
19
+ end
20
+ end
21
+
22
+ context "when on the debtor side" do
23
+ let(:creditor) { false }
24
+
25
+ context "with an invoice" do
26
+ let(:type) { :invoice }
27
+ it { should == "KF" }
28
+ end
29
+
30
+ context "with a payment" do
31
+ let(:type) { :payment }
32
+ it { should == "KI" }
33
+ end
34
+ end
35
+
36
+ context "when neither a payment or invoice" do
37
+ let(:creditor) { false }
38
+ let(:type) { :manual_bookable }
39
+ it { should == "LV" }
40
+ end
41
+ end
@@ -0,0 +1,181 @@
1
+ # encoding: utf-8
2
+ require "spec_helper"
3
+ require "sie"
4
+ require "active_support/core_ext/date/calculations"
5
+
6
+ describe Sie::Document, "#render" do
7
+ let(:from_date) { Date.new(2011, 9, 1) }
8
+ let(:to_date) { Date.new(2013, 12, 31) }
9
+ let(:generated_on) { Date.yesterday }
10
+ let(:accounts) {
11
+ [
12
+ number: 1500, description: "Customer ledger"
13
+ ]
14
+ }
15
+ let(:vouchers) {
16
+ [
17
+ {
18
+ creditor: false, type: :invoice, number: 1, booked_on: from_date + 2, description: "Invoice 1",
19
+ voucher_lines: [
20
+ { account_number: "1500", amount: 512.0, booked_on: from_date + 2, description: "Item 1" },
21
+ { account_number: "3100", amount: -512.0, booked_on: from_date + 2, description: "Item 1" },
22
+ ]
23
+ },
24
+ {
25
+ creditor: true, type: :payment, number: 2, booked_on: from_date + 365, description: "Payout 1",
26
+ voucher_lines: [
27
+ { account_number: "2400", amount: 256.0, booked_on: from_date + 365, description: "Payout line 1" },
28
+ { account_number: "1970", amount: -256.0, booked_on: from_date + 365, description: "Payout line 2" },
29
+ ]
30
+ }
31
+ ]
32
+ }
33
+
34
+ class TestDataSource
35
+ attr_accessor :program, :program_version, :generated_on, :company_name,
36
+ :accounts, :balance_account_numbers, :closing_account_numbers,
37
+ :vouchers, :from_date, :to_date, :financial_year_start_month
38
+
39
+ # vouchers is not part of the expected interface so making it private.
40
+ #
41
+ # Sie::Document uses #each_voucher so that you can build documents for huge sets of vouchers
42
+ # by loading them in batches.
43
+ private :vouchers
44
+
45
+ def initialize(hash)
46
+ hash.each do |k, v|
47
+ public_send("#{k}=", v)
48
+ end
49
+ end
50
+
51
+ def each_voucher(&block)
52
+ vouchers.each(&block)
53
+ end
54
+
55
+ def balance_before(account_number, date)
56
+ # Faking a fetch based on date and number
57
+ account_number.to_i + (date.mday * 100).to_f
58
+ end
59
+ end
60
+
61
+ let(:doc) {
62
+ data_source = TestDataSource.new(
63
+ from_date: from_date,
64
+ to_date: to_date,
65
+ accounts: accounts,
66
+ vouchers: vouchers,
67
+ program: "Foonomic",
68
+ program_version: "3.11",
69
+ generated_on: generated_on,
70
+ company_name: "Foocorp",
71
+ financial_year_start_month: 1,
72
+ balance_account_numbers: [ "1500", "2400" ],
73
+ closing_account_numbers: [ "3100" ]
74
+ )
75
+ doc = Sie::Document.new(data_source)
76
+ }
77
+
78
+ let(:sie_file) { Sie::Parser.new.parse(doc.render) }
79
+
80
+ it "adds a header" do
81
+ expect(entry_attribute("flagga", "x")).to eq("0")
82
+ expect(entry_attribute("program", "programnamn")).to eq("Foonomic")
83
+ expect(entry_attribute("program", "version")).to eq("3.11")
84
+ expect(entry_attribute("format", "PC8")).to eq("PC8")
85
+ expect(entry_attribute("gen", "datum")).to eq(generated_on.strftime("%Y%m%d"))
86
+ expect(entry_attribute("sietyp", "typnr")).to eq("4")
87
+ expect(entry_attribute("fnamn", "foretagsnamn")).to eq("Foocorp")
88
+ end
89
+
90
+ it "has accounting years" do
91
+ expect(indexed_entry_attribute("rar", 0, "arsnr")).to eq("0")
92
+ expect(indexed_entry_attribute("rar", 0, "start")).to eq("20130101")
93
+ expect(indexed_entry_attribute("rar", 0, "slut")).to eq("20131231")
94
+ expect(indexed_entry_attribute("rar", 1, "arsnr")).to eq("-1")
95
+ expect(indexed_entry_attribute("rar", 1, "start")).to eq("20120101")
96
+ expect(indexed_entry_attribute("rar", 1, "slut")).to eq("20121231")
97
+ expect(indexed_entry_attribute("rar", 2, "arsnr")).to eq("-2")
98
+ expect(indexed_entry_attribute("rar", 2, "start")).to eq("20110901")
99
+ expect(indexed_entry_attribute("rar", 2, "slut")).to eq("20111231")
100
+ end
101
+
102
+ it "has accounts" do
103
+ expect(indexed_entry_attributes("konto", 0)).to eq("kontonr" => "1500", "kontonamn" => "Customer ledger")
104
+ end
105
+
106
+ it "has balances brought forward (ingående balans)" do
107
+ expect(indexed_entry_attributes("ib", 0)).to eq("arsnr" => "0", "konto" => "1500", "saldo" => "1600.0")
108
+ expect(indexed_entry_attributes("ib", 1)).to eq("arsnr" => "0", "konto" => "2400", "saldo" => "2500.0")
109
+ expect(indexed_entry_attributes("ib", 2)).to eq("arsnr" => "-1", "konto" => "1500", "saldo" => "1600.0")
110
+ expect(indexed_entry_attributes("ib", 3)).to eq("arsnr" => "-1", "konto" => "2400", "saldo" => "2500.0")
111
+ expect(indexed_entry_attributes("ib", 4)).to eq("arsnr" => "-2", "konto" => "1500", "saldo" => "1600.0")
112
+ expect(indexed_entry_attributes("ib", 5)).to eq("arsnr" => "-2", "konto" => "2400", "saldo" => "2500.0")
113
+ end
114
+
115
+ it "has balances carried forward (utgående balans)" do
116
+ expect(indexed_entry_attributes("ub", 0)).to eq("arsnr" => "0", "konto" => "1500", "saldo" => "4600.0")
117
+ expect(indexed_entry_attributes("ub", 1)).to eq("arsnr" => "0", "konto" => "2400", "saldo" => "5500.0")
118
+ expect(indexed_entry_attributes("ub", 2)).to eq("arsnr" => "-1", "konto" => "1500", "saldo" => "4600.0")
119
+ expect(indexed_entry_attributes("ub", 3)).to eq("arsnr" => "-1", "konto" => "2400", "saldo" => "5500.0")
120
+ expect(indexed_entry_attributes("ub", 4)).to eq("arsnr" => "-2", "konto" => "1500", "saldo" => "4600.0")
121
+ expect(indexed_entry_attributes("ub", 5)).to eq("arsnr" => "-2", "konto" => "2400", "saldo" => "5500.0")
122
+ end
123
+
124
+ it "has closing account balances (saldo för resultatkonto)" do
125
+ expect(indexed_entry_attributes("res", 0)).to eq("ars" => "0", "konto" => "3100", "saldo" => "6200.0")
126
+ expect(indexed_entry_attributes("res", 1)).to eq("ars" => "-1", "konto" => "3100", "saldo" => "6200.0")
127
+ expect(indexed_entry_attributes("res", 2)).to eq("ars" => "-2", "konto" => "3100", "saldo" => "6200.0")
128
+ end
129
+
130
+ it "has vouchers" do
131
+ expect(indexed_entry("ver", 0).attributes).to eq(
132
+ "serie" => "KF", "vernr" => "1",
133
+ "verdatum" => "20110903", "vertext" => "Invoice 1"
134
+ )
135
+ expect(indexed_voucher_entries(0)[0].attributes).to eq(
136
+ "kontonr" => "1500", "belopp" => "512.0",
137
+ "transdat" => "20110903", "transtext" => "Item 1"
138
+ )
139
+ expect(indexed_voucher_entries(0)[1].attributes).to eq(
140
+ "kontonr" => "3100", "belopp" => "-512.0",
141
+ "transdat" => "20110903", "transtext" => "Item 1"
142
+ )
143
+
144
+ expect(indexed_entry("ver", 1).attributes).to eq(
145
+ "serie" => "KB", "vernr" => "2",
146
+ "verdatum" => "20120831", "vertext" => "Payout 1"
147
+ )
148
+ expect(indexed_voucher_entries(1)[0].attributes).to eq(
149
+ "kontonr" => "2400", "belopp" => "256.0",
150
+ "transdat" => "20120831", "transtext" => "Payout line 1"
151
+ )
152
+ expect(indexed_voucher_entries(1)[1].attributes).to eq(
153
+ "kontonr" => "1970", "belopp" => "-256.0",
154
+ "transdat" => "20120831", "transtext" => "Payout line 2"
155
+ )
156
+ end
157
+
158
+ private
159
+
160
+ def entry_attribute(label, attribute)
161
+ indexed_entry_attribute(label, 0, attribute)
162
+ end
163
+
164
+ def indexed_entry_attribute(label, index, attribute)
165
+ indexed_entry_attributes(label, index).fetch(attribute) do
166
+ raise "Unknown attribute #{ attribute } in #{ entry.attributes.keys.inspect }"
167
+ end
168
+ end
169
+
170
+ def indexed_entry_attributes(label, index)
171
+ indexed_entry(label, index).attributes
172
+ end
173
+
174
+ def indexed_voucher_entries(index)
175
+ indexed_entry("ver", index).entries
176
+ end
177
+
178
+ def indexed_entry(label, index)
179
+ sie_file.entries_with_label(label)[index] or raise "No entry with label #{ label.inspect } found!"
180
+ end
181
+ end
@@ -0,0 +1,21 @@
1
+ require "spec_helper"
2
+ require "sie/parser/line_parser"
3
+
4
+ describe Sie::Parser::LineParser, "parse" do
5
+ it "parses lines from a sie file" do
6
+ parser = Sie::Parser::LineParser.new('#TRANS 2400 {} -200 20130101 "Foocorp expense"')
7
+ entry = parser.parse
8
+ expect(entry.label).to eq("trans")
9
+ expect(entry.attributes).to eq({
10
+ "kontonr" => "2400",
11
+ "belopp" => "-200",
12
+ "transdat" => "20130101",
13
+ "transtext" => "Foocorp expense"
14
+ })
15
+ end
16
+
17
+ it "fails if you have non empty metadata arrays until there is a need to support that" do
18
+ parser = Sie::Parser::LineParser.new('#TRANS 2400 { 1 "2" } -200 20130101 "Foocorp expense"')
19
+ expect(-> { parser.parse }).to raise_error(/don't support metadata/)
20
+ end
21
+ end
@@ -0,0 +1,42 @@
1
+ require "spec_helper"
2
+ require "sie/parser/tokenizer"
3
+
4
+ describe Sie::Parser::Tokenizer do
5
+ it "tokenizes the given line" do
6
+ tokenizer = Sie::Parser::Tokenizer.new('#TRANS 2400 {} -200 20130101 "Foocorp expense"')
7
+ tokens = tokenizer.tokenize
8
+
9
+ expect(token_table_for(tokens)).to eq([
10
+ [ "EntryToken", "TRANS" ],
11
+ [ "StringToken", "2400" ],
12
+ [ "BeginArrayToken", "" ],
13
+ [ "EndArrayToken", "" ],
14
+ [ "StringToken", "-200" ],
15
+ [ "StringToken", "20130101" ],
16
+ [ "StringToken", "Foocorp expense" ]
17
+ ])
18
+ end
19
+
20
+ it "can parse metadata arrays" do
21
+ tokenizer = Sie::Parser::Tokenizer.new('#TRANS 2400 { 1 "2" } -200 20130101 "Foocorp expense"')
22
+ tokens = tokenizer.tokenize
23
+
24
+ expect(token_table_for(tokens)).to eq([
25
+ [ "EntryToken", "TRANS" ],
26
+ [ "StringToken", "2400" ],
27
+ [ "BeginArrayToken", "" ],
28
+ [ "StringToken", "1" ],
29
+ [ "StringToken", "2" ],
30
+ [ "EndArrayToken", "" ],
31
+ [ "StringToken", "-200" ],
32
+ [ "StringToken", "20130101" ],
33
+ [ "StringToken", "Foocorp expense" ]
34
+ ])
35
+ end
36
+
37
+ def token_table_for(tokens)
38
+ tokens.map { |token|
39
+ [ token.class.name.split("::").last, token.value ]
40
+ }
41
+ end
42
+ end
@@ -0,0 +1,23 @@
1
+ require "spec_helper"
2
+ require "sie/parser"
3
+
4
+ describe Sie::Parser, "parse" do
5
+ it "parses sie data that includes arrays" do
6
+ parser = Sie::Parser.new
7
+ sie_file = parser.parse(<<DATA
8
+ #VER "LF" 2222 20130101 "Foocorp expense"
9
+ {
10
+ #TRANS 2400 {} -200 20130101 "Foocorp expense"
11
+ #TRANS 4100 {} 180 20130101 "Widgets from foocorp"
12
+ #TRANS 2611 {} -20 20130101 "VAT"
13
+ }
14
+ DATA
15
+ )
16
+
17
+ voucher_entry = sie_file.entries.first
18
+ expect(sie_file.entries.size).to eq(1)
19
+ expect(voucher_entry.attributes["verdatum"]).to eq("20130101")
20
+ expect(voucher_entry.entries.size).to eq(3)
21
+ expect(voucher_entry.entries.first.attributes["kontonr"]).to eq("2400")
22
+ end
23
+ end
metadata ADDED
@@ -0,0 +1,151 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: sie
3
+ version: !ruby/object:Gem::Version
4
+ version: 1.0.0
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Barsoom AB
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2013-11-25 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: attr_extras
16
+ requirement: &70103120492320 !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ! '>='
20
+ - !ruby/object:Gem::Version
21
+ version: '0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: *70103120492320
25
+ - !ruby/object:Gem::Dependency
26
+ name: activesupport
27
+ requirement: &70103120491900 !ruby/object:Gem::Requirement
28
+ none: false
29
+ requirements:
30
+ - - ! '>='
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :runtime
34
+ prerelease: false
35
+ version_requirements: *70103120491900
36
+ - !ruby/object:Gem::Dependency
37
+ name: bundler
38
+ requirement: &70103120491400 !ruby/object:Gem::Requirement
39
+ none: false
40
+ requirements:
41
+ - - ~>
42
+ - !ruby/object:Gem::Version
43
+ version: '1.3'
44
+ type: :development
45
+ prerelease: false
46
+ version_requirements: *70103120491400
47
+ - !ruby/object:Gem::Dependency
48
+ name: rake
49
+ requirement: &70103120490980 !ruby/object:Gem::Requirement
50
+ none: false
51
+ requirements:
52
+ - - ! '>='
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ type: :development
56
+ prerelease: false
57
+ version_requirements: *70103120490980
58
+ - !ruby/object:Gem::Dependency
59
+ name: rspec
60
+ requirement: &70103120490520 !ruby/object:Gem::Requirement
61
+ none: false
62
+ requirements:
63
+ - - ! '>='
64
+ - !ruby/object:Gem::Version
65
+ version: '0'
66
+ type: :development
67
+ prerelease: false
68
+ version_requirements: *70103120490520
69
+ description: SIE parser and generator
70
+ email:
71
+ - all@barsoom.se
72
+ executables: []
73
+ extensions: []
74
+ extra_rdoc_files: []
75
+ files:
76
+ - .gitignore
77
+ - .rspec
78
+ - .rvmrc
79
+ - Gemfile
80
+ - README.md
81
+ - Rakefile
82
+ - lib/sie.rb
83
+ - lib/sie/document.rb
84
+ - lib/sie/document/financial_years.rb
85
+ - lib/sie/document/renderer.rb
86
+ - lib/sie/document/voucher_series.rb
87
+ - lib/sie/parser.rb
88
+ - lib/sie/parser/entry.rb
89
+ - lib/sie/parser/entry_types.rb
90
+ - lib/sie/parser/line_parser.rb
91
+ - lib/sie/parser/sie_file.rb
92
+ - lib/sie/parser/tokenizer.rb
93
+ - lib/sie/parser/tokenizer/character.rb
94
+ - lib/sie/parser/tokenizer/token.rb
95
+ - lib/sie/version.rb
96
+ - script/bootstrap
97
+ - script/ci/support/env
98
+ - script/ci/tests.sh
99
+ - script/refresh
100
+ - script/turbux_rspec
101
+ - sie.gemspec
102
+ - spec/fixtures/sie_file.se
103
+ - spec/integration/parser_spec.rb
104
+ - spec/spec_helper.rb
105
+ - spec/unit/document/financial_years_spec.rb
106
+ - spec/unit/document/voucher_series_spec.rb
107
+ - spec/unit/document_spec.rb
108
+ - spec/unit/parser/line_parser_spec.rb
109
+ - spec/unit/parser/tokenizer_spec.rb
110
+ - spec/unit/parser_spec.rb
111
+ homepage: ''
112
+ licenses:
113
+ - MIT
114
+ post_install_message:
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ none: false
120
+ requirements:
121
+ - - ! '>='
122
+ - !ruby/object:Gem::Version
123
+ version: '0'
124
+ segments:
125
+ - 0
126
+ hash: 1372446967420835703
127
+ required_rubygems_version: !ruby/object:Gem::Requirement
128
+ none: false
129
+ requirements:
130
+ - - ! '>='
131
+ - !ruby/object:Gem::Version
132
+ version: '0'
133
+ segments:
134
+ - 0
135
+ hash: 1372446967420835703
136
+ requirements: []
137
+ rubyforge_project:
138
+ rubygems_version: 1.8.6
139
+ signing_key:
140
+ specification_version: 3
141
+ summary: Parses and generates SIE files (http://sie.se/)
142
+ test_files:
143
+ - spec/fixtures/sie_file.se
144
+ - spec/integration/parser_spec.rb
145
+ - spec/spec_helper.rb
146
+ - spec/unit/document/financial_years_spec.rb
147
+ - spec/unit/document/voucher_series_spec.rb
148
+ - spec/unit/document_spec.rb
149
+ - spec/unit/parser/line_parser_spec.rb
150
+ - spec/unit/parser/tokenizer_spec.rb
151
+ - spec/unit/parser_spec.rb