lucabook 0.2.18 → 0.2.23
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/exe/luca-book +48 -17
- data/lib/luca_book.rb +1 -0
- data/lib/luca_book/dict.rb +78 -1
- data/lib/luca_book/import.rb +29 -32
- data/lib/luca_book/import_jp.rb +83 -0
- data/lib/luca_book/journal.rb +53 -15
- data/lib/luca_book/list.rb +3 -7
- data/lib/luca_book/list_by_header.rb +120 -0
- data/lib/luca_book/setup.rb +2 -1
- data/lib/luca_book/state.rb +135 -92
- 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 +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 866a73f9e372f2ac755ed301c9e9b88bebb556b53a5ffd604391c45eda927653
|
4
|
+
data.tar.gz: 41b02c0e5c567dbfedf37fe4148d11270a0f8776c6fcc78f630354e281aad5d3
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 1f295b8ab521f4b95c81ec57c57579ebf87ae01c3f1acf2fbc564bf076731d1b1d089e0959660752529e190eecfa3e107ee184b6f91484f9bdc61977eda80886
|
7
|
+
data.tar.gz: '0820bc3b42bfd48b0cf8f2924774acc8887c6fda373ec71fcebab5152b46e292096b68f174ac643613fda4ef72dc62a79300b4fa82e306d144f2fe404a770213'
|
data/exe/luca-book
CHANGED
@@ -1,15 +1,17 @@
|
|
1
1
|
#!/usr/bin/ruby
|
2
2
|
|
3
|
+
require 'json'
|
3
4
|
require 'optparse'
|
4
5
|
require 'luca_book'
|
5
6
|
|
6
|
-
|
7
|
-
class Journal
|
7
|
+
class LucaCmd
|
8
|
+
class Journal < LucaCmd
|
8
9
|
def self.import(args, params)
|
9
10
|
if params['config']
|
10
11
|
LucaBook::Import.new(args[0], params['config']).import_csv
|
11
12
|
elsif params['json']
|
12
|
-
|
13
|
+
str = args[0].nil? ? STDIN.read : File.read(args[0])
|
14
|
+
LucaBook::Import.import_json(str)
|
13
15
|
else
|
14
16
|
puts 'Usage: luca-book import -c import_config'
|
15
17
|
exit 1
|
@@ -17,42 +19,60 @@ module LucaCmd
|
|
17
19
|
end
|
18
20
|
|
19
21
|
def self.list(args, params)
|
20
|
-
args =
|
22
|
+
args = gen_range(params[:n] || 1) if args.empty?
|
21
23
|
if params['code']
|
22
|
-
|
24
|
+
if params['headers']
|
25
|
+
render(LucaBook::ListByHeader.term(*args, code: params['code'], header: params['headers']).list_by_code, params)
|
26
|
+
else
|
27
|
+
render(LucaBook::List.term(*args, code: params['code']).list_by_code, params)
|
28
|
+
end
|
23
29
|
else
|
24
|
-
LucaBook::List.term(*args).list_journals
|
30
|
+
render(LucaBook::List.term(*args).list_journals, params)
|
25
31
|
end
|
26
32
|
end
|
27
33
|
|
28
34
|
def self.stats(args, params)
|
29
|
-
args =
|
30
|
-
|
35
|
+
args = gen_range(params[:n]) if args.empty?
|
36
|
+
if params['code']
|
37
|
+
render(LucaBook::State.by_code(params['code'], *args), params)
|
38
|
+
else
|
39
|
+
render(LucaBook::State.range(*args).stats(params[:level]), params)
|
40
|
+
end
|
31
41
|
end
|
32
42
|
end
|
33
43
|
|
34
|
-
class Report
|
44
|
+
class Report < LucaCmd
|
35
45
|
def self.balancesheet(args, params)
|
36
|
-
level = params[:level] ||
|
46
|
+
level = params[:level] || 3
|
37
47
|
legal = params[:legal] || false
|
38
|
-
args =
|
39
|
-
LucaBook::State.
|
48
|
+
args = gen_range(params[:n] || 1) if args.empty?
|
49
|
+
render(LucaBook::State.range(*args).bs(level, legal: legal), params)
|
40
50
|
end
|
41
51
|
|
42
52
|
def self.profitloss(args, params)
|
43
|
-
|
44
|
-
|
53
|
+
level = params[:level] || 2
|
54
|
+
args = gen_range(params[:n]) if args.empty?
|
55
|
+
render(LucaBook::State.range(*args).pl(level), params)
|
45
56
|
end
|
46
57
|
end
|
47
58
|
|
48
|
-
|
49
|
-
|
50
|
-
def gen_range(count)
|
59
|
+
def self.gen_range(count)
|
51
60
|
count ||= 3
|
52
61
|
today = Date.today
|
53
62
|
start = today.prev_month(count - 1)
|
54
63
|
[start.year, start.month, today.year, today.month]
|
55
64
|
end
|
65
|
+
|
66
|
+
def self.render(dat, params)
|
67
|
+
case params[:output]
|
68
|
+
when 'json'
|
69
|
+
puts JSON.dump(dat)
|
70
|
+
when 'nu'
|
71
|
+
LucaSupport::View.nushell(YAML.dump(dat))
|
72
|
+
else
|
73
|
+
puts YAML.dump(dat)
|
74
|
+
end
|
75
|
+
end
|
56
76
|
end
|
57
77
|
|
58
78
|
def new_pj(args = nil, params = {})
|
@@ -79,7 +99,10 @@ when /journals?/, 'j'
|
|
79
99
|
OptionParser.new do |opt|
|
80
100
|
opt.banner = 'Usage: luca-book journals list [options] [YYYY M]'
|
81
101
|
opt.on('-c', '--code VAL', 'search with code') { |v| params['code'] = v }
|
102
|
+
opt.on('--customer', 'categorize by x-customer header') { |_v| params['headers'] = 'x-customer' }
|
82
103
|
opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
|
104
|
+
opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
|
105
|
+
opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
|
83
106
|
opt.on_tail('List records. If you specify code and/or month, search on each criteria.')
|
84
107
|
args = opt.parse!(ARGV)
|
85
108
|
LucaCmd::Journal.list(args, params)
|
@@ -87,7 +110,10 @@ when /journals?/, 'j'
|
|
87
110
|
when 'stats'
|
88
111
|
OptionParser.new do |opt|
|
89
112
|
opt.banner = 'Usage: luca-book journals stats [options] [YYYY M]'
|
113
|
+
opt.on('-c', '--code VAL', 'search with code') { |v| params['code'] = v }
|
90
114
|
opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
|
115
|
+
opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
|
116
|
+
opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
|
91
117
|
args = opt.parse!(ARGV)
|
92
118
|
LucaCmd::Journal.stats(args, params)
|
93
119
|
end
|
@@ -115,13 +141,18 @@ when /reports?/, 'r'
|
|
115
141
|
opt.banner = 'Usage: luca-book reports bs [options] [YYYY M]'
|
116
142
|
opt.on('-l', '--level VAL', 'account level') { |v| params[:level] = v.to_i }
|
117
143
|
opt.on('--legal', 'show legal mandatory account') { |_v| params[:legal] = true }
|
144
|
+
opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
|
145
|
+
opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
|
118
146
|
args = opt.parse!(ARGV)
|
119
147
|
LucaCmd::Report.balancesheet(args, params)
|
120
148
|
end
|
121
149
|
when 'pl'
|
122
150
|
OptionParser.new do |opt|
|
123
151
|
opt.banner = 'Usage: luca-book reports pl [options] [YYYY M YYYY M]'
|
152
|
+
opt.on('-l', '--level VAL', 'account level') { |v| params[:level] = v.to_i }
|
124
153
|
opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
|
154
|
+
opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
|
155
|
+
opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
|
125
156
|
args = opt.parse!(ARGV)
|
126
157
|
LucaCmd::Report.profitloss(args, params)
|
127
158
|
end
|
data/lib/luca_book.rb
CHANGED
@@ -9,6 +9,7 @@ module LucaBook
|
|
9
9
|
autoload :Import, 'luca_book/import'
|
10
10
|
autoload :Journal, 'luca_book/journal'
|
11
11
|
autoload :List, 'luca_book/list'
|
12
|
+
autoload :ListByHeader, 'luca_book/list_by_header'
|
12
13
|
autoload :Setup, 'luca_book/setup'
|
13
14
|
autoload :State, 'luca_book/state'
|
14
15
|
autoload :Util, 'luca_book/util'
|
data/lib/luca_book/dict.rb
CHANGED
@@ -7,8 +7,85 @@ require 'pathname'
|
|
7
7
|
|
8
8
|
module LucaBook
|
9
9
|
class Dict < LucaRecord::Dict
|
10
|
+
# Column number settings for CSV/TSV convert
|
11
|
+
#
|
12
|
+
# :label
|
13
|
+
# for double entry data
|
14
|
+
# :counter_label
|
15
|
+
# must be specified with label
|
16
|
+
# :debit_label
|
17
|
+
# for double entry data
|
18
|
+
# * debit_value
|
19
|
+
# :credit_label
|
20
|
+
# for double entry data
|
21
|
+
# * credit_value
|
22
|
+
# :note
|
23
|
+
# can be the same column as another label
|
24
|
+
#
|
25
|
+
# :encoding
|
26
|
+
# file encoding
|
27
|
+
#
|
28
|
+
def csv_config
|
29
|
+
{}.tap do |config|
|
30
|
+
if @config.dig('label')
|
31
|
+
config[:label] = @config['label'].to_i
|
32
|
+
if @config.dig('counter_label')
|
33
|
+
config[:counter_label] = @config['counter_label']
|
34
|
+
config[:type] = 'single'
|
35
|
+
end
|
36
|
+
elsif @config.dig('debit_label')
|
37
|
+
config[:debit_label] = @config['debit_label'].to_i
|
38
|
+
if @config.dig('credit_label')
|
39
|
+
config[:credit_label] = @config['credit_label'].to_i
|
40
|
+
config[:type] = 'double'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
config[:type] ||= 'invalid'
|
44
|
+
config[:debit_value] = @config['debit_value'].to_i if @config.dig('debit_value')
|
45
|
+
config[:credit_value] = @config['credit_value'].to_i if @config.dig('credit_value')
|
46
|
+
config[:note] = @config['note'] if @config.dig('note')
|
47
|
+
config[:encoding] = @config['encoding'] if @config.dig('encoding')
|
48
|
+
|
49
|
+
config[:year] = @config['year'] if @config.dig('year')
|
50
|
+
config[:month] = @config['month'] if @config.dig('month')
|
51
|
+
config[:day] = @config['day'] if @config.dig('day')
|
52
|
+
config[:default_debit] = @config['default_debit'] if @config.dig('default_debit')
|
53
|
+
config[:default_credit] = @config['default_credit'] if @config.dig('default_credit')
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
def search(word, default_word = nil, amount = nil)
|
58
|
+
res = super(word, default_word, main_key: 'account_label')
|
59
|
+
if res.is_a?(Array) && res[0].is_a?(Array)
|
60
|
+
filter_amount(res, amount)
|
61
|
+
else
|
62
|
+
res
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
# Choose setting on Big or small condition.
|
67
|
+
#
|
68
|
+
def filter_amount(settings, amount = nil)
|
69
|
+
return settings[0] if amount.nil?
|
70
|
+
|
71
|
+
settings.each do |item|
|
72
|
+
return item unless item[1].keys.include?(:on_amount)
|
73
|
+
|
74
|
+
condition = item.dig(1, :on_amount)
|
75
|
+
case condition[0]
|
76
|
+
when '>'
|
77
|
+
return item if amount > BigDecimal(condition[1..])
|
78
|
+
when '<'
|
79
|
+
return item if amount < BigDecimal(condition[1..])
|
80
|
+
else
|
81
|
+
return item
|
82
|
+
end
|
83
|
+
end
|
84
|
+
nil
|
85
|
+
end
|
86
|
+
|
10
87
|
def self.latest_balance
|
11
|
-
dict_dir = Pathname(LucaSupport::
|
88
|
+
dict_dir = Pathname(LucaSupport::PJDIR) / 'data' / 'balance'
|
12
89
|
# TODO: search latest balance dictionary
|
13
90
|
load_tsv_dict(dict_dir / 'start.tsv')
|
14
91
|
end
|
data/lib/luca_book/import.rb
CHANGED
@@ -4,9 +4,14 @@ require 'date'
|
|
4
4
|
require 'json'
|
5
5
|
require 'luca_book'
|
6
6
|
require 'luca_support'
|
7
|
-
#require 'luca_book/dict'
|
8
7
|
require 'luca_record'
|
9
8
|
|
9
|
+
begin
|
10
|
+
require "luca_book/import_#{LucaSupport::CONFIG['country']}"
|
11
|
+
rescue LoadError => e
|
12
|
+
e.message
|
13
|
+
end
|
14
|
+
|
10
15
|
module LucaBook
|
11
16
|
class Import
|
12
17
|
DEBIT_DEFAULT = '10XX'
|
@@ -18,7 +23,7 @@ module LucaBook
|
|
18
23
|
@target_file = path
|
19
24
|
# TODO: yaml need to be configurable
|
20
25
|
@dict_name = dict
|
21
|
-
@dict =
|
26
|
+
@dict = LucaBook::Dict.new("import-#{dict}.yaml")
|
22
27
|
@code_map = LucaRecord::Dict.reverse(LucaRecord::Dict.load('base.tsv'))
|
23
28
|
@config = @dict.csv_config if dict
|
24
29
|
end
|
@@ -45,8 +50,6 @@ module LucaBook
|
|
45
50
|
#
|
46
51
|
def self.import_json(io)
|
47
52
|
JSON.parse(io).each do |d|
|
48
|
-
validate(d)
|
49
|
-
|
50
53
|
code_map = LucaRecord::Dict.reverse(LucaRecord::Dict.load('base.tsv'))
|
51
54
|
d['debit'].each { |h| h['code'] = code_map.dig(h['label']) || DEBIT_DEFAULT }
|
52
55
|
d['credit'].each { |h| h['code'] = code_map.dig(h['label']) || CREDIT_DEFAULT }
|
@@ -67,46 +70,39 @@ module LucaBook
|
|
67
70
|
end
|
68
71
|
end
|
69
72
|
|
70
|
-
def self.validate(obj)
|
71
|
-
raise 'NoDateKey' unless obj.key?('date')
|
72
|
-
raise 'NoDebitKey' unless obj.key?('debit')
|
73
|
-
raise 'NoDebitValue' if obj['debit'].empty?
|
74
|
-
raise 'NoCreditKey' unless obj.key?('credit')
|
75
|
-
raise 'NoCreditValue' if obj['credit'].empty?
|
76
|
-
end
|
77
|
-
|
78
73
|
private
|
79
74
|
|
80
|
-
#
|
81
75
|
# convert single entry data
|
82
76
|
#
|
83
77
|
def parse_single(row)
|
84
|
-
|
78
|
+
if (row.dig(@config[:credit_value]) || []).empty?
|
79
|
+
value = BigDecimal(row[@config[:debit_value]])
|
80
|
+
debit = true
|
81
|
+
else
|
82
|
+
value = BigDecimal(row[@config[:credit_value]])
|
83
|
+
end
|
84
|
+
default_label = debit ? @config.dig(:default_debit) : @config.dig(:default_credit)
|
85
|
+
code, options = search_code(row[@config[:label]], default_label, value)
|
86
|
+
counter_code = @code_map.dig(@config[:counter_label])
|
87
|
+
if respond_to? :tax_extension
|
88
|
+
data, data_c = tax_extension(code, counter_code, value, options) if options
|
89
|
+
end
|
90
|
+
data ||= [{ 'code' => code, 'value' => value }]
|
91
|
+
data_c ||= [{ 'code' => counter_code, 'value' => value }]
|
85
92
|
{}.tap do |d|
|
86
93
|
d['date'] = parse_date(row)
|
87
|
-
if
|
88
|
-
d['debit'] =
|
89
|
-
|
90
|
-
]
|
91
|
-
d['credit'] = [
|
92
|
-
{ 'code' => @code_map.dig(@config[:counter_label]) }
|
93
|
-
]
|
94
|
+
if debit
|
95
|
+
d['debit'] = data
|
96
|
+
d['credit'] = data_c
|
94
97
|
else
|
95
|
-
d['debit'] =
|
96
|
-
|
97
|
-
]
|
98
|
-
d['credit'] = [
|
99
|
-
{ 'code' => search_code(row[@config[:label]], @config.dig(:default_credit)) || CREDIT_DEFAULT }
|
100
|
-
]
|
98
|
+
d['debit'] = data_c
|
99
|
+
d['credit'] = data
|
101
100
|
end
|
102
|
-
d['debit'][0]['value'] = value
|
103
|
-
d['credit'][0]['value'] = value
|
104
101
|
d['note'] = Array(@config[:note]).map{ |col| row[col] }.join(' ')
|
105
102
|
d['x-editor'] = "LucaBook::Import/#{@dict_name}"
|
106
103
|
end
|
107
104
|
end
|
108
105
|
|
109
|
-
#
|
110
106
|
# convert double entry data
|
111
107
|
#
|
112
108
|
def parse_double(row)
|
@@ -125,8 +121,9 @@ module LucaBook
|
|
125
121
|
end
|
126
122
|
end
|
127
123
|
|
128
|
-
def search_code(label, default_label)
|
129
|
-
@
|
124
|
+
def search_code(label, default_label, amount = nil)
|
125
|
+
label, options = @dict.search(label, default_label, amount)
|
126
|
+
[@code_map.dig(label), options]
|
130
127
|
end
|
131
128
|
|
132
129
|
def parse_date(row)
|
@@ -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, 'value' => amount1 }
|
35
|
+
res1 << { 'code' => consumption_code, 'value' => consumption_amount } if consumption_idx == 0
|
36
|
+
res1 << { 'code' => gensen_code, 'value' => 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, 'value' => amount2 }
|
43
|
+
res2 << { 'code' => consumption_code, 'value' => consumption_amount } if consumption_idx == 1
|
44
|
+
res2 << { 'code' => gensen_code, 'value' => 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, 'value' => amount1 }
|
55
|
+
res1 << { 'code' => gensen_code, 'value' => 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, 'value' => amount2 }
|
62
|
+
res2 << { 'code' => gensen_code, 'value' => 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, 'value' => amount1 }
|
72
|
+
res1 << { 'code' => consumption_code, 'value' => 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, 'value' => amount2 }
|
78
|
+
res2 << { 'code' => consumption_code, 'value' => consumption_amount } if consumption_idx == 1
|
79
|
+
end
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
data/lib/luca_book/journal.rb
CHANGED
@@ -12,9 +12,36 @@ module LucaBook
|
|
12
12
|
|
13
13
|
# create journal from hash
|
14
14
|
#
|
15
|
-
def self.create(
|
15
|
+
def self.create(dat)
|
16
|
+
d = LucaSupport::Code.keys_stringify(dat)
|
17
|
+
validate(d)
|
18
|
+
raise 'NoDateKey' unless d.key?('date')
|
19
|
+
|
16
20
|
date = Date.parse(d['date'])
|
17
21
|
|
22
|
+
# TODO: need to sync filename & content. Limit code length for filename
|
23
|
+
# codes = (debit_code + credit_code).uniq
|
24
|
+
codes = nil
|
25
|
+
|
26
|
+
create_record(nil, date, codes) { |f| f.write journal2csv(d) }
|
27
|
+
end
|
28
|
+
|
29
|
+
# update journal with hash.
|
30
|
+
# If record not found with id, no record will be created.
|
31
|
+
#
|
32
|
+
def self.save(dat)
|
33
|
+
d = LucaSupport::Code.keys_stringify(dat)
|
34
|
+
raise 'record has no id.' if d['id'].nil?
|
35
|
+
|
36
|
+
validate(d)
|
37
|
+
parts = d['id'].split('/')
|
38
|
+
raise 'invalid ID' if parts.length != 2
|
39
|
+
|
40
|
+
codes = nil
|
41
|
+
open_records(@dirname, parts[0], parts[1], codes, 'w') { |f, _path| f.write journal2csv(d) }
|
42
|
+
end
|
43
|
+
|
44
|
+
def self.journal2csv(d)
|
18
45
|
debit_amount = LucaSupport::Code.decimalize(serialize_on_key(d['debit'], 'value'))
|
19
46
|
credit_amount = LucaSupport::Code.decimalize(serialize_on_key(d['credit'], 'value'))
|
20
47
|
raise 'BalanceUnmatch' if debit_amount.inject(:+) != credit_amount.inject(:+)
|
@@ -22,16 +49,13 @@ module LucaBook
|
|
22
49
|
debit_code = serialize_on_key(d['debit'], 'code')
|
23
50
|
credit_code = serialize_on_key(d['credit'], 'code')
|
24
51
|
|
25
|
-
|
26
|
-
# codes = (debit_code + credit_code).uniq
|
27
|
-
codes = nil
|
28
|
-
create_record!(date, codes) do |f|
|
52
|
+
csv = CSV.generate('', col_sep: "\t", headers: false) do |f|
|
29
53
|
f << debit_code
|
30
54
|
f << LucaSupport::Code.readable(debit_amount)
|
31
55
|
f << credit_code
|
32
56
|
f << LucaSupport::Code.readable(credit_amount)
|
33
57
|
['x-customer', 'x-editor'].each do |x_header|
|
34
|
-
f << [x_header, d[x_header]] if d.dig(x_header)
|
58
|
+
f << [x_header, d['headers'][x_header]] if d.dig('headers', x_header)
|
35
59
|
end
|
36
60
|
f << []
|
37
61
|
f << [d.dig('note')]
|
@@ -45,11 +69,19 @@ module LucaBook
|
|
45
69
|
change_codes(obj[:id], codes)
|
46
70
|
end
|
47
71
|
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
72
|
+
def self.validate(obj)
|
73
|
+
raise 'NoDebitKey' unless obj.key?('debit')
|
74
|
+
raise 'NoCreditKey' unless obj.key?('credit')
|
75
|
+
debit_codes = serialize_on_key(obj['debit'], 'code').compact
|
76
|
+
debit_values = serialize_on_key(obj['debit'], 'value').compact
|
77
|
+
raise 'NoDebitCode' if debit_codes.empty?
|
78
|
+
raise 'NoDebitValue' if debit_values.empty?
|
79
|
+
raise 'UnmatchDebit' if debit_codes.length != debit_values.length
|
80
|
+
credit_codes = serialize_on_key(obj['credit'], 'code').compact
|
81
|
+
credit_values = serialize_on_key(obj['credit'], 'value').compact
|
82
|
+
raise 'NoCreditCode' if credit_codes.empty?
|
83
|
+
raise 'NoCreditValue' if credit_values.empty?
|
84
|
+
raise 'UnmatchCredit' if credit_codes.length != credit_values.length
|
53
85
|
end
|
54
86
|
|
55
87
|
# collect values on specified key
|
@@ -76,10 +108,16 @@ module LucaBook
|
|
76
108
|
when 3
|
77
109
|
line.each_with_index { |amount, j| record[:credit][j][:amount] = BigDecimal(amount.to_s) }
|
78
110
|
else
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
111
|
+
case body
|
112
|
+
when false
|
113
|
+
if line.empty?
|
114
|
+
record[:note] ||= []
|
115
|
+
body = true
|
116
|
+
else
|
117
|
+
record[:headers] ||= {}
|
118
|
+
record[:headers][line[0]] = line[1]
|
119
|
+
end
|
120
|
+
when true
|
83
121
|
record[:note] << line.join(' ') if body
|
84
122
|
end
|
85
123
|
end
|