lucabook 0.2.12 → 0.2.20

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.
@@ -30,6 +30,9 @@ module LucaBook
30
30
  f << LucaSupport::Code.readable(debit_amount)
31
31
  f << credit_code
32
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
@@ -80,8 +83,8 @@ module LucaBook
80
83
  record[:note] << line.join(' ') if body
81
84
  end
82
85
  end
83
- record[:note]&.join('\n')
84
86
  end
87
+ record[:note] = record[:note]&.join('\n')
85
88
  end
86
89
  end
87
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
@@ -9,7 +9,7 @@ module LucaBook
9
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
15
  dict = if File.exist?("#{__dir__}/templates/dict-#{country}.tsv")
@@ -18,6 +18,21 @@ module LucaBook
18
18
  'dict-en.tsv'
19
19
  end
20
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
21
36
  end
22
37
  end
23
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,12 @@ module LucaBook
17
17
 
18
18
  attr_reader :statement
19
19
 
20
- def initialize(data)
20
+ def initialize(data, count = nil, date: nil)
21
21
  @data = data
22
+ @count = count
22
23
  @dict = LucaRecord::Dict.load('base.tsv')
24
+ @start_date = date
25
+ @start_balance = set_balance
23
26
  end
24
27
 
25
28
  # TODO: not compatible with LucaRecord::Base.open_records
@@ -41,106 +44,206 @@ module LucaBook
41
44
  last_date = Date.new(to_year.to_i, to_month.to_i, -1)
42
45
  raise 'invalid term specified' if date > last_date
43
46
 
47
+ counts = []
44
48
  reports = [].tap do |r|
45
49
  while date <= last_date do
46
- r << accumulate_month(date.year, date.month)
47
- date = date.next_month
50
+ diff, count = accumulate_month(date.year, date.month)
51
+ r << diff.tap { |c| c['_d'] = date.to_s }
52
+ counts << count.tap { |c| c['_d'] = date.to_s }
53
+ date = Date.new(date.next_month.year, date.next_month.month, -1)
48
54
  end
49
55
  end
50
- new reports
56
+ new(reports, counts, date: Date.new(from_year.to_i, from_month.to_i, -1))
51
57
  end
52
58
 
53
59
  def by_code(code, year=nil, month=nil)
54
- raise "not supported year range yet" if ! year.nil? && month.nil?
60
+ raise 'not supported year range yet' if ! year.nil? && month.nil?
55
61
 
56
- bl = @book.load_start.dig(code) || 0
57
- full_term = scan_terms(LucaSupport::Config::Pjdir)
62
+ balance = @book.load_start.dig(code) || 0
63
+ full_term = self.class.scan_terms
58
64
  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)
65
+ pre_term = full_term.select { |y, m| y <= year.to_i && m < month.to_i }
66
+ balance += pre_term.map { |y, m| self.class.net(y, m)}.inject(0){|sum, h| sum + h[code] }
67
+ [{ code: code, balance: balance, note: "#{code} #{@dict.dig(code, :label)}" }] + records_with_balance(year, month, code, balance)
62
68
  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)
69
+ start = { code: code, balance: balance, note: "#{code} #{@dict.dig(code, :label)}" }
70
+ full_term.map { |y, m| y }.uniq.map { |y|
71
+ records_with_balance(y, nil, code, balance)
66
72
  }.flatten.prepend(start)
67
73
  end
68
74
  end
69
75
 
70
76
  def records_with_balance(year, month, code, balance)
71
77
  @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)
78
+ balance += Util.calc_diff(Util.amount_by_code(h[:debit], code), code) - Util.calc_diff(Util.amount_by_code(h[:credit], code), code)
73
79
  h[:balance] = balance
74
80
  end
75
81
  end
76
82
 
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
83
  def to_yaml
105
- YAML.dump(code2label).tap { |data| puts data }
84
+ YAML.dump(readable(code2label)).tap { |data| puts data }
106
85
  end
107
86
 
108
87
  def code2label
109
88
  @statement ||= @data
110
89
  @statement.map do |report|
111
90
  {}.tap do |h|
112
- report.each { |k, v| h[@dict.dig(k, :label)] = v }
91
+ report.each { |k, v| h[@dict.dig(k, :label) || k] = v }
113
92
  end
114
93
  end
115
94
  end
116
95
 
117
- def bs
118
- @statement = @data.map do |data|
119
- data.select { |k, v| /^[0-9].+/.match(k) }
96
+ def stats(level = nil)
97
+ keys = @count.map(&:keys).flatten.push('_t').uniq.sort
98
+ @count.map! do |data|
99
+ sum = 0
100
+ keys.each do |k|
101
+ data[k] ||= 0
102
+ sum += data[k] if /^[^_]/.match(k)
103
+ next if level.nil? || k.length <= level
104
+
105
+ if data[k[0, level]]
106
+ data[k[0, level]] += data[k]
107
+ else
108
+ data[k[0, level]] = data[k]
109
+ end
110
+ end
111
+ data.select! { |k, _v| k.length <= level } if level
112
+ data['_t'] = sum
113
+ data.sort.to_h
114
+ end
115
+ keys.map! { |k| k[0, level] }.uniq.select! { |k| k.length <= level } if level
116
+ @count.prepend({}.tap { |header| keys.each { |k| header[k] = @dict.dig(k, :label) }})
117
+ puts YAML.dump(@count)
118
+ @count
119
+ end
120
+
121
+ def bs(level = 3, legal: false)
122
+ @start_balance.keys.each { |k| @data.first[k] ||= 0 }
123
+ @data.map! { |data| data.select { |k, _v| k.length <= level } }
124
+ @data.map! { |data| code_sum(data).merge(data) } if legal
125
+ base = accumulate_balance(@data)
126
+ rows = [base[:debit].length, base[:credit].length].max
127
+ @statement = [].tap do |a|
128
+ rows.times do |i|
129
+ {}.tap do |res|
130
+ res['debit_label'] = base[:debit][i] ? @dict.dig(base[:debit][i].keys[0], :label) : ''
131
+ res['debit_balance'] = base[:debit][i] ? (@start_balance.dig(base[:debit][i].keys[0]) || 0) + base[:debit][i].values[0] : ''
132
+ res['debit_diff'] = base[:debit][i] ? base[:debit][i].values[0] : ''
133
+ res['credit_label'] = base[:credit][i] ? @dict.dig(base[:credit][i].keys[0], :label) : ''
134
+ res['credit_balance'] = base[:credit][i] ? (@start_balance.dig(base[:credit][i].keys[0]) || 0) + base[:credit][i].values[0] : ''
135
+ res['credit_diff'] = base[:credit][i] ? base[:credit][i].values[0] : ''
136
+ a << res
137
+ end
138
+ end
120
139
  end
140
+ puts YAML.dump(readable(@statement))
121
141
  self
122
142
  end
123
143
 
124
- def pl
144
+ def accumulate_balance(monthly_diffs)
145
+ data = monthly_diffs.each_with_object({}) do |month, h|
146
+ month.each do |k, v|
147
+ h[k] = h[k].nil? ? v : h[k] + v
148
+ end
149
+ end
150
+ { debit: [], credit: [] }.tap do |res|
151
+ data.sort.to_h.each do |k, v|
152
+ case k
153
+ when /^[0-4].*/
154
+ res[:debit] << { k => v }
155
+ when /^[5-9].*/
156
+ res[:credit] << { k => v }
157
+ end
158
+ end
159
+ end
160
+ end
161
+
162
+ def pl(level = 2)
163
+ term_keys = @data.inject([]) { |a, data| a + data.keys }
164
+ .compact.select { |k| /^[A-H_].+/.match(k) }
165
+ fy = @start_balance.select { |k, _v| /^[A-H].+/.match(k) }
166
+ keys = (term_keys + fy.keys).uniq.sort
167
+ keys.select! { |k| k.length <= level }
125
168
  @statement = @data.map do |data|
126
- data.select { |k, v| /^[A-F].+/.match(k) }
169
+ {}.tap do |h|
170
+ keys.each { |k| h[k] = data[k] || BigDecimal('0') }
171
+ end
127
172
  end
173
+ term = @statement.each_with_object({}) do |item, h|
174
+ item.each do |k, v|
175
+ h[k] = h[k].nil? ? v : h[k] + v if /^[^_]/.match(k)
176
+ end
177
+ end
178
+ fy = {}.tap do |h|
179
+ keys.each do |k|
180
+ h[k] = BigDecimal(fy[k] || '0') + BigDecimal(term[k] || '0')
181
+ end
182
+ end
183
+ @statement << term.tap { |h| h['_d'] = 'Period Total' }
184
+ @statement << fy.tap { |h| h['_d'] = 'FY Total' }
128
185
  self
129
186
  end
130
187
 
131
- def self.accumulate_month(year, month)
132
- monthly_record = net(year, month)
133
- total_subaccount(monthly_record)
188
+ def self.accumulate_term(start_year, start_month, end_year, end_month)
189
+ date = Date.new(start_year, start_month, 1)
190
+ last_date = Date.new(end_year, end_month, -1)
191
+ return nil if date > last_date
192
+
193
+ {}.tap do |res|
194
+ while date <= last_date do
195
+ diff, _count = net(date.year, date.month)
196
+ diff.each do |k, v|
197
+ next if /^[_]/.match(k)
198
+
199
+ res[k] = res[k].nil? ? v : res[k] + v
200
+ end
201
+ date = date.next_month
202
+ end
203
+ end
134
204
  end
135
205
 
136
- def amount_by_code(items, code)
137
- items
138
- .select{|item| item.dig(:code) == code }
139
- .inject(0){|sum, item| sum + item[:amount] }
206
+ def self.accumulate_month(year, month)
207
+ monthly_record, count = net(year, month)
208
+ [total_subaccount(monthly_record), count]
140
209
  end
141
210
 
211
+ # Accumulate Level 2, 3 account.
212
+ #
142
213
  def self.total_subaccount(report)
143
- report.dup.tap do |res|
214
+ {}.tap do |res|
215
+ res['A0'] = sum_matched(report, /^[A][0-9A-Z]{2,}/)
216
+ res['B0'] = sum_matched(report, /^[B][0-9A-Z]{2,}/)
217
+ res['BA'] = res['A0'] - res['B0']
218
+ res['C0'] = sum_matched(report, /^[C][0-9A-Z]{2,}/)
219
+ res['CA'] = res['BA'] - res['C0']
220
+ res['D0'] = sum_matched(report, /^[D][0-9A-Z]{2,}/)
221
+ res['E0'] = sum_matched(report, /^[E][0-9A-Z]{2,}/)
222
+ res['EA'] = res['CA'] + res['D0'] - res['E0']
223
+ res['F0'] = sum_matched(report, /^[F][0-9A-Z]{2,}/)
224
+ res['G0'] = sum_matched(report, /^[G][0-9][0-9A-Z]{1,}/)
225
+ res['GA'] = res['EA'] + res['F0'] - res['G0']
226
+ res['H0'] = sum_matched(report, /^[H][0-9][0-9A-Z]{1,}/)
227
+ res['HA'] = res['GA'] - res['H0']
228
+
229
+ report['9142'] = (report['9142'] || BigDecimal('0')) + res['HA']
230
+ res['9142'] = report['9142']
231
+ res['10'] = sum_matched(report, /^[123][0-9A-Z]{2,}/)
232
+ res['40'] = sum_matched(report, /^[4][0-9A-Z]{2,}/)
233
+ res['50'] = sum_matched(report, /^[56][0-9A-Z]{2,}/)
234
+ res['70'] = sum_matched(report, /^[78][0-9A-Z]{2,}/)
235
+ res['91'] = sum_matched(report, /^91[0-9A-Z]{1,}/)
236
+ res['8ZZ'] = res['50'] + res['70']
237
+ res['9ZZ'] = sum_matched(report, /^[9][0-9A-Z]{2,}/)
238
+
239
+ res['1'] = res['10'] + res['40']
240
+ res['5'] = res['8ZZ'] + res['9ZZ']
241
+ res['_d'] = report['_d']
242
+
243
+ report.each do |k, v|
244
+ res[k] = v if k.length == 3
245
+ end
246
+
144
247
  report.each do |k, v|
145
248
  if k.length >= 4
146
249
  if res[k[0, 3]]
@@ -150,54 +253,78 @@ module LucaBook
150
253
  end
151
254
  end
152
255
  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]/)
256
+ res.sort.to_h
170
257
  end
171
258
  end
172
259
 
260
+ def code_sum(report)
261
+ legal_items.each.with_object({}) do |k, h|
262
+ h[k] = self.class.sum_matched(report, /^#{k}.*/)
263
+ end
264
+ end
265
+
266
+ def set_balance
267
+ pre_last = @start_date.prev_month
268
+ pre = if @start_date.month > LucaSupport::CONFIG['fy_start'].to_i
269
+ self.class.accumulate_term(pre_last.year, LucaSupport::CONFIG['fy_start'], pre_last.year, pre_last.month)
270
+ elsif @start_date.month < LucaSupport::CONFIG['fy_start'].to_i
271
+ self.class.accumulate_term(pre_last.year - 1, LucaSupport::CONFIG['fy_start'], pre_last.year, pre_last.month)
272
+ end
273
+
274
+ base = Dict.latest_balance.each_with_object({}) do |(k, v), h|
275
+ h[k] = BigDecimal(v[:balance].to_s) if v[:balance]
276
+ end
277
+ if pre
278
+ idx = (pre.keys + base.keys).uniq
279
+ base = {}.tap do |h|
280
+ idx.each { |k| h[k] = (base[k] || BigDecimal('0')) + (pre[k] || BigDecimal('0')) }
281
+ end
282
+ end
283
+ self.class.total_subaccount(base)
284
+ end
285
+
173
286
  def self.sum_matched(report, reg)
174
287
  report.select { |k, v| reg.match(k)}.values.sum
175
288
  end
176
289
 
177
290
  # for assert purpose
291
+ #
178
292
  def self.gross(year, month = nil, code = nil, date_range = nil, rows = 4)
179
293
  if ! date_range.nil?
180
294
  raise if date_range.class != Range
181
295
  # TODO: date based range search
182
296
  end
183
297
 
184
- sum = { debit: {}, credit: {} }
298
+ sum = { debit: {}, credit: {}, debit_count: {}, credit_count: {} }
185
299
  idx_memo = []
186
300
  asof(year, month) do |f, _path|
187
301
  CSV.new(f, headers: false, col_sep: "\t", encoding: 'UTF-8')
188
302
  .each_with_index do |row, i|
189
303
  break if i >= rows
304
+
190
305
  case i
191
306
  when 0
192
307
  idx_memo = row.map(&:to_s)
193
- idx_memo.each { |r| sum[:debit][r] ||= 0 }
308
+ idx_memo.each do |r|
309
+ sum[:debit][r] ||= BigDecimal('0')
310
+ sum[:debit_count][r] ||= 0
311
+ end
194
312
  when 1
195
- row.each_with_index { |r, i| sum[:debit][idx_memo[i]] += r.to_i } # TODO: bigdecimal support
313
+ row.each_with_index do |r, j|
314
+ sum[:debit][idx_memo[j]] += BigDecimal(r.to_s)
315
+ sum[:debit_count][idx_memo[j]] += 1
316
+ end
196
317
  when 2
197
318
  idx_memo = row.map(&:to_s)
198
- idx_memo.each { |r| sum[:credit][r] ||= 0 }
319
+ idx_memo.each do |r|
320
+ sum[:credit][r] ||= BigDecimal('0')
321
+ sum[:credit_count][r] ||= 0
322
+ end
199
323
  when 3
200
- row.each_with_index { |r, i| sum[:credit][idx_memo[i]] += r.to_i } # TODO: bigdecimal support
324
+ row.each_with_index do |r, j|
325
+ sum[:credit][idx_memo[j]] += BigDecimal(r.to_s)
326
+ sum[:credit_count][idx_memo[j]] += 1
327
+ end
201
328
  else
202
329
  puts row # for debug
203
330
  end
@@ -207,52 +334,38 @@ module LucaBook
207
334
  end
208
335
 
209
336
  # netting vouchers in specified term
337
+ #
210
338
  def self.net(year, month = nil, code = nil, date_range = nil)
211
339
  g = gross(year, month, code, date_range)
212
340
  idx = (g[:debit].keys + g[:credit].keys).uniq.sort
213
- {}.tap do |sum|
341
+ count = {}
342
+ diff = {}.tap do |sum|
214
343
  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)
344
+ sum[code] = g.dig(:debit, code).nil? ? BigDecimal('0') : Util.calc_diff(g[:debit][code], code)
345
+ sum[code] -= g.dig(:credit, code).nil? ? BigDecimal('0') : Util.calc_diff(g[:credit][code], code)
346
+ count[code] = (g.dig(:debit_count, code) || 0) + (g.dig(:credit_count, code) || 0)
217
347
  end
218
348
  end
349
+ [diff, count]
219
350
  end
220
351
 
221
- # TODO: replace load_tsv -> generic load_tsv_dict
352
+ # TODO: obsolete in favor of Dict.latest_balance()
222
353
  def load_start
223
- file = LucaSupport::Config::Pjdir + 'start.tsv'
224
- {}.tap do |dic|
225
- load_tsv(file) do |row|
226
- dic[row[0]] = row[2].to_i if ! row[2].nil?
227
- end
354
+ file = Pathname(LucaSupport::Config::Pjdir) / 'data' / 'balance' / 'start.tsv'
355
+ {}.tap do |dict|
356
+ LucaRecord::Dict.load_tsv_dict(file).each { |k, v| h[k] = v[:balance] if !v[:balance].nil? }
228
357
  end
229
358
  end
230
359
 
231
- def load_tsv(path)
232
- return enum_for(:load_tsv, path) unless block_given?
233
-
234
- data = CSV.read(path, headers: true, col_sep: "\t", encoding: 'UTF-8')
235
- data.each { |row| yield row }
236
- end
360
+ private
237
361
 
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
362
+ def legal_items
363
+ return [] unless LucaSupport::Config::COUNTRY
242
364
 
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
365
+ case LucaSupport::Config::COUNTRY
366
+ when 'jp'
367
+ ['91', '911', '912', '913', '9131', '9132', '914', '9141', '9142', '915', '916', '92', '93']
251
368
  end
252
369
  end
253
-
254
- def dict
255
- LucaBook::Dict::Data
256
- end
257
370
  end
258
371
  end