lucabook 0.2.19 → 0.2.24

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.
@@ -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