lucabook 0.2.19 → 0.2.24

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,43 +1,90 @@
1
- #
2
- # manipulate files based on transaction date
3
- #
1
+ # frozen_string_literal: true
4
2
 
5
3
  require 'csv'
6
4
  require 'date'
7
5
  require 'luca_record'
8
6
 
9
- module LucaBook
7
+ module LucaBook #:nodoc:
8
+ # Journal has several annotations on headers:
9
+ #
10
+ # x-customer::
11
+ # Identifying customer.
12
+ # x-editor::
13
+ # Application name editing the journal.
14
+ # x-tax::
15
+ # For tracking tax related transaction.
16
+ #
10
17
  class Journal < LucaRecord::Base
18
+ ACCEPTED_HEADERS = ['x-customer', 'x-editor', 'x-tax']
11
19
  @dirname = 'journals'
12
20
 
13
21
  # create journal from hash
14
22
  #
15
- def self.create(d)
23
+ def self.create(dat)
24
+ d = LucaSupport::Code.keys_stringify(dat)
25
+ validate(d)
26
+ raise 'NoDateKey' unless d.key?('date')
27
+
16
28
  date = Date.parse(d['date'])
17
29
 
18
- debit_amount = LucaSupport::Code.decimalize(serialize_on_key(d['debit'], 'value'))
19
- credit_amount = LucaSupport::Code.decimalize(serialize_on_key(d['credit'], 'value'))
30
+ # TODO: need to sync filename & content. Limit code length for filename
31
+ # codes = (debit_code + credit_code).uniq
32
+ codes = nil
33
+
34
+ create_record(nil, date, codes) { |f| f.write journal2csv(d) }
35
+ end
36
+
37
+ # update journal with hash.
38
+ # If record not found with id, no record will be created.
39
+ #
40
+ def self.save(dat)
41
+ d = LucaSupport::Code.keys_stringify(dat)
42
+ raise 'record has no id.' if d['id'].nil?
43
+
44
+ validate(d)
45
+ parts = d['id'].split('/')
46
+ raise 'invalid ID' if parts.length != 2
47
+
48
+ codes = nil
49
+ open_records(@dirname, parts[0], parts[1], codes, 'w') { |f, _path| f.write journal2csv(d) }
50
+ end
51
+
52
+ # Convert journal object to TSV format.
53
+ #
54
+ def self.journal2csv(d)
55
+ debit_amount = LucaSupport::Code.decimalize(serialize_on_key(d['debit'], 'amount'))
56
+ credit_amount = LucaSupport::Code.decimalize(serialize_on_key(d['credit'], 'amount'))
20
57
  raise 'BalanceUnmatch' if debit_amount.inject(:+) != credit_amount.inject(:+)
21
58
 
22
59
  debit_code = serialize_on_key(d['debit'], 'code')
23
60
  credit_code = serialize_on_key(d['credit'], 'code')
24
61
 
25
- # TODO: need to sync filename & content. Limit code length for filename
26
- # codes = (debit_code + credit_code).uniq
27
- codes = nil
28
- create_record!(date, codes) do |f|
62
+ csv = CSV.generate(String.new, col_sep: "\t", headers: false) do |f|
29
63
  f << debit_code
30
64
  f << LucaSupport::Code.readable(debit_amount)
31
65
  f << credit_code
32
66
  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)
67
+ ACCEPTED_HEADERS.each do |x_header|
68
+ f << [x_header, d['headers'][x_header]] if d.dig('headers', x_header)
35
69
  end
36
70
  f << []
37
71
  f << [d.dig('note')]
38
72
  end
39
73
  end
40
74
 
75
+ # Set accepted header with key/value
76
+ #
77
+ def self.add_header(journal_hash, key, val)
78
+ return journal_hash if val.nil?
79
+ return journal_hash unless ACCEPTED_HEADERS.include?(key)
80
+
81
+ journal_hash.tap do |o|
82
+ o[:headers] = {} unless o.dig(:headers)
83
+ o[:headers][key] = val
84
+ save o
85
+ end
86
+ end
87
+
41
88
  def self.update_codes(obj)
42
89
  debit_code = serialize_on_key(obj[:debit], :code)
43
90
  credit_code = serialize_on_key(obj[:credit], :code)
@@ -45,11 +92,19 @@ module LucaBook
45
92
  change_codes(obj[:id], codes)
46
93
  end
47
94
 
48
- # define new transaction ID & write data at once
49
- def self.create_record!(date_obj, codes = nil)
50
- create_record(nil, date_obj, codes) do |f|
51
- f.write CSV.generate('', col_sep: "\t", headers: false) { |c| yield(c) }
52
- end
95
+ def self.validate(obj)
96
+ raise 'NoDebitKey' unless obj.key?('debit')
97
+ raise 'NoCreditKey' unless obj.key?('credit')
98
+ debit_codes = serialize_on_key(obj['debit'], 'code').compact
99
+ debit_amount = serialize_on_key(obj['debit'], 'amount').compact
100
+ raise 'NoDebitCode' if debit_codes.empty?
101
+ raise 'NoDebitAmount' if debit_amount.empty?
102
+ raise 'UnmatchDebit' if debit_codes.length != debit_amount.length
103
+ credit_codes = serialize_on_key(obj['credit'], 'code').compact
104
+ credit_amount = serialize_on_key(obj['credit'], 'amount').compact
105
+ raise 'NoCreditCode' if credit_codes.empty?
106
+ raise 'NoCreditAmount' if credit_amount.empty?
107
+ raise 'UnmatchCredit' if credit_codes.length != credit_amount.length
53
108
  end
54
109
 
55
110
  # collect values on specified key
@@ -58,7 +113,21 @@ module LucaBook
58
113
  array_of_hash.map { |h| h[key] }
59
114
  end
60
115
 
61
- # override de-serializing journal format
116
+ # override de-serializing journal format. Sample format is:
117
+ #
118
+ # {
119
+ # id: '2021A/V001',
120
+ # headers: {
121
+ # 'x-customer' => 'Some Customer Co.'
122
+ # },
123
+ # debit: [
124
+ # { code: 'A12', amount: 1000 }
125
+ # ],
126
+ # credit: [
127
+ # { code: '311', amount: 1000 }
128
+ # ],
129
+ # note: 'note for each journal'
130
+ # }
62
131
  #
63
132
  def self.load_data(io, path)
64
133
  {}.tap do |record|
@@ -76,10 +145,16 @@ module LucaBook
76
145
  when 3
77
146
  line.each_with_index { |amount, j| record[:credit][j][:amount] = BigDecimal(amount.to_s) }
78
147
  else
79
- if body == false && line.empty?
80
- record[:note] ||= []
81
- body = true
82
- else
148
+ case body
149
+ when false
150
+ if line.empty?
151
+ record[:note] ||= []
152
+ body = true
153
+ else
154
+ record[:headers] ||= {}
155
+ record[:headers][line[0]] = line[1]
156
+ end
157
+ when true
83
158
  record[:note] << line.join(' ') if body
84
159
  end
85
160
  end
@@ -7,11 +7,12 @@ require 'luca_record'
7
7
  require 'luca_record/dict'
8
8
  require 'luca_book'
9
9
 
10
- # Journal List on specified term
11
- #
12
- module LucaBook
10
+ module LucaBook #:nodoc:
11
+ # Journal List on specified term
12
+ #
13
13
  class List < LucaBook::Journal
14
14
  @dirname = 'journals'
15
+ attr_reader :data
15
16
 
16
17
  def initialize(data, start_date, code = nil)
17
18
  @data = data
@@ -31,7 +32,17 @@ module LucaBook
31
32
  new data, Date.new(from_year.to_i, from_month.to_i, 1), code
32
33
  end
33
34
 
34
- def list_on_code
35
+ def self.add_header(from_year, from_month, to_year = from_year, to_month = from_month, code: nil, header_key: nil, header_val: nil)
36
+ return nil if code.nil?
37
+ return nil unless Journal::ACCEPTED_HEADERS.include?(header_key)
38
+
39
+ term(from_year, from_month, to_year, to_month, code: code)
40
+ .data.each do |journal|
41
+ Journal.add_header(journal, header_key, header_val)
42
+ end
43
+ end
44
+
45
+ def list_by_code
35
46
  calc_code
36
47
  convert_label
37
48
  @data = [code_header] + @data.map do |dat|
@@ -47,7 +58,7 @@ module LucaBook
47
58
  res['note'] = dat[:note]
48
59
  end
49
60
  end
50
- self
61
+ readable(@data)
51
62
  end
52
63
 
53
64
  def list_journals
@@ -65,7 +76,7 @@ module LucaBook
65
76
  res['note'] = dat[:note]
66
77
  end
67
78
  end
68
- self
79
+ readable(@data)
69
80
  end
70
81
 
71
82
  def accumulate_code
@@ -74,10 +85,6 @@ module LucaBook
74
85
  end
75
86
  end
76
87
 
77
- def to_yaml
78
- YAML.dump(LucaSupport::Code.readable(@data)).tap { |data| puts data }
79
- end
80
-
81
88
  private
82
89
 
83
90
  def set_balance
@@ -0,0 +1,120 @@
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
+ module LucaBook #:nodoc:
11
+ # Journal List on specified term
12
+ #
13
+ class ListByHeader < LucaBook::Journal
14
+ @dirname = 'journals'
15
+
16
+ def initialize(data, start_date, code = nil, header_name = nil)
17
+ @data = data
18
+ @code = code
19
+ @header = header_name
20
+ @start = start_date
21
+ @dict = LucaRecord::Dict.load('base.tsv')
22
+ end
23
+
24
+ def self.term(from_year, from_month, to_year = from_year, to_month = from_month, code: nil, header: nil, basedir: @dirname)
25
+ data = Journal.term(from_year, from_month, to_year, to_month, code).select do |dat|
26
+ if code.nil?
27
+ true
28
+ else
29
+ [:debit, :credit].map { |key| serialize_on_key(dat[key], :code) }.flatten.include?(code)
30
+ end
31
+ end
32
+ new data, Date.new(from_year.to_i, from_month.to_i, 1), code, header
33
+ end
34
+
35
+ def list_by_code
36
+ calc_code
37
+ convert_label
38
+ @data = @data.each_with_object([]) do |(k, v), a|
39
+ journals = v.map do |dat|
40
+ date, txid = decode_id(dat[:id])
41
+ {}.tap do |res|
42
+ res['header'] = k
43
+ res['date'] = date
44
+ res['no'] = txid
45
+ res['id'] = dat[:id]
46
+ res['diff'] = dat[:diff]
47
+ res['balance'] = dat[:balance]
48
+ res['counter_code'] = dat[:counter_code].length == 1 ? dat[:counter_code].first : dat[:counter_code]
49
+ res['note'] = dat[:note]
50
+ end
51
+ end
52
+ a << { 'code' => v.last[:code], 'header' => k, 'balance' => v.last[:balance], 'count' => v.count, 'jounals' => journals }
53
+ end
54
+ readable(@data)
55
+ end
56
+
57
+ def accumulate_code
58
+ @data.each_with_object({}) do |dat, sum|
59
+ idx = dat.dig(:headers, @header) || 'others'
60
+ sum[idx] ||= BigDecimal('0')
61
+ sum[idx] += Util.diff_by_code(dat[:debit], @code) - Util.diff_by_code(dat[:credit], @code)
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def set_balance
68
+ return BigDecimal('0') if @code.nil? || /^[A-H]/.match(@code)
69
+
70
+ balance_dict = Dict.latest_balance
71
+ start_balance = BigDecimal(balance_dict.dig(@code.to_s, :balance) || '0')
72
+ start = Dict.issue_date(balance_dict)&.next_month
73
+ last = @start.prev_month
74
+ if last.year >= start.year && last.month >= start.month
75
+ #TODO: start_balance to be implemented by header
76
+ self.class.term(start.year, start.month, last.year, last.month, code: @code).accumulate_code
77
+ else
78
+ #start_balance
79
+ end
80
+ end
81
+
82
+ def calc_code
83
+ raise 'no account code specified' if @code.nil?
84
+
85
+ @balance = set_balance
86
+ balance = @balance
87
+ res = {}
88
+ @data.each do |dat|
89
+ idx = dat.dig(:headers, @header) || 'others'
90
+ balance[idx] ||= BigDecimal('0')
91
+ res[idx] ||= []
92
+ {}.tap do |h|
93
+ h[:id] = dat[:id]
94
+ h[:diff] = Util.diff_by_code(dat[:debit], @code) - Util.diff_by_code(dat[:credit], @code)
95
+ balance[idx] += h[:diff]
96
+ h[:balance] = balance[idx]
97
+ h[:code] = @code
98
+ counter = h[:diff] * Util.pn_debit(@code) > 0 ? :credit : :debit
99
+ h[:counter_code] = dat[counter].map { |d| d[:code] }
100
+ h[:note] = dat[:note]
101
+ res[idx] << h
102
+ end
103
+ end
104
+ @data = res
105
+ self
106
+ end
107
+
108
+ def convert_label
109
+ @data.each do |_k, v|
110
+ v.each do |dat|
111
+ raise 'no account code specified' if @code.nil?
112
+
113
+ dat[:code] = "#{dat[:code]} #{@dict.dig(dat[:code], :label)}"
114
+ dat[:counter_code] = dat[:counter_code].map { |counter| "#{counter} #{@dict.dig(counter, :label)}" }
115
+ end
116
+ end
117
+ self
118
+ end
119
+ end
120
+ end
@@ -6,7 +6,7 @@ require 'fileutils'
6
6
  module LucaBook
7
7
  class Setup
8
8
  # create project skeleton under specified directory
9
- def self.create_project(country = nil, dir = LucaSupport::Config::Pjdir)
9
+ def self.create_project(country = nil, dir = LucaSupport::PJDIR)
10
10
  FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
11
11
  Dir.chdir(dir) do
12
12
  %w[data/journals data/balance dict].each do |subdir|
@@ -18,6 +18,7 @@ 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
+ FileUtils.cp("#{__dir__}/templates/config.yml", 'config.yml') unless File.exist?('config.yml')
21
22
  prepare_starttsv(dict) unless File.exist? 'data/balance/start.tsv'
22
23
  end
23
24
  end
@@ -25,21 +25,7 @@ module LucaBook
25
25
  @start_balance = set_balance
26
26
  end
27
27
 
28
- # TODO: not compatible with LucaRecord::Base.open_records
29
- def search_tag(code)
30
- count = 0
31
- Dir.children(LucaSupport::Config::Pjdir).sort.each do |dir|
32
- next if ! FileTest.directory?(LucaSupport::Config::Pjdir+dir)
33
-
34
- open_records(datadir, dir, 3) do |row, i|
35
- next if i == 2
36
- count += 1 if row.include?(code)
37
- end
38
- end
39
- puts "#{code}: #{count}"
40
- end
41
-
42
- def self.term(from_year, from_month, to_year = from_year, to_month = from_month)
28
+ def self.range(from_year, from_month, to_year = from_year, to_month = from_month)
43
29
  date = Date.new(from_year.to_i, from_month.to_i, -1)
44
30
  last_date = Date.new(to_year.to_i, to_month.to_i, -1)
45
31
  raise 'invalid term specified' if date > last_date
@@ -56,32 +42,31 @@ module LucaBook
56
42
  new(reports, counts, date: Date.new(from_year.to_i, from_month.to_i, -1))
57
43
  end
58
44
 
59
- def by_code(code, year=nil, month=nil)
60
- raise 'not supported year range yet' if ! year.nil? && month.nil?
61
-
62
- balance = @book.load_start.dig(code) || 0
63
- full_term = self.class.scan_terms
64
- if ! month.nil?
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)
68
- else
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)
72
- }.flatten.prepend(start)
73
- end
74
- end
45
+ def self.by_code(code, from_year, from_month, to_year = from_year, to_month = from_month)
46
+ date = Date.new(from_year.to_i, from_month.to_i, -1)
47
+ last_date = Date.new(to_year.to_i, to_month.to_i, -1)
48
+ raise 'invalid term specified' if date > last_date
75
49
 
76
- def records_with_balance(year, month, code, balance)
77
- @book.search(year, month, nil, code).each do |h|
78
- balance += Util.calc_diff(Util.amount_by_code(h[:debit], code), code) - Util.calc_diff(Util.amount_by_code(h[:credit], code), code)
79
- h[:balance] = balance
50
+ reports = [].tap do |r|
51
+ while date <= last_date do
52
+ diff = {}.tap do |h|
53
+ g = gross(date.year, date.month, code: code)
54
+ sum = g.dig(:debit).nil? ? BigDecimal('0') : Util.calc_diff(g[:debit], code)
55
+ sum -= g.dig(:credit).nil? ? BigDecimal('0') : Util.calc_diff(g[:credit], code)
56
+ h['code'] = code
57
+ h['label'] = LucaRecord::Dict.load('base.tsv').dig(code, :label)
58
+ h['net'] = sum
59
+ h['debit_amount'] = g[:debit]
60
+ h['debit_count'] = g[:debit_count]
61
+ h['credit_amount'] = g[:credit]
62
+ h['credit_count'] = g[:credit_count]
63
+ h['_d'] = date.to_s
64
+ end
65
+ r << diff
66
+ date = Date.new(date.next_month.year, date.next_month.month, -1)
67
+ end
80
68
  end
81
- end
82
-
83
- def to_yaml
84
- YAML.dump(code2label).tap { |data| puts data }
69
+ LucaSupport::Code.readable(reports)
85
70
  end
86
71
 
87
72
  def code2label
@@ -114,7 +99,6 @@ module LucaBook
114
99
  end
115
100
  keys.map! { |k| k[0, level] }.uniq.select! { |k| k.length <= level } if level
116
101
  @count.prepend({}.tap { |header| keys.each { |k| header[k] = @dict.dig(k, :label) }})
117
- puts YAML.dump(@count)
118
102
  @count
119
103
  end
120
104
 
@@ -137,8 +121,7 @@ module LucaBook
137
121
  end
138
122
  end
139
123
  end
140
- puts YAML.dump(@statement)
141
- self
124
+ readable(@statement)
142
125
  end
143
126
 
144
127
  def accumulate_balance(monthly_diffs)
@@ -159,18 +142,30 @@ module LucaBook
159
142
  end
160
143
  end
161
144
 
162
- def pl
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 } }
145
+ def pl(level = 2)
146
+ term_keys = @data.inject([]) { |a, data| a + data.keys }
147
+ .compact.select { |k| /^[A-H_].+/.match(k) }
165
148
  fy = @start_balance.select { |k, _v| /^[A-H].+/.match(k) }
149
+ keys = (term_keys + fy.keys).uniq.sort
150
+ keys.select! { |k| k.length <= level }
151
+ @statement = @data.map do |data|
152
+ {}.tap do |h|
153
+ keys.each { |k| h[k] = data[k] || BigDecimal('0') }
154
+ end
155
+ end
156
+ term = @statement.each_with_object({}) do |item, h|
157
+ item.each do |k, v|
158
+ h[k] = h[k].nil? ? v : h[k] + v if /^[^_]/.match(k)
159
+ end
160
+ end
166
161
  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
162
+ keys.each do |k|
163
+ h[k] = BigDecimal(fy[k] || '0') + BigDecimal(term[k] || '0')
169
164
  end
170
165
  end
171
166
  @statement << term.tap { |h| h['_d'] = 'Period Total' }
172
167
  @statement << fy.tap { |h| h['_d'] = 'FY Total' }
173
- self
168
+ readable(code2label)
174
169
  end
175
170
 
176
171
  def self.accumulate_term(start_year, start_month, end_year, end_month)
@@ -179,14 +174,11 @@ module LucaBook
179
174
  return nil if date > last_date
180
175
 
181
176
  {}.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)
177
+ diff, _count = net(date.year, date.month, last_date.year, last_date.month)
178
+ diff.each do |k, v|
179
+ next if /^[_]/.match(k)
186
180
 
187
- res[k] = res[k].nil? ? v : res[k] + v
188
- end
189
- date = date.next_month
181
+ res[k] = res[k].nil? ? v : res[k] + v
190
182
  end
191
183
  end
192
184
  end
@@ -214,21 +206,24 @@ module LucaBook
214
206
  res['H0'] = sum_matched(report, /^[H][0-9][0-9A-Z]{1,}/)
215
207
  res['HA'] = res['GA'] - res['H0']
216
208
 
217
- report['9142'] = (report['9142'] || 0) + res['HA']
209
+ report['9142'] = (report['9142'] || BigDecimal('0')) + res['HA']
218
210
  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,}/)
211
+ res['10'] = sum_matched(report, /^[12][0-9A-Z]{2,}/)
212
+ jp_4v = sum_matched(report, /^[4][V]{2,}/) # deferred assets for JP GAAP
213
+ res['30'] = sum_matched(report, /^[34][0-9A-Z]{2,}/) - jp_4v
214
+ res['4V'] = jp_4v if CONFIG['country'] == 'jp'
221
215
  res['50'] = sum_matched(report, /^[56][0-9A-Z]{2,}/)
222
216
  res['70'] = sum_matched(report, /^[78][0-9A-Z]{2,}/)
223
217
  res['91'] = sum_matched(report, /^91[0-9A-Z]{1,}/)
224
218
  res['8ZZ'] = res['50'] + res['70']
225
219
  res['9ZZ'] = sum_matched(report, /^[9][0-9A-Z]{2,}/)
226
220
 
227
- res['1'] = res['10'] + res['40']
221
+ res['1'] = res['10'] + res['30']
228
222
  res['5'] = res['8ZZ'] + res['9ZZ']
229
223
  res['_d'] = report['_d']
230
224
 
231
225
  report.each do |k, v|
226
+ res[k] ||= sum_matched(report, /^#{k}[0-9A-Z]{1,}/) if k.length == 2
232
227
  res[k] = v if k.length == 3
233
228
  end
234
229
 
@@ -253,20 +248,23 @@ module LucaBook
253
248
 
254
249
  def set_balance
255
250
  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
251
+ start_year = if @start_date.month > CONFIG['fy_start'].to_i
252
+ pre_last.year
253
+ else
254
+ pre_last.year - 1
255
+ end
256
+ pre = self.class.accumulate_term(start_year, CONFIG['fy_start'], pre_last.year, pre_last.month)
261
257
 
262
258
  base = Dict.latest_balance.each_with_object({}) do |(k, v), h|
263
- h[k] = v[:balance].to_i if v[:balance]
259
+ h[k] = BigDecimal(v[:balance].to_s) if v[:balance]
260
+ h[k] ||= BigDecimal('0') if k.length == 2
264
261
  end
265
262
  if pre
266
263
  idx = (pre.keys + base.keys).uniq
267
- base = {}.tap { |h| idx.each { |k| h[k] = (base[k] || 0) + (pre[k] || 0) } }
264
+ base = {}.tap do |h|
265
+ idx.each { |k| h[k] = (base[k] || BigDecimal('0')) + (pre[k] || BigDecimal('0')) }
266
+ end
268
267
  end
269
- #code_sum(base).merge(self.class.total_subaccount(base))
270
268
  self.class.total_subaccount(base)
271
269
  end
272
270
 
@@ -275,39 +273,49 @@ module LucaBook
275
273
  end
276
274
 
277
275
  # for assert purpose
278
- def self.gross(year, month = nil, code = nil, date_range = nil, rows = 4)
276
+ #
277
+ def self.gross(start_year, start_month, end_year = nil, end_month = nil, code: nil, date_range: nil, rows: 4)
279
278
  if ! date_range.nil?
280
279
  raise if date_range.class != Range
281
280
  # TODO: date based range search
282
281
  end
283
282
 
283
+ end_year ||= start_year
284
+ end_month ||= start_month
284
285
  sum = { debit: {}, credit: {}, debit_count: {}, credit_count: {} }
285
286
  idx_memo = []
286
- asof(year, month) do |f, _path|
287
+ term(start_year, start_month, end_year, end_month, code) do |f, _path|
287
288
  CSV.new(f, headers: false, col_sep: "\t", encoding: 'UTF-8')
288
289
  .each_with_index do |row, i|
289
290
  break if i >= rows
291
+
290
292
  case i
291
293
  when 0
292
294
  idx_memo = row.map(&:to_s)
295
+ next if code && !idx_memo.include?(code)
296
+
293
297
  idx_memo.each do |r|
294
- sum[:debit][r] ||= 0
298
+ sum[:debit][r] ||= BigDecimal('0')
295
299
  sum[:debit_count][r] ||= 0
296
300
  end
297
301
  when 1
302
+ next if code && !idx_memo.include?(code)
303
+
298
304
  row.each_with_index do |r, j|
299
- sum[:debit][idx_memo[j]] += r.to_i # TODO: bigdecimal support
305
+ sum[:debit][idx_memo[j]] += BigDecimal(r.to_s)
300
306
  sum[:debit_count][idx_memo[j]] += 1
301
307
  end
302
308
  when 2
303
309
  idx_memo = row.map(&:to_s)
310
+ break if code && !idx_memo.include?(code)
311
+
304
312
  idx_memo.each do |r|
305
- sum[:credit][r] ||= 0
313
+ sum[:credit][r] ||= BigDecimal('0')
306
314
  sum[:credit_count][r] ||= 0
307
315
  end
308
316
  when 3
309
317
  row.each_with_index do |r, j|
310
- sum[:credit][idx_memo[j]] += r.to_i # TODO: bigdecimal support
318
+ sum[:credit][idx_memo[j]] += BigDecimal(r.to_s)
311
319
  sum[:credit_count][idx_memo[j]] += 1
312
320
  end
313
321
  else
@@ -315,49 +323,39 @@ module LucaBook
315
323
  end
316
324
  end
317
325
  end
326
+ if code
327
+ sum[:debit] = sum[:debit][code] || BigDecimal('0')
328
+ sum[:credit] = sum[:credit][code] || BigDecimal('0')
329
+ sum[:debit_count] = sum[:debit_count][code] || 0
330
+ sum[:credit_count] = sum[:credit_count][code] || 0
331
+ end
318
332
  sum
319
333
  end
320
334
 
321
335
  # netting vouchers in specified term
322
- def self.net(year, month = nil, code = nil, date_range = nil)
323
- g = gross(year, month, code, date_range)
336
+ #
337
+ def self.net(start_year, start_month, end_year = nil, end_month = nil, code: nil, date_range: nil)
338
+ g = gross(start_year, start_month, end_year, end_month, code: code, date_range: date_range)
324
339
  idx = (g[:debit].keys + g[:credit].keys).uniq.sort
325
340
  count = {}
326
341
  diff = {}.tap do |sum|
327
342
  idx.each do |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)
343
+ sum[code] = g.dig(:debit, code).nil? ? BigDecimal('0') : Util.calc_diff(g[:debit][code], code)
344
+ sum[code] -= g.dig(:credit, code).nil? ? BigDecimal('0') : Util.calc_diff(g[:credit][code], code)
330
345
  count[code] = (g.dig(:debit_count, code) || 0) + (g.dig(:credit_count, code) || 0)
331
346
  end
332
347
  end
333
348
  [diff, count]
334
349
  end
335
350
 
336
- # TODO: replace load_tsv -> generic load_tsv_dict
337
- def load_start
338
- file = Pathname(LucaSupport::Config::Pjdir) / 'data' / 'balance' / 'start.tsv'
339
- {}.tap do |dict|
340
- load_tsv(file) do |row|
341
- dict[row[0]] = row[2].to_i if ! row[2].nil?
342
- end
343
- end
344
- end
345
-
346
- def load_tsv(path)
347
- return enum_for(:load_tsv, path) unless block_given?
348
-
349
- data = CSV.read(path, headers: true, col_sep: "\t", encoding: 'UTF-8')
350
- data.each { |row| yield row }
351
- end
352
-
353
351
  private
354
352
 
355
353
  def legal_items
356
- return [] unless LucaSupport::Config::COUNTRY
354
+ return [] unless CONFIG['country']
357
355
 
358
- case LucaSupport::Config::COUNTRY
356
+ case CONFIG['country']
359
357
  when 'jp'
360
- ['91', '911', '912', '913', '9131', '9132', '914', '9141', '9142', '915', '916', '92', '93']
358
+ ['31', '32', '33', '91', '911', '912', '913', '9131', '9132', '914', '9141', '9142', '915', '916', '92', '93']
361
359
  end
362
360
  end
363
361
  end