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.
- checksums.yaml +4 -4
- data/exe/luca-book +66 -16
- data/lib/luca_book.rb +1 -0
- data/lib/luca_book/console.rb +4 -4
- data/lib/luca_book/dict.rb +78 -1
- data/lib/luca_book/import.rb +36 -37
- data/lib/luca_book/import_jp.rb +83 -0
- data/lib/luca_book/journal.rb +98 -23
- data/lib/luca_book/list.rb +17 -10
- data/lib/luca_book/list_by_header.rb +120 -0
- data/lib/luca_book/setup.rb +2 -1
- data/lib/luca_book/state.rb +95 -97
- data/lib/luca_book/templates/config.yml +4 -0
- data/lib/luca_book/templates/dict-en.tsv +45 -50
- data/lib/luca_book/templates/dict-jp.tsv +34 -30
- data/lib/luca_book/version.rb +1 -1
- metadata +6 -3
data/lib/luca_book/journal.rb
CHANGED
@@ -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(
|
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
|
-
|
19
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
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
|
data/lib/luca_book/list.rb
CHANGED
@@ -7,11 +7,12 @@ require 'luca_record'
|
|
7
7
|
require 'luca_record/dict'
|
8
8
|
require 'luca_book'
|
9
9
|
|
10
|
-
|
11
|
-
#
|
12
|
-
|
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
|
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
|
-
|
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
|
-
|
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
|
data/lib/luca_book/setup.rb
CHANGED
@@ -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::
|
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
|
data/lib/luca_book/state.rb
CHANGED
@@ -25,21 +25,7 @@ module LucaBook
|
|
25
25
|
@start_balance = set_balance
|
26
26
|
end
|
27
27
|
|
28
|
-
|
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,
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
-
|
77
|
-
|
78
|
-
|
79
|
-
|
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
|
-
|
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
|
-
|
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
|
-
|
164
|
-
|
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
|
-
|
168
|
-
h[k] = (fy[k] || 0)
|
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
|
-
|
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
|
-
|
183
|
-
|
184
|
-
|
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
|
-
|
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, /^[
|
220
|
-
|
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['
|
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
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
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].
|
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
|
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
|
-
|
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
|
-
|
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.
|
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.
|
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
|
-
|
323
|
-
|
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
|
354
|
+
return [] unless CONFIG['country']
|
357
355
|
|
358
|
-
case
|
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
|