lucabook 0.2.10 → 0.2.18

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.
@@ -10,77 +10,81 @@ module LucaBook
10
10
  class Journal < LucaRecord::Base
11
11
  @dirname = 'journals'
12
12
 
13
- #
14
13
  # create journal from hash
15
14
  #
16
- def self.create!(d)
15
+ def self.create(d)
17
16
  date = Date.parse(d['date'])
18
17
 
19
- debit_amount = serialize_on_key(d['debit'], 'value')
20
- credit_amount = serialize_on_key(d['credit'], 'value')
18
+ debit_amount = LucaSupport::Code.decimalize(serialize_on_key(d['debit'], 'value'))
19
+ credit_amount = LucaSupport::Code.decimalize(serialize_on_key(d['credit'], 'value'))
21
20
  raise 'BalanceUnmatch' if debit_amount.inject(:+) != credit_amount.inject(:+)
22
21
 
23
22
  debit_code = serialize_on_key(d['debit'], 'code')
24
23
  credit_code = serialize_on_key(d['credit'], 'code')
25
24
 
26
- # TODO: limit code length for filename
27
- codes = (debit_code + credit_code).uniq
25
+ # TODO: need to sync filename & content. Limit code length for filename
26
+ # codes = (debit_code + credit_code).uniq
27
+ codes = nil
28
28
  create_record!(date, codes) do |f|
29
29
  f << debit_code
30
- f << debit_amount
30
+ f << LucaSupport::Code.readable(debit_amount)
31
31
  f << credit_code
32
- f << credit_amount
32
+ f << LucaSupport::Code.readable(credit_amount)
33
+ ['x-customer', 'x-editor'].each do |x_header|
34
+ f << [x_header, d[x_header]] if d.dig(x_header)
35
+ end
33
36
  f << []
34
37
  f << [d.dig('note')]
35
38
  end
36
39
  end
37
40
 
41
+ def self.update_codes(obj)
42
+ debit_code = serialize_on_key(obj[:debit], :code)
43
+ credit_code = serialize_on_key(obj[:credit], :code)
44
+ codes = (debit_code + credit_code).uniq.sort.compact
45
+ change_codes(obj[:id], codes)
46
+ end
47
+
38
48
  # define new transaction ID & write data at once
39
49
  def self.create_record!(date_obj, codes = nil)
40
- gen_record_file!(@dirname, date_obj, codes) do |f|
50
+ create_record(nil, date_obj, codes) do |f|
41
51
  f.write CSV.generate('', col_sep: "\t", headers: false) { |c| yield(c) }
42
52
  end
43
53
  end
44
54
 
45
- #
46
55
  # collect values on specified key
47
56
  #
48
57
  def self.serialize_on_key(array_of_hash, key)
49
58
  array_of_hash.map { |h| h[key] }
50
59
  end
51
60
 
52
- #
53
61
  # override de-serializing journal format
54
62
  #
55
63
  def self.load_data(io, path)
56
64
  {}.tap do |record|
57
65
  body = false
58
- record[:id] = path[0] + path[1]
66
+ record[:id] = "#{path[0]}/#{path[1]}"
59
67
  CSV.new(io, headers: false, col_sep: "\t", encoding: 'UTF-8')
60
68
  .each.with_index(0) do |line, i|
61
69
  case i
62
70
  when 0
63
71
  record[:debit] = line.map { |row| { code: row } }
64
72
  when 1
65
- line.each_with_index do |amount, i|
66
- record[:debit][i][:amount] = amount.to_i # TODO: bigdecimal support
67
- end
73
+ line.each_with_index { |amount, j| record[:debit][j][:amount] = BigDecimal(amount.to_s) }
68
74
  when 2
69
75
  record[:credit] = line.map { |row| { code: row } }
70
76
  when 3
71
- line.each_with_index do |amount, i|
72
- record[:credit][i][:amount] = amount.to_i # TODO: bigdecimal support
73
- end
77
+ line.each_with_index { |amount, j| record[:credit][j][:amount] = BigDecimal(amount.to_s) }
74
78
  else
75
- if line.empty?
79
+ if body == false && line.empty?
76
80
  record[:note] ||= []
77
81
  body = true
78
- next
82
+ else
83
+ record[:note] << line.join(' ') if body
79
84
  end
80
- record[:note] << line.join(' ') if body
81
85
  end
82
- record[:note]&.join('\n')
83
86
  end
87
+ record[:note] = record[:note]&.join('\n')
84
88
  end
85
89
  end
86
90
  end
@@ -0,0 +1,138 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'pathname'
4
+ require 'date'
5
+ require 'luca_support'
6
+ require 'luca_record'
7
+ require 'luca_record/dict'
8
+ require 'luca_book'
9
+
10
+ # Journal List on specified term
11
+ #
12
+ module LucaBook
13
+ class List < LucaBook::Journal
14
+ @dirname = 'journals'
15
+
16
+ def initialize(data, start_date, code = nil)
17
+ @data = data
18
+ @code = code
19
+ @start = start_date
20
+ @dict = LucaRecord::Dict.load('base.tsv')
21
+ end
22
+
23
+ def self.term(from_year, from_month, to_year = from_year, to_month = from_month, code: nil, basedir: @dirname)
24
+ data = LucaBook::Journal.term(from_year, from_month, to_year, to_month, code).select do |dat|
25
+ if code.nil?
26
+ true
27
+ else
28
+ [:debit, :credit].map { |key| serialize_on_key(dat[key], :code) }.flatten.include?(code)
29
+ end
30
+ end
31
+ new data, Date.new(from_year.to_i, from_month.to_i, 1), code
32
+ end
33
+
34
+ def list_on_code
35
+ calc_code
36
+ convert_label
37
+ @data = [code_header] + @data.map do |dat|
38
+ date, txid = LucaSupport::Code.decode_id(dat[:id])
39
+ {}.tap do |res|
40
+ res['code'] = dat[:code]
41
+ res['date'] = date
42
+ res['no'] = txid
43
+ res['id'] = dat[:id]
44
+ res['diff'] = dat[:diff]
45
+ res['balance'] = dat[:balance]
46
+ res['counter_code'] = dat[:counter_code].length == 1 ? dat[:counter_code].first : dat[:counter_code]
47
+ res['note'] = dat[:note]
48
+ end
49
+ end
50
+ self
51
+ end
52
+
53
+ def list_journals
54
+ convert_label
55
+ @data = @data.map do |dat|
56
+ date, txid = LucaSupport::Code.decode_id(dat[:id])
57
+ {}.tap do |res|
58
+ res['date'] = date
59
+ res['no'] = txid
60
+ res['id'] = dat[:id]
61
+ res['debit_code'] = dat[:debit].length == 1 ? dat[:debit][0][:code] : dat[:debit].map { |d| d[:code] }
62
+ res['debit_amount'] = dat[:debit].inject(0) { |sum, d| sum + d[:amount] }
63
+ res['credit_code'] = dat[:credit].length == 1 ? dat[:credit][0][:code] : dat[:credit].map { |d| d[:code] }
64
+ res['credit_amount'] = dat[:credit].inject(0) { |sum, d| sum + d[:amount] }
65
+ res['note'] = dat[:note]
66
+ end
67
+ end
68
+ self
69
+ end
70
+
71
+ def accumulate_code
72
+ @data.inject(BigDecimal('0')) do |sum, dat|
73
+ sum + Util.diff_by_code(dat[:debit], @code) - Util.diff_by_code(dat[:credit], @code)
74
+ end
75
+ end
76
+
77
+ def to_yaml
78
+ YAML.dump(LucaSupport::Code.readable(@data)).tap { |data| puts data }
79
+ end
80
+
81
+ private
82
+
83
+ def set_balance
84
+ return BigDecimal('0') if @code.nil? || /^[A-H]/.match(@code)
85
+
86
+ balance_dict = Dict.latest_balance
87
+ start_balance = BigDecimal(balance_dict.dig(@code.to_s, :balance) || '0')
88
+ start = Dict.issue_date(balance_dict)&.next_month
89
+ last = @start.prev_month
90
+ if last.year >= start.year && last.month >= start.month
91
+ start_balance + self.class.term(start.year, start.month, last.year, last.month, code: @code).accumulate_code
92
+ else
93
+ start_balance
94
+ end
95
+ end
96
+
97
+ def calc_code
98
+ @balance = set_balance
99
+ if @code
100
+ balance = @balance
101
+ @data.each do |dat|
102
+ dat[:diff] = Util.diff_by_code(dat[:debit], @code) - Util.diff_by_code(dat[:credit], @code)
103
+ balance += dat[:diff]
104
+ dat[:balance] = balance
105
+ dat[:code] = @code
106
+ counter = dat[:diff] * Util.pn_debit(@code) > 0 ? :credit : :debit
107
+ dat[:counter_code] = dat[counter].map { |d| d[:code] }
108
+ end
109
+ end
110
+ self
111
+ end
112
+
113
+ def convert_label
114
+ @data.each do |dat|
115
+ if @code
116
+ dat[:code] = "#{dat[:code]} #{@dict.dig(dat[:code], :label)}"
117
+ dat[:counter_code] = dat[:counter_code].map { |counter| "#{counter} #{@dict.dig(counter, :label)}" }
118
+ else
119
+ dat[:debit].each { |debit| debit[:code] = "#{debit[:code]} #{@dict.dig(debit[:code], :label)}" }
120
+ dat[:credit].each { |credit| credit[:code] = "#{credit[:code]} #{@dict.dig(credit[:code], :label)}" }
121
+ end
122
+ end
123
+ self
124
+ end
125
+
126
+ def dict
127
+ LucaBook::Dict::Data
128
+ end
129
+
130
+ def code_header
131
+ {}.tap do |h|
132
+ %w[code date no id diff balance counter_code note].each do |k|
133
+ h[k] = k == 'balance' ? @balance : ''
134
+ end
135
+ end
136
+ end
137
+ end
138
+ end
@@ -6,13 +6,33 @@ require 'fileutils'
6
6
  module LucaBook
7
7
  class Setup
8
8
  # create project skeleton under specified directory
9
- def self.create_project(dir = LucaSupport::Config::Pjdir)
9
+ def self.create_project(country = nil, dir = LucaSupport::Config::Pjdir)
10
10
  FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
11
11
  Dir.chdir(dir) do
12
- %w[data/journals dict].each do |subdir|
12
+ %w[data/journals data/balance dict].each do |subdir|
13
13
  FileUtils.mkdir_p(subdir) unless Dir.exist?(subdir)
14
14
  end
15
- FileUtils.cp("#{__dir__}/templates/dict-en.tsv", 'dict/base.tsv') unless File.exist?('config.yml')
15
+ dict = if File.exist?("#{__dir__}/templates/dict-#{country}.tsv")
16
+ "dict-#{country}.tsv"
17
+ else
18
+ 'dict-en.tsv'
19
+ end
20
+ FileUtils.cp("#{__dir__}/templates/#{dict}", 'dict/base.tsv') unless File.exist?('dict/base.tsv')
21
+ prepare_starttsv(dict) unless File.exist? 'data/balance/start.tsv'
22
+ end
23
+ end
24
+
25
+ # Generate initial balance template.
26
+ # Codes are same as base dictionary.
27
+ # The previous month of start date is better for _date.
28
+ #
29
+ def self.prepare_starttsv(dict)
30
+ CSV.open('data/balance/start.tsv', 'w', col_sep: "\t", encoding: 'UTF-8') do |csv|
31
+ csv << ['code', 'label', 'balance']
32
+ csv << ['_date', '2020-1-1']
33
+ CSV.open("#{__dir__}/templates/#{dict}", 'r', col_sep: "\t", encoding: 'UTF-8').each do |row|
34
+ csv << row if /^[1-9]/.match(row[0])
35
+ end
16
36
  end
17
37
  end
18
38
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  require 'csv'
3
4
  require 'pathname'
@@ -7,7 +8,6 @@ require 'luca_record'
7
8
  require 'luca_record/dict'
8
9
  require 'luca_book'
9
10
 
10
- #
11
11
  # Statement on specified term
12
12
  #
13
13
  module LucaBook
@@ -17,9 +17,11 @@ module LucaBook
17
17
 
18
18
  attr_reader :statement
19
19
 
20
- def initialize(data)
20
+ def initialize(data, count = nil)
21
21
  @data = data
22
+ @count = count
22
23
  @dict = LucaRecord::Dict.load('base.tsv')
24
+ @start_balance = set_balance
23
25
  end
24
26
 
25
27
  # TODO: not compatible with LucaRecord::Base.open_records
@@ -41,66 +43,42 @@ module LucaBook
41
43
  last_date = Date.new(to_year.to_i, to_month.to_i, -1)
42
44
  raise 'invalid term specified' if date > last_date
43
45
 
46
+ counts = []
44
47
  reports = [].tap do |r|
45
48
  while date <= last_date do
46
- r << accumulate_month(date.year, date.month)
47
- date = date.next_month
49
+ diff, count = accumulate_month(date.year, date.month)
50
+ r << diff.tap { |c| c['_d'] = date.to_s }
51
+ counts << count.tap { |c| c['_d'] = date.to_s }
52
+ date = Date.new(date.next_month.year, date.next_month.month, -1)
48
53
  end
49
54
  end
50
- new reports
55
+ new reports, counts
51
56
  end
52
57
 
53
58
  def by_code(code, year=nil, month=nil)
54
- raise "not supported year range yet" if ! year.nil? && month.nil?
59
+ raise 'not supported year range yet' if ! year.nil? && month.nil?
55
60
 
56
- bl = @book.load_start.dig(code) || 0
57
- full_term = scan_terms(LucaSupport::Config::Pjdir)
61
+ balance = @book.load_start.dig(code) || 0
62
+ full_term = self.class.scan_terms
58
63
  if ! month.nil?
59
- pre_term = full_term.select{|y,m| y <= year.to_i && m < month.to_i }
60
- bl += pre_term.map{|y,m| self.class.net(y, m)}.inject(0){|sum, h| sum + h[code]}
61
- [{ code: code, balance: bl, note: "#{code} #{dict.dig(code, :label)}" }] + records_with_balance(year, month, code, bl)
64
+ pre_term = full_term.select { |y, m| y <= year.to_i && m < month.to_i }
65
+ balance += pre_term.map { |y, m| self.class.net(y, m)}.inject(0){|sum, h| sum + h[code] }
66
+ [{ code: code, balance: balance, note: "#{code} #{@dict.dig(code, :label)}" }] + records_with_balance(year, month, code, balance)
62
67
  else
63
- start = { code: code, balance: bl, note: "#{code} #{dict.dig(code, :label)}" }
64
- full_term.map {|y, m| y }.uniq.map {|y|
65
- records_with_balance(y, nil, code, bl)
68
+ start = { code: code, balance: balance, note: "#{code} #{@dict.dig(code, :label)}" }
69
+ full_term.map { |y, m| y }.uniq.map { |y|
70
+ records_with_balance(y, nil, code, balance)
66
71
  }.flatten.prepend(start)
67
72
  end
68
73
  end
69
74
 
70
75
  def records_with_balance(year, month, code, balance)
71
76
  @book.search(year, month, nil, code).each do |h|
72
- balance += self.class.calc_diff(amount_by_code(h[:debit], code), code) - @book.calc_diff(amount_by_code(h[:credit], code), code)
77
+ balance += Util.calc_diff(Util.amount_by_code(h[:debit], code), code) - Util.calc_diff(Util.amount_by_code(h[:credit], code), code)
73
78
  h[:balance] = balance
74
79
  end
75
80
  end
76
81
 
77
- #
78
- # TODO: useless method. consider to remove
79
- #
80
- def accumulate_all
81
- current = @book.load_start
82
- target = []
83
- Dir.chdir(@book.pjdir) do
84
- net_records = scan_terms(@book.pjdir).map do |year, month|
85
- target << [year, month]
86
- accumulate_month(year, month)
87
- end
88
- all_keys = net_records.map{|h| h.keys}.flatten.uniq
89
- net_records.each.with_index(0) do |diff, i|
90
- all_keys.each {|key| diff[key] = 0 unless diff.has_key?(key)}
91
- diff.each do |k,v|
92
- if current[k]
93
- current[k] += v
94
- else
95
- current[k] = v
96
- end
97
- end
98
- f = { target: "#{target[i][0]}-#{target[i][1]}", diff: diff.sort, current: current.sort }
99
- yield f
100
- end
101
- end
102
- end
103
-
104
82
  def to_yaml
105
83
  YAML.dump(code2label).tap { |data| puts data }
106
84
  end
@@ -109,38 +87,118 @@ module LucaBook
109
87
  @statement ||= @data
110
88
  @statement.map do |report|
111
89
  {}.tap do |h|
112
- report.each { |k, v| h[@dict.dig(k, :label)] = v }
90
+ report.each { |k, v| h[@dict.dig(k, :label) || k] = v }
91
+ end
92
+ end
93
+ end
94
+
95
+ def stats(level = nil)
96
+ keys = @count.map(&:keys).flatten.push('_t').uniq.sort
97
+ @count.map! do |data|
98
+ sum = 0
99
+ keys.each do |k|
100
+ data[k] ||= 0
101
+ sum += data[k] if /^[^_]/.match(k)
102
+ next if level.nil? || k.length <= level
103
+
104
+ if data[k[0, level]]
105
+ data[k[0, level]] += data[k]
106
+ else
107
+ data[k[0, level]] = data[k]
108
+ end
113
109
  end
110
+ data.select! { |k, _v| k.length <= level } if level
111
+ data['_t'] = sum
112
+ data.sort.to_h
114
113
  end
114
+ keys.map! { |k| k[0, level] }.uniq.select! { |k| k.length <= level } if level
115
+ @count.prepend({}.tap { |header| keys.each { |k| header[k] = @dict.dig(k, :label) }})
116
+ puts YAML.dump(@count)
117
+ @count
115
118
  end
116
119
 
117
- def bs
118
- @statement = @data.map do |data|
119
- data.select { |k, v| /^[0-9].+/.match(k) }
120
+ def bs(level = 3, legal: false)
121
+ @data.map! { |data| data.select { |k, _v| k.length <= level } }
122
+ @data.map! { |data| code_sum(data).merge(data) } if legal
123
+ base = accumulate_balance(@data)
124
+ length = [base[:debit].length, base[:credit].length].max
125
+ @statement = [].tap do |a|
126
+ length.times do |i|
127
+ {}.tap do |res|
128
+ res['debit_label'] = base[:debit][i] ? @dict.dig(base[:debit][i].keys[0], :label) : ''
129
+ res['debit_balance'] = base[:debit][i] ? @start_balance.dig(base[:debit][i].keys[0]) + base[:debit][i].values[0] : ''
130
+ res['debit_diff'] = base[:debit][i] ? base[:debit][i].values[0] : ''
131
+ res['credit_label'] = base[:credit][i] ? @dict.dig(base[:credit][i].keys[0], :label) : ''
132
+ res['credit_balance'] = base[:credit][i] ? @start_balance.dig(base[:credit][i].keys[0]) + base[:credit][i].values[0] : ''
133
+ res['credit_diff'] = base[:credit][i] ? base[:credit][i].values[0] : ''
134
+ a << res
135
+ end
136
+ end
120
137
  end
138
+ puts YAML.dump(@statement)
121
139
  self
122
140
  end
123
141
 
124
- def pl
125
- @statement = @data.map do |data|
126
- data.select { |k, v| /^[A-F].+/.match(k) }
142
+ def accumulate_balance(monthly_diffs)
143
+ data = monthly_diffs.each_with_object({}) do |month, h|
144
+ month.each do |k, v|
145
+ h[k] = h[k].nil? ? v : h[k] + v
146
+ end
147
+ end.sort.to_h
148
+ { debit: [], credit: [] }.tap do |res|
149
+ data.each do |k, v|
150
+ case k
151
+ when /^[0-4].*/
152
+ res[:debit] << { k => v }
153
+ when /^[5-9].*/
154
+ res[:credit] << { k => v }
155
+ end
156
+ end
127
157
  end
128
- self
129
158
  end
130
159
 
131
- def self.accumulate_month(year, month)
132
- monthly_record = net(year, month)
133
- total_subaccount(monthly_record)
160
+ def pl
161
+ @statement = @data.map { |data| data.select { |k, _v| /^[A-H_].+/.match(k) } }
162
+ @statement << @statement.each_with_object({}) { |item, h| item.each { |k, v| h[k].nil? ? h[k] = v : h[k] += v } }
163
+ .tap { |h| h['_d'] = 'Total' }
164
+ self
134
165
  end
135
166
 
136
- def amount_by_code(items, code)
137
- items
138
- .select{|item| item.dig(:code) == code }
139
- .inject(0){|sum, item| sum + item[:amount] }
167
+ def self.accumulate_month(year, month)
168
+ monthly_record, count = net(year, month)
169
+ [total_subaccount(monthly_record), count]
140
170
  end
141
171
 
172
+ # Accumulate Level 2, 3 account.
173
+ #
142
174
  def self.total_subaccount(report)
143
- report.dup.tap do |res|
175
+ {}.tap do |res|
176
+ res['A0'] = sum_matched(report, /^[A][0-9A-Z]{2,}/)
177
+ res['B0'] = sum_matched(report, /^[B][0-9A-Z]{2,}/)
178
+ res['BA'] = res['A0'] - res['B0']
179
+ res['C0'] = sum_matched(report, /^[C][0-9A-Z]{2,}/)
180
+ res['CA'] = res['BA'] - res['C0']
181
+ res['D0'] = sum_matched(report, /^[D][0-9A-Z]{2,}/)
182
+ res['E0'] = sum_matched(report, /^[E][0-9A-Z]{2,}/)
183
+ res['EA'] = res['CA'] + res['D0'] - res['E0']
184
+ res['F0'] = sum_matched(report, /^[F][0-9A-Z]{2,}/)
185
+ res['G0'] = sum_matched(report, /^[G][0-9][0-9A-Z]{1,}/)
186
+ res['GA'] = res['EA'] + res['F0'] - res['G0']
187
+ res['H0'] = sum_matched(report, /^[H][0-9][0-9A-Z]{1,}/)
188
+ res['HA'] = res['GA'] - res['H0']
189
+
190
+ res['9142'] = (report['9142'] || 0) + res['HA']
191
+ res['10'] = sum_matched(report, /^[123][0-9A-Z]{2,}/)
192
+ res['40'] = sum_matched(report, /^[4][0-9A-Z]{2,}/)
193
+ res['50'] = sum_matched(report, /^[56][0-9A-Z]{2,}/)
194
+ res['70'] = sum_matched(report, /^[78][0-9A-Z]{2,}/)
195
+ res['8ZZ'] = res['50'] + res['70']
196
+ res['9ZZ'] = sum_matched(report, /^[9][0-9A-Z]{2,}/)
197
+
198
+ res['1'] = res['10'] + res['40']
199
+ res['5'] = res['8ZZ'] + res['9ZZ'] + res['HA']
200
+ res['_d'] = report['_d']
201
+
144
202
  report.each do |k, v|
145
203
  if k.length >= 4
146
204
  if res[k[0, 3]]
@@ -150,26 +208,23 @@ module LucaBook
150
208
  end
151
209
  end
152
210
  end
153
- res['10'] = sum_matched(report, /^[123].[^0]/)
154
- res['40'] = sum_matched(report, /^[4].[^0]}/)
155
- res['50'] = sum_matched(report, /^[56].[^0]/)
156
- res['70'] = sum_matched(report, /^[78].[^0]/)
157
- res['90'] = sum_matched(report, /^[9].[^0]/)
158
- res['A0'] = sum_matched(report, /^[A].[^0]/)
159
- res['B0'] = sum_matched(report, /^[B].[^0]/)
160
- res['BA'] = res['A0'] - res['B0']
161
- res['C0'] = sum_matched(report, /^[C].[^0]/)
162
- res['CA'] = res['BA'] - res['C0']
163
- res['D0'] = sum_matched(report, /^[D].[^0]/)
164
- res['E0'] = sum_matched(report, /^[E].[^0]/)
165
- res['EA'] = res['CA'] + res['D0'] - res['E0']
166
- res['F0'] = sum_matched(report, /^[F].[^0]/)
167
- res['G0'] = sum_matched(report, /^[G].[^0]/)
168
- res['GA'] = res['EA'] + res['F0'] - res['G0']
169
- res['HA'] = res['GA'] - sum_matched(report, /^[H].[^0]/)
211
+ res.sort.to_h
212
+ end
213
+ end
214
+
215
+ def code_sum(report)
216
+ legal_items.each.with_object({}) do |k, h|
217
+ h[k] = self.class.sum_matched(report, /^#{k}.*/)
170
218
  end
171
219
  end
172
220
 
221
+ def set_balance
222
+ base = Dict.latest_balance.each_with_object({}) do |(k, v), h|
223
+ h[k] = v[:balance].to_i if v[:balance]
224
+ end
225
+ code_sum(base).merge(self.class.total_subaccount(base))
226
+ end
227
+
173
228
  def self.sum_matched(report, reg)
174
229
  report.select { |k, v| reg.match(k)}.values.sum
175
230
  end
@@ -181,7 +236,7 @@ module LucaBook
181
236
  # TODO: date based range search
182
237
  end
183
238
 
184
- sum = { debit: {}, credit: {} }
239
+ sum = { debit: {}, credit: {}, debit_count: {}, credit_count: {} }
185
240
  idx_memo = []
186
241
  asof(year, month) do |f, _path|
187
242
  CSV.new(f, headers: false, col_sep: "\t", encoding: 'UTF-8')
@@ -190,14 +245,26 @@ module LucaBook
190
245
  case i
191
246
  when 0
192
247
  idx_memo = row.map(&:to_s)
193
- idx_memo.each { |r| sum[:debit][r] ||= 0 }
248
+ idx_memo.each do |r|
249
+ sum[:debit][r] ||= 0
250
+ sum[:debit_count][r] ||= 0
251
+ end
194
252
  when 1
195
- row.each_with_index { |r, i| sum[:debit][idx_memo[i]] += r.to_i } # TODO: bigdecimal support
253
+ row.each_with_index do |r, j|
254
+ sum[:debit][idx_memo[j]] += r.to_i # TODO: bigdecimal support
255
+ sum[:debit_count][idx_memo[j]] += 1
256
+ end
196
257
  when 2
197
258
  idx_memo = row.map(&:to_s)
198
- idx_memo.each { |r| sum[:credit][r] ||= 0 }
259
+ idx_memo.each do |r|
260
+ sum[:credit][r] ||= 0
261
+ sum[:credit_count][r] ||= 0
262
+ end
199
263
  when 3
200
- row.each_with_index { |r, i| sum[:credit][idx_memo[i]] += r.to_i } # TODO: bigdecimal support
264
+ row.each_with_index do |r, j|
265
+ sum[:credit][idx_memo[j]] += r.to_i # TODO: bigdecimal support
266
+ sum[:credit_count][idx_memo[j]] += 1
267
+ end
201
268
  else
202
269
  puts row # for debug
203
270
  end
@@ -210,17 +277,20 @@ module LucaBook
210
277
  def self.net(year, month = nil, code = nil, date_range = nil)
211
278
  g = gross(year, month, code, date_range)
212
279
  idx = (g[:debit].keys + g[:credit].keys).uniq.sort
213
- {}.tap do |sum|
280
+ count = {}
281
+ diff = {}.tap do |sum|
214
282
  idx.each do |code|
215
- sum[code] = g.dig(:debit, code).nil? ? 0 : calc_diff(g[:debit][code], code)
216
- sum[code] -= g.dig(:credit, code).nil? ? 0 : calc_diff(g[:credit][code], code)
283
+ sum[code] = g.dig(:debit, code).nil? ? 0 : Util.calc_diff(g[:debit][code], code)
284
+ sum[code] -= g.dig(:credit, code).nil? ? 0 : Util.calc_diff(g[:credit][code], code)
285
+ count[code] = (g.dig(:debit_count, code) || 0) + (g.dig(:credit_count, code) || 0)
217
286
  end
218
287
  end
288
+ [diff, count]
219
289
  end
220
290
 
221
291
  # TODO: replace load_tsv -> generic load_tsv_dict
222
292
  def load_start
223
- file = LucaSupport::Config::Pjdir + 'start.tsv'
293
+ file = Pathname(LucaSupport::Config::Pjdir) / 'data' / 'balance' / 'start.tsv'
224
294
  {}.tap do |dic|
225
295
  load_tsv(file) do |row|
226
296
  dic[row[0]] = row[2].to_i if ! row[2].nil?
@@ -235,24 +305,15 @@ module LucaBook
235
305
  data.each { |row| yield row }
236
306
  end
237
307
 
238
- def self.calc_diff(num, code)
239
- amount = /\./.match(num.to_s) ? BigDecimal(num) : num.to_i
240
- amount * pn_debit(code.to_s)
241
- end
308
+ private
242
309
 
243
- def self.pn_debit(code)
244
- case code
245
- when /^[0-4BCEGH]/
246
- 1
247
- when /^[5-9ADF]/
248
- -1
249
- else
250
- nil
251
- end
252
- end
310
+ def legal_items
311
+ return [] unless LucaSupport::Config::COUNTRY
253
312
 
254
- def dict
255
- LucaBook::Dict::Data
313
+ case LucaSupport::Config::COUNTRY
314
+ when 'jp'
315
+ ['91', '911', '912', '913', '9131', '9132', '914', '9141', '9142', '915', '916', '92', '93']
316
+ end
256
317
  end
257
318
  end
258
319
  end