lucabook 0.2.11 → 0.2.19

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.
@@ -12,28 +12,39 @@ module LucaBook
12
12
 
13
13
  # create journal from hash
14
14
  #
15
- def self.create!(d)
15
+ def self.create(d)
16
16
  date = Date.parse(d['date'])
17
17
 
18
- debit_amount = serialize_on_key(d['debit'], 'value')
19
- 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'))
20
20
  raise 'BalanceUnmatch' if debit_amount.inject(:+) != credit_amount.inject(:+)
21
21
 
22
22
  debit_code = serialize_on_key(d['debit'], 'code')
23
23
  credit_code = serialize_on_key(d['credit'], 'code')
24
24
 
25
- # TODO: limit code length for filename
26
- 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
27
28
  create_record!(date, codes) do |f|
28
29
  f << debit_code
29
- f << debit_amount
30
+ f << LucaSupport::Code.readable(debit_amount)
30
31
  f << credit_code
31
- 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
32
36
  f << []
33
37
  f << [d.dig('note')]
34
38
  end
35
39
  end
36
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
+
37
48
  # define new transaction ID & write data at once
38
49
  def self.create_record!(date_obj, codes = nil)
39
50
  create_record(nil, date_obj, codes) do |f|
@@ -52,32 +63,28 @@ module LucaBook
52
63
  def self.load_data(io, path)
53
64
  {}.tap do |record|
54
65
  body = false
55
- record[:id] = path[0] + path[1]
66
+ record[:id] = "#{path[0]}/#{path[1]}"
56
67
  CSV.new(io, headers: false, col_sep: "\t", encoding: 'UTF-8')
57
68
  .each.with_index(0) do |line, i|
58
69
  case i
59
70
  when 0
60
71
  record[:debit] = line.map { |row| { code: row } }
61
72
  when 1
62
- line.each_with_index do |amount, i|
63
- record[:debit][i][:amount] = amount.to_i # TODO: bigdecimal support
64
- end
73
+ line.each_with_index { |amount, j| record[:debit][j][:amount] = BigDecimal(amount.to_s) }
65
74
  when 2
66
75
  record[:credit] = line.map { |row| { code: row } }
67
76
  when 3
68
- line.each_with_index do |amount, i|
69
- record[:credit][i][:amount] = amount.to_i # TODO: bigdecimal support
70
- end
77
+ line.each_with_index { |amount, j| record[:credit][j][:amount] = BigDecimal(amount.to_s) }
71
78
  else
72
- if line.empty?
79
+ if body == false && line.empty?
73
80
  record[:note] ||= []
74
81
  body = true
75
- next
82
+ else
83
+ record[:note] << line.join(' ') if body
76
84
  end
77
- record[:note] << line.join(' ') if body
78
85
  end
79
- record[:note]&.join('\n')
80
86
  end
87
+ record[:note] = record[:note]&.join('\n')
81
88
  end
82
89
  end
83
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,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,66 +44,42 @@ 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
84
  YAML.dump(code2label).tap { |data| puts data }
106
85
  end
@@ -109,38 +88,150 @@ module LucaBook
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 }
92
+ end
93
+ end
94
+ end
95
+
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
113
110
  end
111
+ data.select! { |k, _v| k.length <= level } if level
112
+ data['_t'] = sum
113
+ data.sort.to_h
114
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
115
119
  end
116
120
 
117
- def bs
118
- @statement = @data.map do |data|
119
- data.select { |k, v| /^[0-9].+/.match(k) }
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(@statement)
121
141
  self
122
142
  end
123
143
 
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
+
124
162
  def pl
125
- @statement = @data.map do |data|
126
- data.select { |k, v| /^[A-F].+/.match(k) }
163
+ @statement = @data.map { |data| data.select { |k, _v| /^[A-H_].+/.match(k) } }
164
+ term = @statement.each_with_object({}) { |item, h| item.each { |k, v| h[k].nil? ? h[k] = v : h[k] += v } }
165
+ fy = @start_balance.select { |k, _v| /^[A-H].+/.match(k) }
166
+ fy = {}.tap do |h|
167
+ (term.keys + fy.keys).uniq.each do |k|
168
+ h[k] = (fy[k] || 0).to_i + (term[k] || 0).to_i
169
+ end
127
170
  end
171
+ @statement << term.tap { |h| h['_d'] = 'Period Total' }
172
+ @statement << fy.tap { |h| h['_d'] = 'FY Total' }
128
173
  self
129
174
  end
130
175
 
131
- def self.accumulate_month(year, month)
132
- monthly_record = net(year, month)
133
- total_subaccount(monthly_record)
176
+ def self.accumulate_term(start_year, start_month, end_year, end_month)
177
+ date = Date.new(start_year, start_month, 1)
178
+ last_date = Date.new(end_year, end_month, -1)
179
+ return nil if date > last_date
180
+
181
+ {}.tap do |res|
182
+ while date <= last_date do
183
+ diff, _count = net(date.year, date.month)
184
+ diff.each do |k, v|
185
+ next if /^[_]/.match(k)
186
+
187
+ res[k] = res[k].nil? ? v : res[k] + v
188
+ end
189
+ date = date.next_month
190
+ end
191
+ end
134
192
  end
135
193
 
136
- def amount_by_code(items, code)
137
- items
138
- .select{|item| item.dig(:code) == code }
139
- .inject(0){|sum, item| sum + item[:amount] }
194
+ def self.accumulate_month(year, month)
195
+ monthly_record, count = net(year, month)
196
+ [total_subaccount(monthly_record), count]
140
197
  end
141
198
 
199
+ # Accumulate Level 2, 3 account.
200
+ #
142
201
  def self.total_subaccount(report)
143
- report.dup.tap do |res|
202
+ {}.tap do |res|
203
+ res['A0'] = sum_matched(report, /^[A][0-9A-Z]{2,}/)
204
+ res['B0'] = sum_matched(report, /^[B][0-9A-Z]{2,}/)
205
+ res['BA'] = res['A0'] - res['B0']
206
+ res['C0'] = sum_matched(report, /^[C][0-9A-Z]{2,}/)
207
+ res['CA'] = res['BA'] - res['C0']
208
+ res['D0'] = sum_matched(report, /^[D][0-9A-Z]{2,}/)
209
+ res['E0'] = sum_matched(report, /^[E][0-9A-Z]{2,}/)
210
+ res['EA'] = res['CA'] + res['D0'] - res['E0']
211
+ res['F0'] = sum_matched(report, /^[F][0-9A-Z]{2,}/)
212
+ res['G0'] = sum_matched(report, /^[G][0-9][0-9A-Z]{1,}/)
213
+ res['GA'] = res['EA'] + res['F0'] - res['G0']
214
+ res['H0'] = sum_matched(report, /^[H][0-9][0-9A-Z]{1,}/)
215
+ res['HA'] = res['GA'] - res['H0']
216
+
217
+ report['9142'] = (report['9142'] || 0) + res['HA']
218
+ res['9142'] = report['9142']
219
+ res['10'] = sum_matched(report, /^[123][0-9A-Z]{2,}/)
220
+ res['40'] = sum_matched(report, /^[4][0-9A-Z]{2,}/)
221
+ res['50'] = sum_matched(report, /^[56][0-9A-Z]{2,}/)
222
+ res['70'] = sum_matched(report, /^[78][0-9A-Z]{2,}/)
223
+ res['91'] = sum_matched(report, /^91[0-9A-Z]{1,}/)
224
+ res['8ZZ'] = res['50'] + res['70']
225
+ res['9ZZ'] = sum_matched(report, /^[9][0-9A-Z]{2,}/)
226
+
227
+ res['1'] = res['10'] + res['40']
228
+ res['5'] = res['8ZZ'] + res['9ZZ']
229
+ res['_d'] = report['_d']
230
+
231
+ report.each do |k, v|
232
+ res[k] = v if k.length == 3
233
+ end
234
+
144
235
  report.each do |k, v|
145
236
  if k.length >= 4
146
237
  if res[k[0, 3]]
@@ -150,26 +241,35 @@ module LucaBook
150
241
  end
151
242
  end
152
243
  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]/)
244
+ res.sort.to_h
170
245
  end
171
246
  end
172
247
 
248
+ def code_sum(report)
249
+ legal_items.each.with_object({}) do |k, h|
250
+ h[k] = self.class.sum_matched(report, /^#{k}.*/)
251
+ end
252
+ end
253
+
254
+ def set_balance
255
+ pre_last = @start_date.prev_month
256
+ pre = if @start_date.month > LucaSupport::CONFIG['fy_start'].to_i
257
+ self.class.accumulate_term(pre_last.year, LucaSupport::CONFIG['fy_start'], pre_last.year, pre_last.month)
258
+ elsif @start_date.month < LucaSupport::CONFIG['fy_start'].to_i
259
+ self.class.accumulate_term(pre_last.year - 1, LucaSupport::CONFIG['fy_start'], pre_last.year, pre_last.month)
260
+ end
261
+
262
+ base = Dict.latest_balance.each_with_object({}) do |(k, v), h|
263
+ h[k] = v[:balance].to_i if v[:balance]
264
+ end
265
+ if pre
266
+ idx = (pre.keys + base.keys).uniq
267
+ base = {}.tap { |h| idx.each { |k| h[k] = (base[k] || 0) + (pre[k] || 0) } }
268
+ end
269
+ #code_sum(base).merge(self.class.total_subaccount(base))
270
+ self.class.total_subaccount(base)
271
+ end
272
+
173
273
  def self.sum_matched(report, reg)
174
274
  report.select { |k, v| reg.match(k)}.values.sum
175
275
  end
@@ -181,7 +281,7 @@ module LucaBook
181
281
  # TODO: date based range search
182
282
  end
183
283
 
184
- sum = { debit: {}, credit: {} }
284
+ sum = { debit: {}, credit: {}, debit_count: {}, credit_count: {} }
185
285
  idx_memo = []
186
286
  asof(year, month) do |f, _path|
187
287
  CSV.new(f, headers: false, col_sep: "\t", encoding: 'UTF-8')
@@ -190,14 +290,26 @@ module LucaBook
190
290
  case i
191
291
  when 0
192
292
  idx_memo = row.map(&:to_s)
193
- idx_memo.each { |r| sum[:debit][r] ||= 0 }
293
+ idx_memo.each do |r|
294
+ sum[:debit][r] ||= 0
295
+ sum[:debit_count][r] ||= 0
296
+ end
194
297
  when 1
195
- row.each_with_index { |r, i| sum[:debit][idx_memo[i]] += r.to_i } # TODO: bigdecimal support
298
+ row.each_with_index do |r, j|
299
+ sum[:debit][idx_memo[j]] += r.to_i # TODO: bigdecimal support
300
+ sum[:debit_count][idx_memo[j]] += 1
301
+ end
196
302
  when 2
197
303
  idx_memo = row.map(&:to_s)
198
- idx_memo.each { |r| sum[:credit][r] ||= 0 }
304
+ idx_memo.each do |r|
305
+ sum[:credit][r] ||= 0
306
+ sum[:credit_count][r] ||= 0
307
+ end
199
308
  when 3
200
- row.each_with_index { |r, i| sum[:credit][idx_memo[i]] += r.to_i } # TODO: bigdecimal support
309
+ row.each_with_index do |r, j|
310
+ sum[:credit][idx_memo[j]] += r.to_i # TODO: bigdecimal support
311
+ sum[:credit_count][idx_memo[j]] += 1
312
+ end
201
313
  else
202
314
  puts row # for debug
203
315
  end
@@ -210,20 +322,23 @@ module LucaBook
210
322
  def self.net(year, month = nil, code = nil, date_range = nil)
211
323
  g = gross(year, month, code, date_range)
212
324
  idx = (g[:debit].keys + g[:credit].keys).uniq.sort
213
- {}.tap do |sum|
325
+ count = {}
326
+ diff = {}.tap do |sum|
214
327
  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)
328
+ sum[code] = g.dig(:debit, code).nil? ? 0 : Util.calc_diff(g[:debit][code], code)
329
+ sum[code] -= g.dig(:credit, code).nil? ? 0 : Util.calc_diff(g[:credit][code], code)
330
+ count[code] = (g.dig(:debit_count, code) || 0) + (g.dig(:credit_count, code) || 0)
217
331
  end
218
332
  end
333
+ [diff, count]
219
334
  end
220
335
 
221
336
  # TODO: replace load_tsv -> generic load_tsv_dict
222
337
  def load_start
223
- file = LucaSupport::Config::Pjdir + 'start.tsv'
224
- {}.tap do |dic|
338
+ file = Pathname(LucaSupport::Config::Pjdir) / 'data' / 'balance' / 'start.tsv'
339
+ {}.tap do |dict|
225
340
  load_tsv(file) do |row|
226
- dic[row[0]] = row[2].to_i if ! row[2].nil?
341
+ dict[row[0]] = row[2].to_i if ! row[2].nil?
227
342
  end
228
343
  end
229
344
  end
@@ -235,24 +350,15 @@ module LucaBook
235
350
  data.each { |row| yield row }
236
351
  end
237
352
 
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
353
+ private
242
354
 
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
355
+ def legal_items
356
+ return [] unless LucaSupport::Config::COUNTRY
253
357
 
254
- def dict
255
- LucaBook::Dict::Data
358
+ case LucaSupport::Config::COUNTRY
359
+ when 'jp'
360
+ ['91', '911', '912', '913', '9131', '9132', '914', '9141', '9142', '915', '916', '92', '93']
361
+ end
256
362
  end
257
363
  end
258
364
  end