lucabook 0.2.15 → 0.2.21

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: 3f7366698bee6b770fbae39f8fe650e1f460b04dec25c8036c84013f970adaa3
4
+ data.tar.gz: 8ecbf412b31f001f0cfa2de27ceb879463d17f28e3db552770866b44c0b73114
5
5
  SHA512:
6
- metadata.gz: 4cee8065abe4d479dc909e2e9f12f0fdb942e7b3c51b2f5f5e993598080db28411625d990c7f7a7ec85ebc96686a4dcf6d0173e8fed58fd3a3261731ea3da831
7
- data.tar.gz: 1cd81e73a4dfcf3eef52a8105b7e1dc921f9d79d8b9945bf2b9e74d80134d2ca157346d69d98a26037d0f087fa4475e05938ae0044a7b1fcb3120b744f3a9ba8
6
+ metadata.gz: e30fabd50119ad470c90512bcf066b3992b5df2d67139f437a5f4336d73c843fb65dab2a70b5914c190155de68f82ec722840e0f056079061634f77bb5da4964
7
+ data.tar.gz: 3e8775200e05f8bfe0923b53690fa5245441ea4d71768246a7309ee503294d847a466e82da85568d3071f47eccd4f9783a79199b4d4eb630c961de84488a31ed
@@ -1,10 +1,11 @@
1
1
  #!/usr/bin/ruby
2
2
 
3
+ require 'json'
3
4
  require 'optparse'
4
5
  require 'luca_book'
5
6
 
6
- module LucaCmd
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
@@ -12,31 +13,66 @@ module LucaCmd
12
13
  LucaBook::Import.import_json(STDIN.read)
13
14
  else
14
15
  puts 'Usage: luca-book import -c import_config'
16
+ exit 1
15
17
  end
16
18
  end
17
19
 
18
20
  def self.list(args, params)
21
+ args = gen_range(params[:n] || 1) if args.empty?
19
22
  if params['code']
20
- LucaBook::List.term(*args, code: params['code']).flat_list.to_yaml
21
- elsif args.length > 0
22
- LucaBook::List.term(*args).flat_list.to_yaml
23
+ render(LucaBook::List.term(*args, code: params['code']).list_on_code, params)
23
24
  else
24
- # TODO: define default function
25
+ render(LucaBook::List.term(*args).list_journals, params)
26
+ end
27
+ end
28
+
29
+ def self.stats(args, params)
30
+ args = gen_range(params[:n]) if args.empty?
31
+ if params['code']
32
+ render(LucaBook::State.by_code(params['code'], *args), params)
33
+ else
34
+ render(LucaBook::State.term(*args).stats(params[:level]), params)
25
35
  end
26
36
  end
27
37
  end
28
38
 
29
- class Report
39
+ class Report < LucaCmd
30
40
  def self.balancesheet(args, params)
31
- LucaBook::State.term(*args).bs.to_yaml
41
+ level = params[:level] || 3
42
+ legal = params[:legal] || false
43
+ args = gen_range(params[:n] || 1) if args.empty?
44
+ render(LucaBook::State.term(*args).bs(level, legal: legal), params)
32
45
  end
33
46
 
34
47
  def self.profitloss(args, params)
35
- LucaBook::State.term(*args).pl.to_yaml
48
+ level = params[:level] || 2
49
+ args = gen_range(params[:n]) if args.empty?
50
+ render(LucaBook::State.term(*args).pl(level), params)
51
+ end
52
+ end
53
+
54
+ def self.gen_range(count)
55
+ count ||= 3
56
+ today = Date.today
57
+ start = today.prev_month(count - 1)
58
+ [start.year, start.month, today.year, today.month]
59
+ end
60
+
61
+ def self.render(dat, params)
62
+ case params[:output]
63
+ when 'json'
64
+ puts JSON.dump(dat)
65
+ when 'nu'
66
+ LucaSupport::View.nushell(YAML.dump(dat))
67
+ else
68
+ puts YAML.dump(dat)
36
69
  end
37
70
  end
38
71
  end
39
72
 
73
+ def new_pj(args = nil, params = {})
74
+ LucaBook::Setup.create_project params['country'], args[0]
75
+ end
40
76
 
41
77
  LucaRecord::Base.valid_project?
42
78
  cmd = ARGV.shift
@@ -48,41 +84,85 @@ when /journals?/, 'j'
48
84
  case subcmd
49
85
  when 'import'
50
86
  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 }
87
+ opt.banner = 'Usage: luca-book journals import [options] filepath'
88
+ opt.on('-c', '--config VAL', 'import definition'){ |v| params['config'] = v }
89
+ opt.on('-j', '--json', 'import via json format'){ |_v| params['json'] = true }
54
90
  args = opt.parse!(ARGV)
55
91
  LucaCmd::Journal.import(args, params)
56
92
  end
57
93
  when 'list'
58
94
  OptionParser.new do |opt|
59
- opt.banner = 'Usage: luca list [year month]'
95
+ opt.banner = 'Usage: luca-book journals list [options] [YYYY M]'
60
96
  opt.on('-c', '--code VAL', 'search with code') { |v| params['code'] = v }
97
+ opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
98
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
99
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
61
100
  opt.on_tail('List records. If you specify code and/or month, search on each criteria.')
62
101
  args = opt.parse!(ARGV)
63
102
  LucaCmd::Journal.list(args, params)
64
103
  end
104
+ when 'stats'
105
+ OptionParser.new do |opt|
106
+ opt.banner = 'Usage: luca-book journals stats [options] [YYYY M]'
107
+ opt.on('-c', '--code VAL', 'search with code') { |v| params['code'] = v }
108
+ opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
109
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
110
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
111
+ args = opt.parse!(ARGV)
112
+ LucaCmd::Journal.stats(args, params)
113
+ end
114
+ else
115
+ puts 'Proper subcommand needed.'
116
+ puts
117
+ puts 'Usage: luca-book (j|journal[s]) subcommand [options] [YYYY M YYYY M]'
118
+ puts ' import: import journals from JSON/TSV'
119
+ puts ' list: list journals'
120
+ puts ' stats: list account statistics'
121
+ exit 1
122
+ end
123
+ when 'new'
124
+ OptionParser.new do |opt|
125
+ opt.banner = 'Usage: luca-book new [options] Dir'
126
+ opt.on('-c', '--country VAL', 'specify country code') { |v| params['coountry'] = v }
127
+ args = opt.parse(ARGV)
128
+ new_pj(args, params)
65
129
  end
66
130
  when /reports?/, 'r'
67
131
  subcmd = ARGV.shift
68
132
  case subcmd
69
133
  when 'bs'
70
134
  OptionParser.new do |opt|
71
- opt.banner = 'Usage: luca-book reports bs'
135
+ opt.banner = 'Usage: luca-book reports bs [options] [YYYY M]'
136
+ opt.on('-l', '--level VAL', 'account level') { |v| params[:level] = v.to_i }
137
+ opt.on('--legal', 'show legal mandatory account') { |_v| params[:legal] = true }
138
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
139
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
72
140
  args = opt.parse!(ARGV)
73
141
  LucaCmd::Report.balancesheet(args, params)
74
142
  end
75
143
  when 'pl'
76
144
  OptionParser.new do |opt|
77
- opt.banner = 'Usage: luca-book reports pl'
145
+ opt.banner = 'Usage: luca-book reports pl [options] [YYYY M YYYY M]'
146
+ opt.on('-l', '--level VAL', 'account level') { |v| params[:level] = v.to_i }
147
+ opt.on('-n VAL', 'report count') { |v| params[:n] = v.to_i }
148
+ opt.on('--nu', 'show table in nushell') { |_v| params[:output] = 'nu' }
149
+ opt.on('-o', '--output VAL', 'output serialized data') { |v| params[:output] = v }
78
150
  args = opt.parse!(ARGV)
79
151
  LucaCmd::Report.profitloss(args, params)
80
152
  end
153
+ else
154
+ puts 'Proper subcommand needed.'
155
+ puts
156
+ puts 'Usage: luca-book (r|report[s]) (bs|pl) [options] YYYY M'
157
+ puts ' bs: show balance sheet'
158
+ puts ' pl: show statement of income'
159
+ exit 1
81
160
  end
82
- when '--help'
83
- puts 'Usage: luca-book subcommand'
161
+ else
162
+ puts 'Proper subcommand needed.'
163
+ puts
164
+ puts 'Usage: luca-book (j[ournals]|r[eports]) subcommand'
84
165
  puts ' journals: operate journal records'
85
166
  puts ' reports: show reports'
86
- else
87
- puts 'Invalid subcommand'
167
+ exit 1
88
168
  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
- self
50
+ readable(@data)
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
58
- self
67
+ end
68
+ readable(@data)
69
+ end
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
59
75
  end
60
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