lucabook 0.2.18 → 0.2.23

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
  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