total_in 0.5.1 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 9daf2078aea2bffac2851c381b979d49c65cce6f
4
- data.tar.gz: 918f03d6bd88d8c99c3f2f6ef54232aa60922ae0
3
+ metadata.gz: 353b198563a1b0e9a48706e4c84a43a259cd4e05
4
+ data.tar.gz: 7b88983001ccc29990a62b3bae56c84995082aec
5
5
  SHA512:
6
- metadata.gz: ffa4a64cd178435c8eae2d7de63ee20bcf58074190f92cb6fa75d3dd4d399c20d9e0354b5edb88b7937903392cda373bcf5fbfdb13ded8491fb9d781df3be3e8
7
- data.tar.gz: 4a84dd6f73a09230540a0e77be52a7ac402071b0d92052991ea763cefae3ccd6c7f1374e45dafd91850246965178d5881be775993a108b4b818425ad04a65220
6
+ metadata.gz: 1ea026300e106f9ed6f8b18fbd3f2882d68e87c26a51b3e2c66cd90cdb218c0da56badaf595bc0e552ba11c8ba51e135daa71962e205a5368cad954a8906de9c
7
+ data.tar.gz: 9a35cb35e78bae51be28b160e44625c756e902496d8c90462613a3e289b4f187bc707715db57d363129f0c512ee436d928d9b3e3d52d421f94c585cab6991c76
data/README.md CHANGED
@@ -3,6 +3,7 @@
3
3
  [![Code Climate](https://codeclimate.com/github/Oktavilla/total-in-ruby/badges/gpa.svg)](https://codeclimate.com/github/Oktavilla/total-in-ruby)
4
4
  [![Test Coverage](https://codeclimate.com/github/Oktavilla/total-in-ruby/badges/coverage.svg)](https://codeclimate.com/github/Oktavilla/total-in-ruby)
5
5
 
6
+ Parses transaction files in the [TotalIn format](http://www.nordea.se/Images/39-16101/postbeskrivning-total-in-eng.pdf).
6
7
 
7
8
  ## Usage
8
9
 
@@ -15,12 +15,16 @@ module TotalIn
15
15
  @accounts ||= []
16
16
  end
17
17
 
18
+ def transaction_date
19
+ accounts.map(&:date).compact.first
20
+ end
21
+
18
22
  def payments
19
- accounts.map { |a| a.payments }.flatten
23
+ accounts.flat_map(&:payments)
20
24
  end
21
25
 
22
26
  def deductions
23
- accounts.map { |a| a.deductions }.flatten
27
+ accounts.flat_map(&:deductions)
24
28
  end
25
29
 
26
30
  class Account
@@ -0,0 +1,19 @@
1
+ require "total_in/line_parser"
2
+ module TotalIn
3
+ class FileFormatValidator < LineParser
4
+ field :record_type, 0..1, :raw
5
+ field :file_type, 36..38
6
+
7
+ def valid?
8
+ errors.empty?
9
+ end
10
+
11
+ def errors
12
+ errors = []
13
+ errors << "Must start with 00 (was ”#{record_type}”)" if record_type != "00"
14
+ errors << "Must be of type TL1 (was ”#{file_type}”" if file_type != "TL1"
15
+
16
+ errors
17
+ end
18
+ end
19
+ end
@@ -3,33 +3,97 @@ require "total_in/line_processors"
3
3
 
4
4
  module TotalIn
5
5
  module LineHandlers
6
+ def self.mapping
7
+ {
8
+ "00" => self.document_start,
9
+ "99" => self.document_end,
10
+ "10" => self.account_start,
11
+ "90" => self.account_end,
12
+ "20" => self.payment_start,
13
+ "25" => self.deduction_start,
14
+ "30" => self.reference_numbers,
15
+ "40" => self.messages,
16
+ "50" => self.sender_names,
17
+ "51" => self.sender_address,
18
+ "52" => self.sender_locality,
19
+ "60" => self.sender_account_start,
20
+ "61" => self.sender_account_names,
21
+ "62" => self.sender_account_address,
22
+ "63" => self.sender_account_locality,
23
+ "70" => self.international
24
+ }
25
+ end
26
+
27
+ def self.document_start
28
+ Handler.new LineParsers::DocumentStart, LineProcessors::DocumentStart
29
+ end
30
+
31
+ def self.document_end
32
+ Handler.new LineParsers::DocumentEnd, LineProcessors::DocumentEnd
33
+ end
34
+
35
+ def self.account_start
36
+ Handler.new LineParsers::AccountStart, LineProcessors::AccountStart
37
+ end
38
+
39
+ def self.account_end
40
+ Handler.new LineParsers::AccountEnd, LineProcessors::AccountEnd
41
+ end
42
+
43
+ def self.payment_start
44
+ Handler.new LineParsers::PaymentStart, LineProcessors::PaymentStart
45
+ end
46
+
47
+ def self.deduction_start
48
+ Handler.new LineParsers::DeductionStart, LineProcessors::DeductionStart
49
+ end
50
+
51
+ def self.reference_numbers
52
+ Handler.new LineParsers::ReferenceNumbers, LineProcessors::ReferenceNumbers
53
+ end
54
+
55
+ def self.messages
56
+ Handler.new LineParsers::Messages, LineProcessors::Messages
57
+ end
58
+
59
+ def self.sender_names
60
+ Handler.new LineParsers::Names, LineProcessors::Names.new(Document::Sender)
61
+ end
62
+
63
+ def self.sender_address
64
+ Handler.new LineParsers::Addresses, LineProcessors::Addresses.new(Document::Sender)
65
+ end
66
+
67
+ def self.sender_locality
68
+ Handler.new LineParsers::Locality, LineProcessors::Locality.new(Document::Sender)
69
+ end
70
+
71
+ def self.sender_account_start
72
+ Handler.new LineParsers::SenderAccount, LineProcessors::SenderAccount
73
+ end
74
+
75
+ def self.sender_account_names
76
+ Handler.new LineParsers::Names, LineProcessors::Names.new(Document::SenderAccount)
77
+ end
78
+
79
+ def self.sender_account_address
80
+ Handler.new LineParsers::Addresses, LineProcessors::Addresses.new(Document::SenderAccount)
81
+ end
82
+
83
+ def self.sender_account_locality
84
+ Handler.new LineParsers::Locality, LineProcessors::Locality.new(Document::SenderAccount)
85
+ end
86
+
87
+ def self.international
88
+ Handler.new LineParsers::International, LineProcessors::International
89
+ end
90
+
6
91
  Handler = Struct.new :parser, :processor do
7
- def handle line, contexts
92
+ def process line, contexts
8
93
  line_parser = self.parser.new(line)
9
94
 
10
95
  processor.call line_parser, contexts
11
96
  end
12
97
  end
13
-
14
- def self.all
15
- {
16
- "00" => Handler.new(LineParsers::DocumentStart, LineProcessors::DocumentStart),
17
- "99" => Handler.new(LineParsers::DocumentEnd, LineProcessors::DocumentEnd),
18
- "10" => Handler.new(LineParsers::AccountStart, LineProcessors::AccountStart),
19
- "90" => Handler.new(LineParsers::AccountEnd, LineProcessors::AccountEnd),
20
- "20" => Handler.new(LineParsers::PaymentStart, LineProcessors::PaymentStart),
21
- "25" => Handler.new(LineParsers::DeductionStart, LineProcessors::DeductionStart),
22
- "30" => Handler.new(LineParsers::ReferenceNumbers, LineProcessors::ReferenceNumbers),
23
- "40" => Handler.new(LineParsers::Messages, LineProcessors::Messages),
24
- "50" => Handler.new(LineParsers::Names, LineProcessors::Names.new(Document::Sender)),
25
- "51" => Handler.new(LineParsers::Addresses, LineProcessors::Addresses.new(Document::Sender)),
26
- "52" => Handler.new(LineParsers::Locality, LineProcessors::Locality.new(Document::Sender)),
27
- "60" => Handler.new(LineParsers::SenderAccount, LineProcessors::SenderAccount),
28
- "61" => Handler.new(LineParsers::Names, LineProcessors::Names.new(Document::SenderAccount)),
29
- "62" => Handler.new(LineParsers::Addresses, LineProcessors::Addresses.new(Document::SenderAccount)),
30
- "63" => Handler.new(LineParsers::Locality, LineProcessors::Locality.new(Document::SenderAccount)),
31
- "70" => Handler.new(LineParsers::International, LineProcessors::International)
32
- }
33
- end
34
98
  end
35
99
  end
@@ -32,7 +32,7 @@ module TotalIn
32
32
  end
33
33
 
34
34
  def value_at_position range, type
35
- typecast line[range].strip, type
35
+ typecast line[range].to_s.strip, type
36
36
  end
37
37
 
38
38
  def typecast value, type
@@ -1,48 +1,71 @@
1
+ require "total_in/file_format_validator"
1
2
  require "total_in/line_handlers"
2
3
  require "total_in/contexts"
3
4
 
4
5
  module TotalIn
6
+ class InvalidFileFormatError < ArgumentError; end;
7
+
5
8
  class Parser
6
- attr_reader :text
9
+ attr_reader :file
7
10
 
8
- def initialize text
9
- @text = text
11
+ # Parser.new accepts a File instance or a String
12
+ # A InvalidFileFormatError will be raised if file isn't in the TotalIn format
13
+ def initialize file
14
+ @file = file
15
+ validate_file_format
10
16
  end
11
17
 
12
18
  def result
13
- contexts = parse_lines text.each_line.to_a, Contexts.new
19
+ parse_lines(Contexts.new).result
20
+ end
21
+
22
+ protected
14
23
 
15
- contexts.result
24
+ def lines
25
+ @lines ||= file.each_line
16
26
  end
17
27
 
18
28
  private
19
29
 
20
- def parse_lines lines, contexts
21
- if line = lines.shift
22
- parse_lines lines, parse_line(line, contexts)
23
- else
30
+ def validate_file_format
31
+ validator = FileFormatValidator.new first_line
32
+ raise InvalidFileFormatError.new(validator.errors.join(", ")) unless validator.valid?
33
+ end
34
+
35
+ def parse_lines contexts
36
+ begin
37
+ parse_lines parse_line(self.lines.next, contexts)
38
+ rescue StopIteration
39
+ self.lines.rewind # Ensure we do not bomb out when calling result multiple times
24
40
  contexts
25
41
  end
26
42
  end
27
43
 
44
+ # Look up a matching handler for the line and process it
45
+ # The process method on a handler always returns a Contexts object
28
46
  def parse_line line, contexts
47
+ line = line.encode Encoding::UTF_8 if encode_lines?
48
+
29
49
  handler = handler_for_line line
30
50
 
31
- handler.handle line, contexts
51
+ handler.process line, contexts
32
52
  end
33
53
 
34
54
  def handler_for_line line
35
- self.class.handlers.fetch line[0..1]
55
+ LineHandlers.mapping.fetch line[0..1]
36
56
  end
37
57
 
38
- def self.handlers
39
- @handlers ||= {}
58
+ def encode_lines?
59
+ first_line.encoding != Encoding::UTF_8
40
60
  end
41
61
 
42
- def self.register_handlers handlers
43
- @handlers = handlers
44
- end
62
+ def first_line
63
+ @first_line ||= begin
64
+ line = self.lines.peek
65
+ self.lines.rewind # peek seems to move the pointer when file is an actual File object
45
66
 
46
- register_handlers LineHandlers.all
67
+ line
68
+ end
69
+ end
47
70
  end
48
71
  end
@@ -9,9 +9,10 @@ module TotalIn
9
9
  def self.casters
10
10
  {
11
11
  integer: ->(value) { value.to_i },
12
- time: ->(value) { Time.parse(value) },
13
- date: ->(value) { Date.parse(value) },
14
- string: ->(value) { value unless value.match(/\A0+\Z/) }
12
+ time: ->(value) { Time.strptime(value, "%Y%m%d%H%M%S%N") },
13
+ date: ->(value) { Date.strptime(value, "%Y%m%d") },
14
+ string: ->(value) { value unless value.match(/\A0+\Z/) },
15
+ raw: ->(value) { value }
15
16
  }
16
17
  end
17
18
  end
@@ -1,3 +1,3 @@
1
1
  module TotalIn
2
- VERSION = "0.5.1"
2
+ VERSION = "1.0.0"
3
3
  end
@@ -1,61 +1,61 @@
1
- 00TI222222 2011102504133012345601TL1TOTALIN
2
- 1010181 SEK20111024
3
- 2038952344444 00000000030255022222333334444451
4
- 3038952345678 38952145778
5
- 40FAKTURANR:38952344444 INTERN REF: 9780858
6
- 40FAKTURANR:38952345678 38952145778ABC
7
- 601234567 19999999999
8
- 61TESTBOLAGET AB
9
- 62GATAN 12
10
- 6312345 TESTSTAD
11
- 200000000000000000000000000 00000000042973522222333334444455
12
- 40TACK FÖR LÅNET
13
- 50FÖRETAGET AB FRISKVÅRDAVD.
14
- 6099999999
15
- 61FÖRETAGET AB
16
- 62GATAN 7
17
- 6388888 TESTORTEN
18
- 200000000000000000000000000 0000000002109002222233333444446734523455
19
- 40BETALNING FÖR VARA 123
20
- 50TESTFABRIKEN AB
21
- 51GATAN 22
22
- 5211111 TESTVIKEN
23
- 6098765433 29999999999
24
- 61NORDEA BANK AB
25
- 6310571 STOCKHOLM
26
- 200000000000000000000000000 00000000014836522222333334444472
27
- 601222211 19999999999
28
- 2038952345555 00000000088000022222333334444423
29
- 40FAKT 38952345555,FAKTURANR
30
- 602232567 19999999999
31
- 70000000000000000 000000000100000EUR000000088000
32
- 25987654123 00000000005255022222333334444457
33
- 40FAKTURA NR: 987654123 ABC KUNDNR: 123
34
- 40ÅTERBETALNING
35
- 601234567 19999999999
36
- 61TESTBOLAGET AB
37
- 62GATAN 12
38
- 6312345 TESTSTAD
39
- 90000000060000000000191900020111024001
40
- 109690017 SEK20111024
41
- 200000000000000000000000000 00000000092343411111222223333344
42
- 40BETALNING AVSEENDE KÖP 110902
43
- 601234567 19999999999
44
- 61TESTBOLAGET AB
45
- 62GATAN 12
46
- 6312345 TESTSTAD
47
- 2038952678900 00000000001050022222333334444477
48
- 50TESTFÖRETAG ENHET 2.A
49
- 6054321 19999999999
50
- 61TESTFÖRETAG
51
- 62TESTV 55
52
- 6312345 TESTSTAD
53
- 2038952678888 00000000002050022222333334444422
54
- 3038952345999
55
- 40FAKTNR=38952348888 OCH FAKTNR=38952345999
56
- 6098765555 19999999999
57
- 61TESTER AB
58
- 62VÄGEN 1
59
- 6388888 TESTORTEN
60
- 90000000030000000000095443420111024001
61
- 99000000000000061
1
+ 00TI222222 2011102504133012345601TL1TOTALIN
2
+ 1010181 SEK20111024
3
+ 2038952344444 00000000030255022222333334444451
4
+ 3038952345678 38952145778
5
+ 40FAKTURANR:38952344444 INTERN REF: 9780858
6
+ 40FAKTURANR:38952345678 38952145778ABC
7
+ 601234567 19999999999
8
+ 61TESTBOLAGET AB
9
+ 62GATAN 12
10
+ 6312345 TESTSTAD
11
+ 200000000000000000000000000 00000000042973522222333334444455
12
+ 40TACK F�R L�NET
13
+ 50F�RETAGET AB FRISKV�RDAVD.
14
+ 6099999999
15
+ 61F�RETAGET AB
16
+ 62GATAN 7
17
+ 6388888 TESTORTEN
18
+ 200000000000000000000000000 0000000002109002222233333444446734523455
19
+ 40BETALNING F�R VARA 123
20
+ 50TESTFABRIKEN AB
21
+ 51GATAN 22
22
+ 5211111 TESTVIKEN
23
+ 6098765433 29999999999
24
+ 61NORDEA BANK AB
25
+ 6310571 STOCKHOLM
26
+ 200000000000000000000000000 00000000014836522222333334444472
27
+ 601222211 19999999999
28
+ 2038952345555 00000000088000022222333334444423
29
+ 40FAKT 38952345555,FAKTURANR
30
+ 602232567 19999999999
31
+ 70000000000000000 000000000100000EUR000000088000
32
+ 25987654123 00000000005255022222333334444457
33
+ 40FAKTURA NR: 987654123 ABC KUNDNR: 123
34
+ 40�TERBETALNING
35
+ 601234567 19999999999
36
+ 61TESTBOLAGET AB
37
+ 62GATAN 12
38
+ 6312345 TESTSTAD
39
+ 90000000060000000000191900020111024001
40
+ 109690017 SEK20111024
41
+ 200000000000000000000000000 00000000092343411111222223333344
42
+ 40BETALNING AVSEENDE K�P 110902
43
+ 601234567 19999999999
44
+ 61TESTBOLAGET AB
45
+ 62GATAN 12
46
+ 6312345 TESTSTAD
47
+ 2038952678900 00000000001050022222333334444477
48
+ 50TESTF�RETAG ENHET 2.A
49
+ 6054321 19999999999
50
+ 61TESTF�RETAG
51
+ 62TESTV 55
52
+ 6312345 TESTSTAD
53
+ 2038952678888 00000000002050022222333334444422
54
+ 3038952345999
55
+ 40FAKTNR=38952348888 OCH FAKTNR=38952345999
56
+ 6098765555 19999999999
57
+ 61TESTER AB
58
+ 62V�GEN 1
59
+ 6388888 TESTORTEN
60
+ 90000000030000000000095443420111024001
61
+ 99000000000000061
@@ -3,7 +3,9 @@ require "total_in"
3
3
  RSpec.describe TotalIn do
4
4
  describe "parse" do
5
5
  before :all do
6
- @document = TotalIn.parse File.read File.join(__dir__, "fixtures/total_in_full.txt")
6
+ File.open File.join(__dir__, "fixtures/total_in_full_latin_1.txt"), encoding: "iso-8859-1" do |file|
7
+ @document = TotalIn.parse file
8
+ end
7
9
  end
8
10
 
9
11
  let :document do
@@ -12,7 +14,7 @@ RSpec.describe TotalIn do
12
14
 
13
15
  it "find the document meta data" do
14
16
  expect(document.id).to eq "TI222222"
15
- expect(document.created_at.strftime("%Y-%m-%d %H:%M:%S")).to eq "2011-10-25 12:34:56"
17
+ expect(document.created_at.strftime("%Y-%m-%d %H:%M:%S")).to eq "2011-10-25 04:13:30"
16
18
  expect(document.delivery_number).to eq 1
17
19
  expect(document.file_type).to eq "TL1"
18
20
  expect(document.name).to eq "TOTALIN"
@@ -1,5 +1,21 @@
1
+ require "total_in/document"
2
+
1
3
  module TotalIn
2
4
  RSpec.describe Document do
5
+ describe "#transaction_date" do
6
+ it "delegates to the first account date it finds" do
7
+ document = Document.new
8
+ document.accounts << Document::Account.new
9
+ document.accounts << Document::Account.new(date: "some date")
10
+
11
+ expect(document.transaction_date).to eq "some date"
12
+ end
13
+
14
+ it "returns nil if there are no account dates" do
15
+ expect(Document.new.transaction_date).to be nil
16
+ end
17
+ end
18
+
3
19
  describe "#payments" do
4
20
  it "returns payments from all accounts" do
5
21
  document = Document.new
@@ -33,7 +49,6 @@ module TotalIn
33
49
  document.accounts << account_two
34
50
 
35
51
  expect(document.deductions).to eq [deduction_one, deduction_two]
36
-
37
52
  end
38
53
  end
39
54
  end
@@ -13,7 +13,7 @@ module TotalIn
13
13
 
14
14
  it "extracts the created at timestamp" do
15
15
  expect(document_start.created_at).to be_a Time
16
- expect(document_start.created_at.strftime("%Y-%m-%d %H:%M:%S")).to eq "2011-10-25 12:34:56"
16
+ expect(document_start.created_at.strftime("%Y-%m-%d %H:%M:%S")).to eq "2011-10-25 04:13:30"
17
17
  end
18
18
 
19
19
  it "parses the deliver number" do
@@ -0,0 +1,22 @@
1
+ require "total_in/parser"
2
+
3
+ module TotalIn
4
+ RSpec.describe Parser do
5
+ describe "ensuring the file is valid" do
6
+ it "accepts a valid file" do
7
+ valid_file = "00TI222222 2011102504133012345601TL1TOTALIN "
8
+ expect{ Parser.new(valid_file) }.to_not raise_error
9
+ end
10
+
11
+ it "should start with 00" do
12
+ invalid_file = "99TI222222 2011102504133012345601TL1TOTALIN "
13
+ expect{ Parser.new(invalid_file) }.to raise_error{ TotalIn::InvalidFileFormatError }
14
+ end
15
+
16
+ it "expects the TL1 file type indicator" do
17
+ invalid_file = "00TI222222 2011102504133012345601TL2TOTALIN "
18
+ expect{ Parser.new(invalid_file) }.to raise_error{ TotalIn::InvalidFileFormatError }
19
+ end
20
+ end
21
+ end
22
+ end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: total_in
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.5.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Joel Junström
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2014-11-25 00:00:00.000000000 Z
11
+ date: 2015-01-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -71,6 +71,7 @@ files:
71
71
  - lib/total_in/attribute_methods.rb
72
72
  - lib/total_in/contexts.rb
73
73
  - lib/total_in/document.rb
74
+ - lib/total_in/file_format_validator.rb
74
75
  - lib/total_in/line_handlers.rb
75
76
  - lib/total_in/line_parser.rb
76
77
  - lib/total_in/line_parsers.rb
@@ -79,7 +80,7 @@ files:
79
80
  - lib/total_in/string_helpers.rb
80
81
  - lib/total_in/typecaster.rb
81
82
  - lib/total_in/version.rb
82
- - spec/fixtures/total_in_full.txt
83
+ - spec/fixtures/total_in_full_latin_1.txt
83
84
  - spec/full_file_parse_spec.rb
84
85
  - spec/spec_helper.rb
85
86
  - spec/support/shared_examples_for_values_line_parsers.rb
@@ -110,6 +111,7 @@ files:
110
111
  - spec/total_in/line_processors/payment_start_spec.rb
111
112
  - spec/total_in/line_processors/reference_numbers_spec.rb
112
113
  - spec/total_in/line_processors/sender_account_spec.rb
114
+ - spec/total_in/parser_spec.rb
113
115
  - spec/total_in_spec.rb
114
116
  - total-in.gemspec
115
117
  homepage: https://github.com/Oktavilla/total-in-ruby
@@ -137,7 +139,7 @@ signing_key:
137
139
  specification_version: 4
138
140
  summary: Parses Nordea Total-IN and Total-IN Basic files
139
141
  test_files:
140
- - spec/fixtures/total_in_full.txt
142
+ - spec/fixtures/total_in_full_latin_1.txt
141
143
  - spec/full_file_parse_spec.rb
142
144
  - spec/spec_helper.rb
143
145
  - spec/support/shared_examples_for_values_line_parsers.rb
@@ -168,4 +170,5 @@ test_files:
168
170
  - spec/total_in/line_processors/payment_start_spec.rb
169
171
  - spec/total_in/line_processors/reference_numbers_spec.rb
170
172
  - spec/total_in/line_processors/sender_account_spec.rb
173
+ - spec/total_in/parser_spec.rb
171
174
  - spec/total_in_spec.rb