lucabook 0.2.18 → 0.2.23

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
  SHA256:
3
- metadata.gz: d25b3a6c62980682db52a5553ce26cafdcb36e80e9657a302a8413e1e92b565b
4
- data.tar.gz: e82a86c04d13aa6addef398afd85755a7a54ce437d1e9415ce3f98bf1b28f914
3
+ metadata.gz: 866a73f9e372f2ac755ed301c9e9b88bebb556b53a5ffd604391c45eda927653
4
+ data.tar.gz: 41b02c0e5c567dbfedf37fe4148d11270a0f8776c6fcc78f630354e281aad5d3
5
5
  SHA512:
6
- metadata.gz: 652c00f982977bbb619bfc834c312dbbb06f2ada524e1824869c04ef4ed09e3fadd5aee2b082fc3944cda4eaf7bfd18fd041a80582fe57f67cc88d13197b60a6
7
- data.tar.gz: 422dedf33b9a9bd53ac8502613ae4a27c3c10730e2ac65841fddd9e424d9435a2cb92118700f9571dceea09b54b1f9a1538b9c76facfed87542832e2ce4ba939
6
+ metadata.gz: 1f295b8ab521f4b95c81ec57c57579ebf87ae01c3f1acf2fbc564bf076731d1b1d089e0959660752529e190eecfa3e107ee184b6f91484f9bdc61977eda80886
7
+ data.tar.gz: '0820bc3b42bfd48b0cf8f2924774acc8887c6fda373ec71fcebab5152b46e292096b68f174ac643613fda4ef72dc62a79300b4fa82e306d144f2fe404a770213'
@@ -1,15 +1,17 @@
1
1
  #!/usr/bin/ruby
2
2
 
3
+ require 'json'
3
4
  require 'optparse'
4
5
  require 'luca_book'
5
6
 
6
- module LucaCmd
7
- class Journal
7
+ class LucaCmd
8
+ class Journal < LucaCmd
8
9
  def self.import(args, params)
9
10
  if params['config']
10
11
  LucaBook::Import.new(args[0], params['config']).import_csv
11
12
  elsif params['json']
12
- LucaBook::Import.import_json(STDIN.read)
13
+ str = args[0].nil? ? STDIN.read : File.read(args[0])
14
+ LucaBook::Import.import_json(str)
13
15
  else
14
16
  puts 'Usage: luca-book import -c import_config'
15
17
  exit 1
@@ -17,42 +19,60 @@ module LucaCmd
17
19
  end
18
20
 
19
21
  def self.list(args, params)
20
- args = LucaCmd.gen_range(params[:n] || 1) if args.empty?
22
+ args = gen_range(params[:n] || 1) if args.empty?
21
23
  if params['code']
22
- LucaBook::List.term(*args, code: params['code']).list_on_code.to_yaml
24
+ if params['headers']
25
+ render(LucaBook::ListByHeader.term(*args, code: params['code'], header: params['headers']).list_by_code, params)
26
+ else
27
+ render(LucaBook::List.term(*args, code: params['code']).list_by_code, params)
28
+ end
23
29
  else
24
- LucaBook::List.term(*args).list_journals.to_yaml
30
+ render(LucaBook::List.term(*args).list_journals, params)
25
31
  end
26
32
  end
27
33
 
28
34
  def self.stats(args, params)
29
- args = LucaCmd.gen_range(params[:n]) if args.empty?
30
- LucaBook::State.term(*args).stats(params[:level])
35
+ args = gen_range(params[:n]) if args.empty?
36
+ if params['code']
37
+ render(LucaBook::State.by_code(params['code'], *args), params)
38
+ else
39
+ render(LucaBook::State.range(*args).stats(params[:level]), params)
40
+ end
31
41
  end
32
42
  end
33
43
 
34
- class Report
44
+ class Report < LucaCmd
35
45
  def self.balancesheet(args, params)
36
- level = params[:level] || 2
46
+ level = params[:level] || 3
37
47
  legal = params[:legal] || false
38
- args = LucaCmd.gen_range(params[:n] || 1) if args.empty?
39
- LucaBook::State.term(*args).bs(level, legal: legal)
48
+ args = gen_range(params[:n] || 1) if args.empty?
49
+ render(LucaBook::State.range(*args).bs(level, legal: legal), params)
40
50
  end
41
51
 
42
52
  def self.profitloss(args, params)
43
- args = LucaCmd.gen_range(params[:n]) if args.empty?
44
- LucaBook::State.term(*args).pl.to_yaml
53
+ level = params[:level] || 2
54
+ args = gen_range(params[:n]) if args.empty?
55
+ render(LucaBook::State.range(*args).pl(level), params)
45
56
  end
46
57
  end
47
58
 
48
- module_function
49
-
50
- def gen_range(count)
59
+ def self.gen_range(count)
51
60
  count ||= 3
52
61
  today = Date.today
53
62
  start = today.prev_month(count - 1)
54
63
  [start.year, start.month, today.year, today.month]
55
64
  end
65
+
66
+ def self.render(dat, params)
67
+ case params[:output]
68
+ when 'json'
69
+ puts JSON.dump(dat)
70
+ when 'nu'
71
+ LucaSupport::View.nushell(YAML.dump(dat))
72
+ else
73
+ puts YAML.dump(dat)
74
+ end
75
+ end
56
76
  end
57
77
 
58
78
  def new_pj(args = nil, params = {})
@@ -79,7 +99,10 @@ when /journals?/, 'j'
79
99
  OptionParser.new do |opt|
80
100
  opt.banner = 'Usage: luca-book journals list [options] [YYYY M]'
81
101
  opt.on('-c', '--code VAL', 'search with code') { |v| params['code'] = v }
102
+ opt.on('--customer', 'categorize by x-customer header') { |_v| params['headers'] = 'x-customer' }
82
103
  opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
104
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
105
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
83
106
  opt.on_tail('List records. If you specify code and/or month, search on each criteria.')
84
107
  args = opt.parse!(ARGV)
85
108
  LucaCmd::Journal.list(args, params)
@@ -87,7 +110,10 @@ when /journals?/, 'j'
87
110
  when 'stats'
88
111
  OptionParser.new do |opt|
89
112
  opt.banner = 'Usage: luca-book journals stats [options] [YYYY M]'
113
+ opt.on('-c', '--code VAL', 'search with code') { |v| params['code'] = v }
90
114
  opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
115
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
116
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
91
117
  args = opt.parse!(ARGV)
92
118
  LucaCmd::Journal.stats(args, params)
93
119
  end
@@ -115,13 +141,18 @@ when /reports?/, 'r'
115
141
  opt.banner = 'Usage: luca-book reports bs [options] [YYYY M]'
116
142
  opt.on('-l', '--level VAL', 'account level') { |v| params[:level] = v.to_i }
117
143
  opt.on('--legal', 'show legal mandatory account') { |_v| params[:legal] = true }
144
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
145
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
118
146
  args = opt.parse!(ARGV)
119
147
  LucaCmd::Report.balancesheet(args, params)
120
148
  end
121
149
  when 'pl'
122
150
  OptionParser.new do |opt|
123
151
  opt.banner = 'Usage: luca-book reports pl [options] [YYYY M YYYY M]'
152
+ opt.on('-l', '--level VAL', 'account level') { |v| params[:level] = v.to_i }
124
153
  opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
154
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
155
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
125
156
  args = opt.parse!(ARGV)
126
157
  LucaCmd::Report.profitloss(args, params)
127
158
  end
@@ -9,6 +9,7 @@ module LucaBook
9
9
  autoload :Import, 'luca_book/import'
10
10
  autoload :Journal, 'luca_book/journal'
11
11
  autoload :List, 'luca_book/list'
12
+ autoload :ListByHeader, 'luca_book/list_by_header'
12
13
  autoload :Setup, 'luca_book/setup'
13
14
  autoload :State, 'luca_book/state'
14
15
  autoload :Util, 'luca_book/util'
@@ -7,8 +7,85 @@ require 'pathname'
7
7
 
8
8
  module LucaBook
9
9
  class Dict < LucaRecord::Dict
10
+ # Column number settings for CSV/TSV convert
11
+ #
12
+ # :label
13
+ # for double entry data
14
+ # :counter_label
15
+ # must be specified with label
16
+ # :debit_label
17
+ # for double entry data
18
+ # * debit_value
19
+ # :credit_label
20
+ # for double entry data
21
+ # * credit_value
22
+ # :note
23
+ # can be the same column as another label
24
+ #
25
+ # :encoding
26
+ # file encoding
27
+ #
28
+ def csv_config
29
+ {}.tap do |config|
30
+ if @config.dig('label')
31
+ config[:label] = @config['label'].to_i
32
+ if @config.dig('counter_label')
33
+ config[:counter_label] = @config['counter_label']
34
+ config[:type] = 'single'
35
+ end
36
+ elsif @config.dig('debit_label')
37
+ config[:debit_label] = @config['debit_label'].to_i
38
+ if @config.dig('credit_label')
39
+ config[:credit_label] = @config['credit_label'].to_i
40
+ config[:type] = 'double'
41
+ end
42
+ end
43
+ config[:type] ||= 'invalid'
44
+ config[:debit_value] = @config['debit_value'].to_i if @config.dig('debit_value')
45
+ config[:credit_value] = @config['credit_value'].to_i if @config.dig('credit_value')
46
+ config[:note] = @config['note'] if @config.dig('note')
47
+ config[:encoding] = @config['encoding'] if @config.dig('encoding')
48
+
49
+ config[:year] = @config['year'] if @config.dig('year')
50
+ config[:month] = @config['month'] if @config.dig('month')
51
+ config[:day] = @config['day'] if @config.dig('day')
52
+ config[:default_debit] = @config['default_debit'] if @config.dig('default_debit')
53
+ config[:default_credit] = @config['default_credit'] if @config.dig('default_credit')
54
+ end
55
+ end
56
+
57
+ def search(word, default_word = nil, amount = nil)
58
+ res = super(word, default_word, main_key: 'account_label')
59
+ if res.is_a?(Array) && res[0].is_a?(Array)
60
+ filter_amount(res, amount)
61
+ else
62
+ res
63
+ end
64
+ end
65
+
66
+ # Choose setting on Big or small condition.
67
+ #
68
+ def filter_amount(settings, amount = nil)
69
+ return settings[0] if amount.nil?
70
+
71
+ settings.each do |item|
72
+ return item unless item[1].keys.include?(:on_amount)
73
+
74
+ condition = item.dig(1, :on_amount)
75
+ case condition[0]
76
+ when '>'
77
+ return item if amount > BigDecimal(condition[1..])
78
+ when '<'
79
+ return item if amount < BigDecimal(condition[1..])
80
+ else
81
+ return item
82
+ end
83
+ end
84
+ nil
85
+ end
86
+
10
87
  def self.latest_balance
11
- dict_dir = Pathname(LucaSupport::Config::Pjdir) / 'data' / 'balance'
88
+ dict_dir = Pathname(LucaSupport::PJDIR) / 'data' / 'balance'
12
89
  # TODO: search latest balance dictionary
13
90
  load_tsv_dict(dict_dir / 'start.tsv')
14
91
  end
@@ -4,9 +4,14 @@ require 'date'
4
4
  require 'json'
5
5
  require 'luca_book'
6
6
  require 'luca_support'
7
- #require 'luca_book/dict'
8
7
  require 'luca_record'
9
8
 
9
+ begin
10
+ require "luca_book/import_#{LucaSupport::CONFIG['country']}"
11
+ rescue LoadError => e
12
+ e.message
13
+ end
14
+
10
15
  module LucaBook
11
16
  class Import
12
17
  DEBIT_DEFAULT = '10XX'
@@ -18,7 +23,7 @@ module LucaBook
18
23
  @target_file = path
19
24
  # TODO: yaml need to be configurable
20
25
  @dict_name = dict
21
- @dict = LucaRecord::Dict.new("import-#{dict}.yaml")
26
+ @dict = LucaBook::Dict.new("import-#{dict}.yaml")
22
27
  @code_map = LucaRecord::Dict.reverse(LucaRecord::Dict.load('base.tsv'))
23
28
  @config = @dict.csv_config if dict
24
29
  end
@@ -45,8 +50,6 @@ module LucaBook
45
50
  #
46
51
  def self.import_json(io)
47
52
  JSON.parse(io).each do |d|
48
- validate(d)
49
-
50
53
  code_map = LucaRecord::Dict.reverse(LucaRecord::Dict.load('base.tsv'))
51
54
  d['debit'].each { |h| h['code'] = code_map.dig(h['label']) || DEBIT_DEFAULT }
52
55
  d['credit'].each { |h| h['code'] = code_map.dig(h['label']) || CREDIT_DEFAULT }
@@ -67,46 +70,39 @@ module LucaBook
67
70
  end
68
71
  end
69
72
 
70
- def self.validate(obj)
71
- raise 'NoDateKey' unless obj.key?('date')
72
- raise 'NoDebitKey' unless obj.key?('debit')
73
- raise 'NoDebitValue' if obj['debit'].empty?
74
- raise 'NoCreditKey' unless obj.key?('credit')
75
- raise 'NoCreditValue' if obj['credit'].empty?
76
- end
77
-
78
73
  private
79
74
 
80
- #
81
75
  # convert single entry data
82
76
  #
83
77
  def parse_single(row)
84
- value = row.dig(@config[:credit_value])&.empty? ? row[@config[:debit_value]] : row[@config[:credit_value]]
78
+ if (row.dig(@config[:credit_value]) || []).empty?
79
+ value = BigDecimal(row[@config[:debit_value]])
80
+ debit = true
81
+ else
82
+ value = BigDecimal(row[@config[:credit_value]])
83
+ end
84
+ default_label = debit ? @config.dig(:default_debit) : @config.dig(:default_credit)
85
+ code, options = search_code(row[@config[:label]], default_label, value)
86
+ counter_code = @code_map.dig(@config[:counter_label])
87
+ if respond_to? :tax_extension
88
+ data, data_c = tax_extension(code, counter_code, value, options) if options
89
+ end
90
+ data ||= [{ 'code' => code, 'value' => value }]
91
+ data_c ||= [{ 'code' => counter_code, 'value' => value }]
85
92
  {}.tap do |d|
86
93
  d['date'] = parse_date(row)
87
- if row.dig(@config[:credit_value])&.empty?
88
- d['debit'] = [
89
- { 'code' => search_code(row[@config[:label]], @config.dig(:default_debit)) || DEBIT_DEFAULT }
90
- ]
91
- d['credit'] = [
92
- { 'code' => @code_map.dig(@config[:counter_label]) }
93
- ]
94
+ if debit
95
+ d['debit'] = data
96
+ d['credit'] = data_c
94
97
  else
95
- d['debit'] = [
96
- { 'code' => @code_map.dig(@config[:counter_label]) }
97
- ]
98
- d['credit'] = [
99
- { 'code' => search_code(row[@config[:label]], @config.dig(:default_credit)) || CREDIT_DEFAULT }
100
- ]
98
+ d['debit'] = data_c
99
+ d['credit'] = data
101
100
  end
102
- d['debit'][0]['value'] = value
103
- d['credit'][0]['value'] = value
104
101
  d['note'] = Array(@config[:note]).map{ |col| row[col] }.join(' ')
105
102
  d['x-editor'] = "LucaBook::Import/#{@dict_name}"
106
103
  end
107
104
  end
108
105
 
109
- #
110
106
  # convert double entry data
111
107
  #
112
108
  def parse_double(row)
@@ -125,8 +121,9 @@ module LucaBook
125
121
  end
126
122
  end
127
123
 
128
- def search_code(label, default_label)
129
- @code_map.dig(@dict.search(label, default_label))
124
+ def search_code(label, default_label, amount = nil)
125
+ label, options = @dict.search(label, default_label, amount)
126
+ [@code_map.dig(label), options]
130
127
  end
131
128
 
132
129
  def parse_date(row)
@@ -0,0 +1,83 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require 'luca_book'
5
+ require 'luca_support'
6
+
7
+ module LucaBook
8
+ class Import
9
+ # TODO: need to be separated into pluggable l10n module.
10
+ # TODO: gensen rate >1m yen.
11
+ # TODO: gensen & consumption `round()` rules need to be confirmed.
12
+ # Profit or Loss account should be specified as code1.
13
+ #
14
+ def tax_extension(code1, code2, amount, options)
15
+ return nil if options.nil? || options[:tax_options].nil?
16
+ return nil if !options[:tax_options].include?('jp-gensen') && !options[:tax_options].include?('jp-consumption')
17
+
18
+ gensen_rate = BigDecimal('0.1021')
19
+ consumption_rate = BigDecimal('0.1')
20
+ gensen_code = @code_map.dig(options[:gensen_label]) || @code_map.dig('預り金')
21
+ gensen_idx = /^[5-8B-G]/.match(code1) ? 1 : 0
22
+ consumption_idx = /^[A-G]/.match(code1) ? 0 : 1
23
+ consumption_code = @code_map.dig(options[:consumption_label])
24
+ consumption_code ||= /^[A]/.match(code1) ? @code_map.dig('仮受消費税等') : @code_map.dig('仮払消費税等')
25
+ if options[:tax_options].include?('jp-gensen') && options[:tax_options].include?('jp-consumption')
26
+ paid_rate = BigDecimal('1') + consumption_rate - gensen_rate
27
+ gensen_amount = (amount / paid_rate * gensen_rate).round
28
+ consumption_amount = (amount / paid_rate * consumption_rate).round
29
+ [].tap do |res|
30
+ res << [].tap do |res1|
31
+ amount1 = amount
32
+ amount1 -= consumption_amount if consumption_idx == 0
33
+ amount1 += gensen_amount if gensen_idx == 1
34
+ res1 << { 'code' => code1, 'value' => amount1 }
35
+ res1 << { 'code' => consumption_code, 'value' => consumption_amount } if consumption_idx == 0
36
+ res1 << { 'code' => gensen_code, 'value' => gensen_amount } if gensen_idx == 0
37
+ end
38
+ res << [].tap do |res2|
39
+ amount2 = amount
40
+ amount2 -= consumption_amount if consumption_idx == 1
41
+ amount2 += gensen_amount if gensen_idx == 0
42
+ res2 << { 'code' => code2, 'value' => amount2 }
43
+ res2 << { 'code' => consumption_code, 'value' => consumption_amount } if consumption_idx == 1
44
+ res2 << { 'code' => gensen_code, 'value' => gensen_amount } if gensen_idx == 1
45
+ end
46
+ end
47
+ elsif options[:tax_options].include?('jp-gensen')
48
+ paid_rate = BigDecimal('1') - gensen_rate
49
+ gensen_amount = (amount / paid_rate * gensen_rate).round
50
+ [].tap do |res|
51
+ res << [].tap do |res1|
52
+ amount1 = amount
53
+ amount1 += gensen_amount if gensen_idx == 1
54
+ res1 << { 'code' => code, 'value' => amount1 }
55
+ res1 << { 'code' => gensen_code, 'value' => gensen_amount } if gensen_idx == 0
56
+ end
57
+ res << [].tap do |res2|
58
+ amount2 = amount
59
+ amount2 += gensen_amount if gensen_idx == 0
60
+ mount2 ||= amount
61
+ res2 << { 'code' => code2, 'value' => amount2 }
62
+ res2 << { 'code' => gensen_code, 'value' => gensen_amount } if gensen_idx == 1
63
+ end
64
+ end
65
+ elsif options[:tax_options].include?('jp-consumption')
66
+ paid_rate = BigDecimal('1') + consumption_rate - gensen_rate
67
+ consumption_amount = (amount / paid_rate * consumption_rate).round
68
+ res << [].tap do |res1|
69
+ amount1 = amount
70
+ amount1 -= consumption_amount if consumption_idx == 0
71
+ res1 << { 'code' => code1, 'value' => amount1 }
72
+ res1 << { 'code' => consumption_code, 'value' => consumption_amount } if consumption_idx == 0
73
+ end
74
+ res << [].tap do |res2|
75
+ amount2 = amount
76
+ amount2 -= consumption_amount if consumption_idx == 1
77
+ res2 << { 'code' => code2, 'value' => amount2 }
78
+ res2 << { 'code' => consumption_code, 'value' => consumption_amount } if consumption_idx == 1
79
+ end
80
+ end
81
+ end
82
+ end
83
+ end
@@ -12,9 +12,36 @@ module LucaBook
12
12
 
13
13
  # create journal from hash
14
14
  #
15
- def self.create(d)
15
+ def self.create(dat)
16
+ d = LucaSupport::Code.keys_stringify(dat)
17
+ validate(d)
18
+ raise 'NoDateKey' unless d.key?('date')
19
+
16
20
  date = Date.parse(d['date'])
17
21
 
22
+ # TODO: need to sync filename & content. Limit code length for filename
23
+ # codes = (debit_code + credit_code).uniq
24
+ codes = nil
25
+
26
+ create_record(nil, date, codes) { |f| f.write journal2csv(d) }
27
+ end
28
+
29
+ # update journal with hash.
30
+ # If record not found with id, no record will be created.
31
+ #
32
+ def self.save(dat)
33
+ d = LucaSupport::Code.keys_stringify(dat)
34
+ raise 'record has no id.' if d['id'].nil?
35
+
36
+ validate(d)
37
+ parts = d['id'].split('/')
38
+ raise 'invalid ID' if parts.length != 2
39
+
40
+ codes = nil
41
+ open_records(@dirname, parts[0], parts[1], codes, 'w') { |f, _path| f.write journal2csv(d) }
42
+ end
43
+
44
+ def self.journal2csv(d)
18
45
  debit_amount = LucaSupport::Code.decimalize(serialize_on_key(d['debit'], 'value'))
19
46
  credit_amount = LucaSupport::Code.decimalize(serialize_on_key(d['credit'], 'value'))
20
47
  raise 'BalanceUnmatch' if debit_amount.inject(:+) != credit_amount.inject(:+)
@@ -22,16 +49,13 @@ module LucaBook
22
49
  debit_code = serialize_on_key(d['debit'], 'code')
23
50
  credit_code = serialize_on_key(d['credit'], 'code')
24
51
 
25
- # TODO: need to sync filename & content. Limit code length for filename
26
- # codes = (debit_code + credit_code).uniq
27
- codes = nil
28
- create_record!(date, codes) do |f|
52
+ csv = CSV.generate('', col_sep: "\t", headers: false) do |f|
29
53
  f << debit_code
30
54
  f << LucaSupport::Code.readable(debit_amount)
31
55
  f << credit_code
32
56
  f << LucaSupport::Code.readable(credit_amount)
33
57
  ['x-customer', 'x-editor'].each do |x_header|
34
- f << [x_header, d[x_header]] if d.dig(x_header)
58
+ f << [x_header, d['headers'][x_header]] if d.dig('headers', x_header)
35
59
  end
36
60
  f << []
37
61
  f << [d.dig('note')]
@@ -45,11 +69,19 @@ module LucaBook
45
69
  change_codes(obj[:id], codes)
46
70
  end
47
71
 
48
- # define new transaction ID & write data at once
49
- def self.create_record!(date_obj, codes = nil)
50
- create_record(nil, date_obj, codes) do |f|
51
- f.write CSV.generate('', col_sep: "\t", headers: false) { |c| yield(c) }
52
- end
72
+ def self.validate(obj)
73
+ raise 'NoDebitKey' unless obj.key?('debit')
74
+ raise 'NoCreditKey' unless obj.key?('credit')
75
+ debit_codes = serialize_on_key(obj['debit'], 'code').compact
76
+ debit_values = serialize_on_key(obj['debit'], 'value').compact
77
+ raise 'NoDebitCode' if debit_codes.empty?
78
+ raise 'NoDebitValue' if debit_values.empty?
79
+ raise 'UnmatchDebit' if debit_codes.length != debit_values.length
80
+ credit_codes = serialize_on_key(obj['credit'], 'code').compact
81
+ credit_values = serialize_on_key(obj['credit'], 'value').compact
82
+ raise 'NoCreditCode' if credit_codes.empty?
83
+ raise 'NoCreditValue' if credit_values.empty?
84
+ raise 'UnmatchCredit' if credit_codes.length != credit_values.length
53
85
  end
54
86
 
55
87
  # collect values on specified key
@@ -76,10 +108,16 @@ module LucaBook
76
108
  when 3
77
109
  line.each_with_index { |amount, j| record[:credit][j][:amount] = BigDecimal(amount.to_s) }
78
110
  else
79
- if body == false && line.empty?
80
- record[:note] ||= []
81
- body = true
82
- else
111
+ case body
112
+ when false
113
+ if line.empty?
114
+ record[:note] ||= []
115
+ body = true
116
+ else
117
+ record[:headers] ||= {}
118
+ record[:headers][line[0]] = line[1]
119
+ end
120
+ when true
83
121
  record[:note] << line.join(' ') if body
84
122
  end
85
123
  end