lucabook 0.2.18 → 0.2.23
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 +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
|