total_in 0.5.1 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
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