lucabook 0.2.15 → 0.2.17

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: db7564331de8046e8a6507c2ccf7dc099382ff3fc1c6a603f9ecc290755398ba
4
- data.tar.gz: 5fb64e6fbccc5a5e693211ae89d8bad6560593add1f26261879b2ae4bc67fa00
3
+ metadata.gz: 5b204a2ed70889d0750a6883de424ceb3b7e335cce99262eb98e288db6962cbd
4
+ data.tar.gz: e19db20331a4edce1ea02ffb47495c3f849f74ba0bf78b13c7cf0562207fc0d2
5
5
  SHA512:
6
- metadata.gz: 4cee8065abe4d479dc909e2e9f12f0fdb942e7b3c51b2f5f5e993598080db28411625d990c7f7a7ec85ebc96686a4dcf6d0173e8fed58fd3a3261731ea3da831
7
- data.tar.gz: 1cd81e73a4dfcf3eef52a8105b7e1dc921f9d79d8b9945bf2b9e74d80134d2ca157346d69d98a26037d0f087fa4475e05938ae0044a7b1fcb3120b744f3a9ba8
6
+ metadata.gz: 16800a60362abcc56adb26ed60611a98a5ec5c3545143b4a0c4a379aac3988952e733dac19bf5c2188ce9e7c244dff1b4d6269635a226eeda51ec85c4ab2178f
7
+ data.tar.gz: f60f61de1a1c18df3db8c1bc5278ae084089137b15ee88f198ff17c6418917c6adebb634820dcd9979b42a9ecf7e902329c7bb14fb950f5270154492a2b0d8b2
@@ -17,18 +17,24 @@ module LucaCmd
17
17
 
18
18
  def self.list(args, params)
19
19
  if params['code']
20
- LucaBook::List.term(*args, code: params['code']).flat_list.to_yaml
20
+ LucaBook::List.term(*args, code: params['code']).list_on_code.to_yaml
21
21
  elsif args.length > 0
22
- LucaBook::List.term(*args).flat_list.to_yaml
22
+ LucaBook::List.term(*args).list_journals.to_yaml
23
23
  else
24
24
  # TODO: define default function
25
25
  end
26
26
  end
27
+
28
+ def self.stats(args, params)
29
+ LucaBook::State.term(*args).stats(params[:level])
30
+ end
27
31
  end
28
32
 
29
33
  class Report
30
34
  def self.balancesheet(args, params)
31
- LucaBook::State.term(*args).bs.to_yaml
35
+ level = params[:level] || 2
36
+ legal = params[:legal] || false
37
+ LucaBook::State.term(*args).bs(level, legal: legal)
32
38
  end
33
39
 
34
40
  def self.profitloss(args, params)
@@ -37,6 +43,9 @@ module LucaCmd
37
43
  end
38
44
  end
39
45
 
46
+ def new_pj(args = nil, params = {})
47
+ LucaBook::Setup.create_project params['country'], args[0]
48
+ end
40
49
 
41
50
  LucaRecord::Base.valid_project?
42
51
  cmd = ARGV.shift
@@ -48,20 +57,34 @@ when /journals?/, 'j'
48
57
  case subcmd
49
58
  when 'import'
50
59
  OptionParser.new do |opt|
51
- opt.banner = 'Usage: luca import filepath'
52
- opt.on('-c', '--config VAL', 'import definition'){|v| params['config'] = v }
53
- opt.on('-j', '--json', 'import via json format'){|_v| params['json'] = true }
60
+ opt.banner = 'Usage: luca-book journals import filepath'
61
+ opt.on('-c', '--config VAL', 'import definition'){ |v| params['config'] = v }
62
+ opt.on('-j', '--json', 'import via json format'){ |_v| params['json'] = true }
54
63
  args = opt.parse!(ARGV)
55
64
  LucaCmd::Journal.import(args, params)
56
65
  end
57
66
  when 'list'
58
67
  OptionParser.new do |opt|
59
- opt.banner = 'Usage: luca list [year month]'
68
+ opt.banner = 'Usage: luca-book journals list [year month]'
60
69
  opt.on('-c', '--code VAL', 'search with code') { |v| params['code'] = v }
61
70
  opt.on_tail('List records. If you specify code and/or month, search on each criteria.')
62
71
  args = opt.parse!(ARGV)
63
72
  LucaCmd::Journal.list(args, params)
64
73
  end
74
+ when 'stats'
75
+ OptionParser.new do |opt|
76
+ opt.banner = 'Usage: luca-book reports bs'
77
+ opt.on('-l', '--level VAL', 'account level') { |v| params[:level] = v.to_i }
78
+ args = opt.parse!(ARGV)
79
+ LucaCmd::Journal.stats(args, params)
80
+ end
81
+ end
82
+ when 'new'
83
+ OptionParser.new do |opt|
84
+ opt.banner = 'Usage: luca-book new Dir'
85
+ opt.on('-c', '--country VAL', 'specify country code') { |v| params['coountry'] = v }
86
+ args = opt.parse(ARGV)
87
+ new_pj(args, params)
65
88
  end
66
89
  when /reports?/, 'r'
67
90
  subcmd = ARGV.shift
@@ -69,6 +92,8 @@ when /reports?/, 'r'
69
92
  when 'bs'
70
93
  OptionParser.new do |opt|
71
94
  opt.banner = 'Usage: luca-book reports bs'
95
+ opt.on('-l', '--level VAL', 'account level') { |v| params[:level] = v.to_i }
96
+ opt.on('--legal', 'show legal mandatory account') { |_v| params[:legal] = true }
72
97
  args = opt.parse!(ARGV)
73
98
  LucaCmd::Report.balancesheet(args, params)
74
99
  end
@@ -5,9 +5,11 @@ require 'luca_record'
5
5
  require 'luca_book/version'
6
6
 
7
7
  module LucaBook
8
+ autoload :Dict, 'luca_book/dict'
8
9
  autoload :Import, 'luca_book/import'
9
10
  autoload :Journal, 'luca_book/journal'
10
11
  autoload :List, 'luca_book/list'
11
12
  autoload :Setup, 'luca_book/setup'
12
13
  autoload :State, 'luca_book/state'
14
+ autoload :Util, 'luca_book/util'
13
15
  end
@@ -29,6 +29,7 @@ class LucaBookConsole
29
29
  end
30
30
  end
31
31
 
32
+ # TODO: deprecated. accumulate_all() already removed.
32
33
  def bs
33
34
  target = []
34
35
  report = []
@@ -56,6 +57,7 @@ class LucaBookConsole
56
57
  puts "---- ----"
57
58
  end
58
59
 
60
+ # TODO: deprecated. accumulate_all() already removed.
59
61
  def pl
60
62
  target = []
61
63
  report = []
@@ -2,12 +2,19 @@
2
2
 
3
3
  require 'luca_support/config'
4
4
  require 'luca_record/dict'
5
+ require 'date'
6
+ require 'pathname'
5
7
 
6
8
  module LucaBook
7
9
  class Dict < LucaRecord::Dict
10
+ def self.latest_balance
11
+ dict_dir = Pathname(LucaSupport::Config::Pjdir) / 'data' / 'balance'
12
+ # TODO: search latest balance dictionary
13
+ load_tsv_dict(dict_dir / 'start.tsv')
14
+ end
8
15
 
9
- @filename = 'dict.tsv'
10
-
11
- Data = load
16
+ def self.issue_date(obj)
17
+ Date.parse(obj.dig('_date', :label))
18
+ end
12
19
  end
13
20
  end
@@ -9,14 +9,15 @@ require 'luca_record'
9
9
 
10
10
  module LucaBook
11
11
  class Import
12
- DEBIT_DEFAULT = '仮払金'
13
- CREDIT_DEFAULT = '仮受金'
12
+ DEBIT_DEFAULT = '10XX'
13
+ CREDIT_DEFAULT = '50XX'
14
14
 
15
15
  def initialize(path, dict)
16
16
  raise 'no such file' unless FileTest.file?(path)
17
17
 
18
18
  @target_file = path
19
19
  # TODO: yaml need to be configurable
20
+ @dict_name = dict
20
21
  @dict = LucaRecord::Dict.new("import-#{dict}.yaml")
21
22
  @code_map = LucaRecord::Dict.reverse(LucaRecord::Dict.load('base.tsv'))
22
23
  @config = @dict.csv_config if dict
@@ -59,7 +60,7 @@ module LucaBook
59
60
  if @config[:type] == 'single'
60
61
  LucaBook::Journal.create(parse_single(row))
61
62
  elsif @config[:type] == 'double'
62
- p parse_double(row)
63
+ p parse_double(row) # TODO: Not implemented yet
63
64
  else
64
65
  p row
65
66
  end
@@ -85,7 +86,7 @@ module LucaBook
85
86
  d['date'] = parse_date(row)
86
87
  if row.dig(@config[:credit_value])&.empty?
87
88
  d['debit'] = [
88
- { 'code' => search_code(row[@config[:label]], DEBIT_DEFAULT) }
89
+ { 'code' => search_code(row[@config[:label]], @config.dig(:default_debit)) || DEBIT_DEFAULT }
89
90
  ]
90
91
  d['credit'] = [
91
92
  { 'code' => @code_map.dig(@config[:counter_label]) }
@@ -95,12 +96,13 @@ module LucaBook
95
96
  { 'code' => @code_map.dig(@config[:counter_label]) }
96
97
  ]
97
98
  d['credit'] = [
98
- { 'code' => search_code(row[@config[:label]], CREDIT_DEFAULT) }
99
+ { 'code' => search_code(row[@config[:label]], @config.dig(:default_credit)) || CREDIT_DEFAULT }
99
100
  ]
100
101
  end
101
102
  d['debit'][0]['value'] = value
102
103
  d['credit'][0]['value'] = value
103
104
  d['note'] = Array(@config[:note]).map{ |col| row[col] }.join(' ')
105
+ d['x-editor'] = "LucaBook::Import/#{@dict_name}"
104
106
  end
105
107
  end
106
108
 
@@ -111,14 +113,15 @@ module LucaBook
111
113
  {}.tap do |d|
112
114
  d['date'] = parse_date(row)
113
115
  d['debit'] = {
114
- 'code' => search_code(row[@config[:debit_label]], DEBIT_DEFAULT),
116
+ 'code' => search_code(row[@config[:label]], @config.dig(:default_debit)) || DEBIT_DEFAULT,
115
117
  'value' => row.dig(@config[:debit_value])
116
118
  }
117
119
  d['credit'] = {
118
- 'code' => search_code(row[@config[:credit_label]], CREDIT_DEFAULT),
120
+ 'code' => search_code(row[@config[:label]], @config.dig(:default_credit)) || CREDIT_DEFAULT,
119
121
  'value' => row.dig(@config[:credit_value])
120
122
  }
121
123
  d['note'] = Array(@config[:note]).map{ |col| row[col] }.join(' ')
124
+ d['x-editor'] = "LucaBook::Import/#{@dict_name}"
122
125
  end
123
126
  end
124
127
 
@@ -13,8 +13,10 @@ module LucaBook
13
13
  class List < LucaBook::Journal
14
14
  @dirname = 'journals'
15
15
 
16
- def initialize(data)
16
+ def initialize(data, start_date, code = nil)
17
17
  @data = data
18
+ @code = code
19
+ @start = start_date
18
20
  @dict = LucaRecord::Dict.load('base.tsv')
19
21
  end
20
22
 
@@ -26,44 +28,111 @@ module LucaBook
26
28
  [:debit, :credit].map { |key| serialize_on_key(dat[key], :code) }.flatten.include?(code)
27
29
  end
28
30
  end
29
- new data
31
+ new data, Date.new(from_year.to_i, from_month.to_i, 1), code
30
32
  end
31
33
 
32
- def convert_label
33
- @data.each do |dat|
34
- dat[:debit].each { |debit| debit[:code] = "#{debit[:code]} #{@dict.dig(debit[:code], :label)}" }
35
- dat[:credit].each { |credit| credit[:code] = "#{credit[:code]} #{@dict.dig(credit[:code], :label)}" }
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
36
49
  end
37
50
  self
38
51
  end
39
52
 
40
- def flat_list
53
+ def list_journals
41
54
  convert_label
42
55
  @data = @data.map do |dat|
43
- idx = dat[:debit].length >= dat[:credit].length ? :debit : :credit
44
- dat[idx].map.with_index do |_k, i|
45
- date, txid = LucaSupport::Code.decode_id(dat[:id])
46
- {}.tap do |res|
47
- res['date'] = date
48
- res['no'] = txid
49
- res['id'] = dat[:id]
50
- res['debit_code'] = dat[:debit][i][:code] if dat[:debit][i]
51
- res['debit_amount'] = dat[:debit][i][:amount] if dat[:debit][i]
52
- res['credit_code'] = dat[:credit][i][:code] if dat[:credit][i]
53
- res['credit_amount'] = dat[:credit][i][:amount] if dat[:credit][i]
54
- res['note'] = dat[:note]
55
- end
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]
56
66
  end
57
- end.flatten
67
+ end
58
68
  self
59
69
  end
60
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
+
61
77
  def to_yaml
62
78
  YAML.dump(LucaSupport::Code.readable(@data)).tap { |data| puts data }
63
79
  end
64
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
+
65
126
  def dict
66
127
  LucaBook::Dict::Data
67
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
68
137
  end
69
138
  end
@@ -9,7 +9,7 @@ module LucaBook
9
9
  def self.create_project(country = nil, dir = LucaSupport::Config::Pjdir)
10
10
  FileUtils.mkdir_p(dir) unless Dir.exist?(dir)
11
11
  Dir.chdir(dir) do
12
- %w[data/journals dict].each do |subdir|
12
+ %w[data/journals data/balance dict].each do |subdir|
13
13
  FileUtils.mkdir_p(subdir) unless Dir.exist?(subdir)
14
14
  end
15
15
  dict = if File.exist?("#{__dir__}/templates/dict-#{country}.tsv")
@@ -18,6 +18,21 @@ module LucaBook
18
18
  'dict-en.tsv'
19
19
  end
20
20
  FileUtils.cp("#{__dir__}/templates/#{dict}", 'dict/base.tsv') unless File.exist?('dict/base.tsv')
21
+ prepare_starttsv(dict) unless File.exist? 'data/balance/start.tsv'
22
+ end
23
+ end
24
+
25
+ # Generate initial balance template.
26
+ # Codes are same as base dictionary.
27
+ # The previous month of start date is better for _date.
28
+ #
29
+ def self.prepare_starttsv(dict)
30
+ CSV.open('data/balance/start.tsv', 'w', col_sep: "\t", encoding: 'UTF-8') do |csv|
31
+ csv << ['code', 'label', 'balance']
32
+ csv << ['_date', '2020-1-1']
33
+ CSV.open("#{__dir__}/templates/#{dict}", 'r', col_sep: "\t", encoding: 'UTF-8').each do |row|
34
+ csv << row if /^[1-9]/.match(row[0])
35
+ end
21
36
  end
22
37
  end
23
38
  end
@@ -1,3 +1,4 @@
1
+ # frozen_string_literal: true
1
2
 
2
3
  require 'csv'
3
4
  require 'pathname'
@@ -7,7 +8,6 @@ require 'luca_record'
7
8
  require 'luca_record/dict'
8
9
  require 'luca_book'
9
10
 
10
- #
11
11
  # Statement on specified term
12
12
  #
13
13
  module LucaBook
@@ -17,9 +17,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,13 +43,16 @@ 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)
49
+ diff, count = accumulate_month(date.year, date.month)
50
+ r << diff
51
+ counts << count
47
52
  date = date.next_month
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)
@@ -58,9 +63,9 @@ module LucaBook
58
63
  if ! month.nil?
59
64
  pre_term = full_term.select { |y, m| y <= year.to_i && m < month.to_i }
60
65
  balance += pre_term.map { |y, m| self.class.net(y, m)}.inject(0){|sum, h| sum + h[code] }
61
- [{ code: code, balance: balance, note: "#{code} #{dict.dig(code, :label)}" }] + records_with_balance(year, month, code, balance)
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: balance, note: "#{code} #{dict.dig(code, :label)}" }
68
+ start = { code: code, balance: balance, note: "#{code} #{@dict.dig(code, :label)}" }
64
69
  full_term.map { |y, m| y }.uniq.map { |y|
65
70
  records_with_balance(y, nil, code, balance)
66
71
  }.flatten.prepend(start)
@@ -69,38 +74,11 @@ module LucaBook
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 = self.class.scan_terms.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
@@ -114,33 +92,105 @@ module LucaBook
114
92
  end
115
93
  end
116
94
 
117
- def bs
118
- @statement = @data.map do |data|
119
- data.select { |k, v| /^[0-9].+/.match(k) }
95
+ def stats(level = nil)
96
+ keys = @count.map(&:keys).flatten.uniq.sort
97
+ @count.map! do |data|
98
+ keys.each do |k|
99
+ data[k] ||= 0
100
+ next if level.nil? || k.length <= level
101
+
102
+ if data[k[0, level]]
103
+ data[k[0, level]] += data[k]
104
+ else
105
+ data[k[0, level]] = data[k]
106
+ end
107
+ end
108
+ data.select! { |k, _v| k.length <= level } if level
109
+ data.sort.to_h
120
110
  end
121
- self
111
+ keys.map! { |k| k[0, level] }.uniq.select! { |k| k.length <= level } if level
112
+ @count.prepend({}.tap { |header| keys.each { |k| header[k] = @dict.dig(k, :label) }})
113
+ puts YAML.dump(@count)
114
+ @count
122
115
  end
123
116
 
124
- def pl
125
- @statement = @data.map do |data|
126
- data.select { |k, v| /^[A-F].+/.match(k) }
117
+ def bs(level = 3, legal: false)
118
+ @data.map! { |data| data.select { |k, _v| k.length <= level } }
119
+ @data.map! { |data| code_sum(data).merge(data) } if legal
120
+ base = accumulate_balance(@data)
121
+ length = [base[:debit].length, base[:credit].length].max
122
+ @statement = [].tap do |a|
123
+ length.times do |i|
124
+ {}.tap do |res|
125
+ res['debit_label'] = base[:debit][i] ? @dict.dig(base[:debit][i].keys[0], :label) : ''
126
+ res['debit_balance'] = base[:debit][i] ? @start_balance.dig(base[:debit][i].keys[0]) + base[:debit][i].values[0] : ''
127
+ res['debit_diff'] = base[:debit][i] ? base[:debit][i].values[0] : ''
128
+ res['credit_label'] = base[:credit][i] ? @dict.dig(base[:credit][i].keys[0], :label) : ''
129
+ res['credit_balance'] = base[:credit][i] ? @start_balance.dig(base[:credit][i].keys[0]) + base[:credit][i].values[0] : ''
130
+ res['credit_diff'] = base[:credit][i] ? base[:credit][i].values[0] : ''
131
+ a << res
132
+ end
133
+ end
127
134
  end
135
+ puts YAML.dump(@statement)
128
136
  self
129
137
  end
130
138
 
131
- def self.accumulate_month(year, month)
132
- monthly_record = net(year, month)
133
- total_subaccount(monthly_record)
139
+ def accumulate_balance(monthly_diffs)
140
+ data = monthly_diffs.each_with_object({}) do |month, h|
141
+ month.each do |k, v|
142
+ h[k] = h[k].nil? ? v : h[k] + v
143
+ end
144
+ end.sort.to_h
145
+ { debit: [], credit: [] }.tap do |res|
146
+ data.each do |k, v|
147
+ case k
148
+ when /^[0-4].*/
149
+ res[:debit] << { k => v }
150
+ when /^[5-9H].*/
151
+ res[:credit] << { k => v }
152
+ end
153
+ end
154
+ end
134
155
  end
135
156
 
136
- def amount_by_code(items, code)
137
- items
138
- .select{|item| item.dig(:code) == code }
139
- .inject(0){|sum, item| sum + item[:amount] }
157
+ def pl
158
+ @statement = @data.map { |data| data.select { |k, _v| /^[A-H].+/.match(k) } }
159
+ @statement << @statement.each_with_object({}) { |item, h| item.each { |k, v| h[k].nil? ? h[k] = v : h[k] += v } }
160
+ self
140
161
  end
141
162
 
163
+ def self.accumulate_month(year, month)
164
+ monthly_record, count = net(year, month)
165
+ [total_subaccount(monthly_record), count]
166
+ end
167
+
168
+ # Accumulate Level 2, 3 account.
169
+ #
142
170
  def self.total_subaccount(report)
143
- report.dup.tap do |res|
171
+ {}.tap do |res|
172
+ res['10'] = sum_matched(report, /^[123][0-9A-Z]{2,}/)
173
+ res['40'] = sum_matched(report, /^[4][0-9A-Z]{2,}/)
174
+ res['50'] = sum_matched(report, /^[56][0-9A-Z]{2,}/)
175
+ res['70'] = sum_matched(report, /^[78][0-9A-Z]{2,}/)
176
+ res['8ZZ'] = res['50'] + res['70']
177
+ res['9ZZ'] = sum_matched(report, /^[9][0-9A-Z]{2,}/)
178
+ res['A0'] = sum_matched(report, /^[A][0-9A-Z]{2,}/)
179
+ res['B0'] = sum_matched(report, /^[B][0-9A-Z]{2,}/)
180
+ res['BA'] = res['A0'] - res['B0']
181
+ res['C0'] = sum_matched(report, /^[C][0-9A-Z]{2,}/)
182
+ res['CA'] = res['BA'] - res['C0']
183
+ res['D0'] = sum_matched(report, /^[D][0-9A-Z]{2,}/)
184
+ res['E0'] = sum_matched(report, /^[E][0-9A-Z]{2,}/)
185
+ res['EA'] = res['CA'] + res['D0'] - res['E0']
186
+ res['F0'] = sum_matched(report, /^[F][0-9A-Z]{2,}/)
187
+ res['G0'] = sum_matched(report, /^[G][0-9A-Z]{2,}/)
188
+ res['GA'] = res['EA'] + res['F0'] - res['G0']
189
+ res['HA'] = res['GA'] - sum_matched(report, /^[H][0-9A-Z]{2,}/)
190
+
191
+ res['1'] = res['10'] + res['40']
192
+ res['5'] = res['8ZZ'] + res['9ZZ'] + res['HA']
193
+
144
194
  report.each do |k, v|
145
195
  if k.length >= 4
146
196
  if res[k[0, 3]]
@@ -150,26 +200,23 @@ module LucaBook
150
200
  end
151
201
  end
152
202
  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]/)
203
+ res.sort.to_h
170
204
  end
171
205
  end
172
206
 
207
+ def code_sum(report)
208
+ legal_items.each.with_object({}) do |k, h|
209
+ h[k] = self.class.sum_matched(report, /^#{k}.*/)
210
+ end
211
+ end
212
+
213
+ def set_balance
214
+ base = Dict.latest_balance.each_with_object({}) do |(k, v), h|
215
+ h[k] = v[:balance].to_i if v[:balance]
216
+ end
217
+ code_sum(base).merge(self.class.total_subaccount(base))
218
+ end
219
+
173
220
  def self.sum_matched(report, reg)
174
221
  report.select { |k, v| reg.match(k)}.values.sum
175
222
  end
@@ -181,7 +228,7 @@ module LucaBook
181
228
  # TODO: date based range search
182
229
  end
183
230
 
184
- sum = { debit: {}, credit: {} }
231
+ sum = { debit: {}, credit: {}, debit_count: {}, credit_count: {} }
185
232
  idx_memo = []
186
233
  asof(year, month) do |f, _path|
187
234
  CSV.new(f, headers: false, col_sep: "\t", encoding: 'UTF-8')
@@ -190,14 +237,26 @@ module LucaBook
190
237
  case i
191
238
  when 0
192
239
  idx_memo = row.map(&:to_s)
193
- idx_memo.each { |r| sum[:debit][r] ||= 0 }
240
+ idx_memo.each do |r|
241
+ sum[:debit][r] ||= 0
242
+ sum[:debit_count][r] ||= 0
243
+ end
194
244
  when 1
195
- row.each_with_index { |r, i| sum[:debit][idx_memo[i]] += r.to_i } # TODO: bigdecimal support
245
+ row.each_with_index do |r, j|
246
+ sum[:debit][idx_memo[j]] += r.to_i # TODO: bigdecimal support
247
+ sum[:debit_count][idx_memo[j]] += 1
248
+ end
196
249
  when 2
197
250
  idx_memo = row.map(&:to_s)
198
- idx_memo.each { |r| sum[:credit][r] ||= 0 }
251
+ idx_memo.each do |r|
252
+ sum[:credit][r] ||= 0
253
+ sum[:credit_count][r] ||= 0
254
+ end
199
255
  when 3
200
- row.each_with_index { |r, i| sum[:credit][idx_memo[i]] += r.to_i } # TODO: bigdecimal support
256
+ row.each_with_index do |r, j|
257
+ sum[:credit][idx_memo[j]] += r.to_i # TODO: bigdecimal support
258
+ sum[:credit_count][idx_memo[j]] += 1
259
+ end
201
260
  else
202
261
  puts row # for debug
203
262
  end
@@ -210,17 +269,20 @@ module LucaBook
210
269
  def self.net(year, month = nil, code = nil, date_range = nil)
211
270
  g = gross(year, month, code, date_range)
212
271
  idx = (g[:debit].keys + g[:credit].keys).uniq.sort
213
- {}.tap do |sum|
272
+ count = {}
273
+ diff = {}.tap do |sum|
214
274
  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)
275
+ sum[code] = g.dig(:debit, code).nil? ? 0 : Util.calc_diff(g[:debit][code], code)
276
+ sum[code] -= g.dig(:credit, code).nil? ? 0 : Util.calc_diff(g[:credit][code], code)
277
+ count[code] = (g.dig(:debit_count, code) || 0) + (g.dig(:credit_count, code) || 0)
217
278
  end
218
279
  end
280
+ [diff, count]
219
281
  end
220
282
 
221
283
  # TODO: replace load_tsv -> generic load_tsv_dict
222
284
  def load_start
223
- file = LucaSupport::Config::Pjdir + 'start.tsv'
285
+ file = Pathname(LucaSupport::Config::Pjdir) / 'data' / 'balance' / 'start.tsv'
224
286
  {}.tap do |dic|
225
287
  load_tsv(file) do |row|
226
288
  dic[row[0]] = row[2].to_i if ! row[2].nil?
@@ -235,24 +297,15 @@ module LucaBook
235
297
  data.each { |row| yield row }
236
298
  end
237
299
 
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
300
+ private
242
301
 
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
302
+ def legal_items
303
+ return [] unless LucaSupport::Config::COUNTRY
253
304
 
254
- def dict
255
- LucaBook::Dict::Data
305
+ case LucaSupport::Config::COUNTRY
306
+ when 'jp'
307
+ ['91', '911', '912', '913', '9131', '9132', '914', '9141', '9142', '915', '916', '92', '93']
308
+ end
256
309
  end
257
310
  end
258
311
  end