lucabook 0.2.10 → 0.2.18

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