lucabook 0.2.20 → 0.2.25
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 +92 -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 +18 -11
- data/lib/luca_book/list_by_header.rb +120 -0
- data/lib/luca_book/setup.rb +2 -1
- data/lib/luca_book/state.rb +175 -110
- data/lib/luca_book/templates/base-jp.xbrl.erb +23 -48
- data/lib/luca_book/templates/base-jp.xsd.erb +17 -0
- 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-edinet.tsv +167 -0
- data/lib/luca_book/templates/dict-jp.tsv +192 -162
- data/lib/luca_book/templates/monthly-report.html.erb +67 -0
- data/lib/luca_book/version.rb +1 -1
- metadata +9 -3
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'date'
|
4
|
+
require 'luca_book'
|
5
|
+
require 'luca_support'
|
6
|
+
|
7
|
+
module LucaBook
|
8
|
+
class Import
|
9
|
+
# TODO: need to be separated into pluggable l10n module.
|
10
|
+
# TODO: gensen rate >1m yen.
|
11
|
+
# TODO: gensen & consumption `round()` rules need to be confirmed.
|
12
|
+
# Profit or Loss account should be specified as code1.
|
13
|
+
#
|
14
|
+
def tax_extension(code1, code2, amount, options)
|
15
|
+
return nil if options.nil? || options[:tax_options].nil?
|
16
|
+
return nil if !options[:tax_options].include?('jp-gensen') && !options[:tax_options].include?('jp-consumption')
|
17
|
+
|
18
|
+
gensen_rate = BigDecimal('0.1021')
|
19
|
+
consumption_rate = BigDecimal('0.1')
|
20
|
+
gensen_code = @code_map.dig(options[:gensen_label]) || @code_map.dig('預り金')
|
21
|
+
gensen_idx = /^[5-8B-G]/.match(code1) ? 1 : 0
|
22
|
+
consumption_idx = /^[A-G]/.match(code1) ? 0 : 1
|
23
|
+
consumption_code = @code_map.dig(options[:consumption_label])
|
24
|
+
consumption_code ||= /^[A]/.match(code1) ? @code_map.dig('仮受消費税等') : @code_map.dig('仮払消費税等')
|
25
|
+
if options[:tax_options].include?('jp-gensen') && options[:tax_options].include?('jp-consumption')
|
26
|
+
paid_rate = BigDecimal('1') + consumption_rate - gensen_rate
|
27
|
+
gensen_amount = (amount / paid_rate * gensen_rate).round
|
28
|
+
consumption_amount = (amount / paid_rate * consumption_rate).round
|
29
|
+
[].tap do |res|
|
30
|
+
res << [].tap do |res1|
|
31
|
+
amount1 = amount
|
32
|
+
amount1 -= consumption_amount if consumption_idx == 0
|
33
|
+
amount1 += gensen_amount if gensen_idx == 1
|
34
|
+
res1 << { 'code' => code1, 'amount' => amount1 }
|
35
|
+
res1 << { 'code' => consumption_code, 'amount' => consumption_amount } if consumption_idx == 0
|
36
|
+
res1 << { 'code' => gensen_code, 'amount' => gensen_amount } if gensen_idx == 0
|
37
|
+
end
|
38
|
+
res << [].tap do |res2|
|
39
|
+
amount2 = amount
|
40
|
+
amount2 -= consumption_amount if consumption_idx == 1
|
41
|
+
amount2 += gensen_amount if gensen_idx == 0
|
42
|
+
res2 << { 'code' => code2, 'amount' => amount2 }
|
43
|
+
res2 << { 'code' => consumption_code, 'amount' => consumption_amount } if consumption_idx == 1
|
44
|
+
res2 << { 'code' => gensen_code, 'amount' => gensen_amount } if gensen_idx == 1
|
45
|
+
end
|
46
|
+
end
|
47
|
+
elsif options[:tax_options].include?('jp-gensen')
|
48
|
+
paid_rate = BigDecimal('1') - gensen_rate
|
49
|
+
gensen_amount = (amount / paid_rate * gensen_rate).round
|
50
|
+
[].tap do |res|
|
51
|
+
res << [].tap do |res1|
|
52
|
+
amount1 = amount
|
53
|
+
amount1 += gensen_amount if gensen_idx == 1
|
54
|
+
res1 << { 'code' => code, 'amount' => amount1 }
|
55
|
+
res1 << { 'code' => gensen_code, 'amount' => gensen_amount } if gensen_idx == 0
|
56
|
+
end
|
57
|
+
res << [].tap do |res2|
|
58
|
+
amount2 = amount
|
59
|
+
amount2 += gensen_amount if gensen_idx == 0
|
60
|
+
mount2 ||= amount
|
61
|
+
res2 << { 'code' => code2, 'amount' => amount2 }
|
62
|
+
res2 << { 'code' => gensen_code, 'amount' => gensen_amount } if gensen_idx == 1
|
63
|
+
end
|
64
|
+
end
|
65
|
+
elsif options[:tax_options].include?('jp-consumption')
|
66
|
+
paid_rate = BigDecimal('1') + consumption_rate - gensen_rate
|
67
|
+
consumption_amount = (amount / paid_rate * consumption_rate).round
|
68
|
+
res << [].tap do |res1|
|
69
|
+
amount1 = amount
|
70
|
+
amount1 -= consumption_amount if consumption_idx == 0
|
71
|
+
res1 << { 'code' => code1, 'amount' => amount1 }
|
72
|
+
res1 << { 'code' => consumption_code, 'amount' => consumption_amount } if consumption_idx == 0
|
73
|
+
end
|
74
|
+
res << [].tap do |res2|
|
75
|
+
amount2 = amount
|
76
|
+
amount2 -= consumption_amount if consumption_idx == 1
|
77
|
+
res2 << { 'code' => code2, 'amount' => amount2 }
|
78
|
+
res2 << { 'code' => consumption_code, 'amount' => consumption_amount } if consumption_idx == 1
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
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, update record if exists.
|
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 if o[:id]
|
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
|
@@ -59,13 +70,13 @@ module LucaBook
|
|
59
70
|
res['no'] = txid
|
60
71
|
res['id'] = dat[:id]
|
61
72
|
res['debit_code'] = dat[:debit].length == 1 ? dat[:debit][0][:code] : dat[:debit].map { |d| d[:code] }
|
62
|
-
res['debit_amount'] =
|
73
|
+
res['debit_amount'] = dat[:debit].inject(0) { |sum, d| sum + d[:amount] }
|
63
74
|
res['credit_code'] = dat[:credit].length == 1 ? dat[:credit][0][:code] : dat[:credit].map { |d| d[:code] }
|
64
75
|
res['credit_amount'] = dat[:credit].inject(0) { |sum, d| sum + d[:amount] }
|
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
|