lucabook 0.2.12 → 0.2.20

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