lucasalary 0.1.14 → 0.1.19

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: 50c263cca4e4786fc11301bdbbab093e69dfd94be7b6f1237c570d2fe6ee6928
4
- data.tar.gz: 49569d2fb2145e028a40817e464812ac7ff1e4499b4c49b96509548320924c92
3
+ metadata.gz: dacc04e5464dfd8f7d38db60b2053af1fe609bc4d71d65bdd7fb83c5d460618a
4
+ data.tar.gz: 800e13698b5e92482085abd8b0928e9aed42fa23c24f5a046b2e06b4cea15889
5
5
  SHA512:
6
- metadata.gz: b401890cb3dfa6b822461269750bea5821ee9ebcc55a8094596abff5cc20bd8285d2b37ce7d4354920b89266fee641861fd46bf52fb2c32b0a8e2cec2231eb60
7
- data.tar.gz: af95b05c547430062bc0a1fa3242974ed37c899fe8be4607b2c8fcb54fc28f3c1a76a5730852882a8ffea56b14a3590824079559d11ed20658b68fe964639c57
6
+ metadata.gz: 99bf549286ee85d693afcbb0fd9774fc833316fb34c6ebb14edcbd31264948c0393d9561ff262a345dedad75a9e3b6ae93cf325730b5106e89c9a02e201ffc97
7
+ data.tar.gz: dbac5444029228f95b84cf173431f65f657f49a5cfbb7d17b161765d09528a8933680a5d74fe490c11c788f68264abb51ab717c1c13d894db58554c780b0c448
@@ -0,0 +1,16 @@
1
+ ## LucaSalary 0.1.20
2
+
3
+ * Implement `year_total`
4
+
5
+ ## LucaSalary 0.1.19
6
+
7
+ * Support `payment_term` on config.yml for accounting export.
8
+
9
+ ## LucaSalary 0.1.18
10
+
11
+ * Add summary to payslip. Refactor monthly payment.
12
+
13
+ ## LucaSalary 0.1.17
14
+
15
+ * Breaking change: restructure CLI in sub-sub command format.
16
+ * Add 'x-editor' on export to LucaBook
data/README.md CHANGED
@@ -1,2 +1,5 @@
1
1
  # Luca Salary
2
+
3
+ [![Gem Version](https://badge.fury.io/rb/lucasalary.svg)](https://badge.fury.io/rb/lucasalary)
4
+
2
5
  Salary calculation framework
@@ -5,60 +5,93 @@ require 'optparse'
5
5
  require 'luca_salary'
6
6
  require 'luca_salary/monthly'
7
7
 
8
- def export(args = nil, _params = nil)
9
- if args
10
- args << 28 if args.length == 2 # specify safe last day
11
- LucaSalary::Monthly.new(args.join('-')).export_json
12
- else
13
- LucaSalary::Monthly.new.export_json
8
+ module LucaCmd
9
+ class Profile
10
+ def self.create(args = nil, _params = nil)
11
+ LucaSalary::Profile.gen_profile!(args.first)
12
+ end
14
13
  end
15
- end
16
14
 
17
- def payment(args = nil, _params = nil)
18
- if args
19
- args << 28 if args.length == 2 # specify safe last day
20
- LucaSalary::Base.new(args.join('-')).calc
21
- else
22
- LucaSalary::Base.new.calc
23
- end
24
- end
15
+ class Payment
16
+ def self.create(args = nil, _params = nil)
17
+ if args
18
+ args << 28 if args.length == 2 # specify safe last day
19
+ LucaSalary::Monthly.new(args.join('-')).calc
20
+ else
21
+ LucaSalary::Monthly.new.calc
22
+ end
23
+ end
25
24
 
26
- def report(args = nil, params = nil)
27
- if args
28
- args << 28 if args.length == 2 # specify safe last day
29
- LucaSalary::Monthly.new(args.join('-')).report(params.dig('mode'))
30
- else
31
- LucaSalary::Monthly.new.report(params.dig('mode'))
32
- end
33
- end
25
+ def self.export(args = nil, _params = nil)
26
+ if args
27
+ args << 28 if args.length == 2 # specify safe last day
28
+ LucaSalary::Payment.new(args.join('-')).export_json
29
+ else
30
+ LucaSalary::Payment.new.export_json
31
+ end
32
+ end
34
33
 
35
- def add_person(args = nil, _params = nil)
36
- LucaSalary::Profile.gen_profile!(args.first)
34
+ def self.list(args = nil, params = nil)
35
+ if args
36
+ args << 28 if args.length == 2 # specify safe last day
37
+ LucaSalary::Monthly.new(args.join('-')).report(params.dig('mode'))
38
+ else
39
+ LucaSalary::Monthly.new.report(params.dig('mode'))
40
+ end
41
+ end
42
+ end
37
43
  end
38
44
 
45
+ LucaRecord::Base.valid_project?
39
46
  cmd = ARGV.shift
47
+ params = {}
40
48
 
41
49
  case cmd
42
- when 'add'
43
- add_person(ARGV)
44
- when 'export'
45
- export(ARGV)
46
- when 'report'
47
- params = {}
48
- OptionParser.new do |opt|
49
- opt.banner = 'Usage: luca-salary report [--mail] year month [date]'
50
- opt.on('--mail', 'send to managers') { |_v| params['mode'] = 'mail' }
51
- args = opt.parse(ARGV)
52
- report(args, params)
50
+ when /profiles?/
51
+ subcmd = ARGV.shift
52
+ case subcmd
53
+ when 'create'
54
+ OptionParser.new do |opt|
55
+ opt.banner = 'Usage: luca-salary profiles create Name'
56
+ args = opt.parse(ARGV)
57
+ LucaCmd::Profile.create(args)
58
+ end
59
+ else
60
+ puts 'Proper subcommand needed.'
61
+ puts
62
+ puts 'Usage: luca-salary profile[s] create Name'
63
+ exit 1
53
64
  end
54
- when 'payment'
55
- params = {}
56
- OptionParser.new do |opt|
57
- opt.banner = 'Usage: luca-salary payment [--mail] year month [date]'
58
- opt.on('--mail', 'send to managers') { |_v| params['mode'] = 'mail' }
59
- args = opt.parse(ARGV)
60
- payment(args)
65
+ when 'export'
66
+ LucaCmd::Payment.export(ARGV)
67
+ when /payments?/
68
+ subcmd = ARGV.shift
69
+ case subcmd
70
+ when 'create'
71
+ OptionParser.new do |opt|
72
+ opt.banner = 'Usage: luca-salary payments create year month [date]'
73
+ args = opt.parse(ARGV)
74
+ LucaCmd::Payment.create(args)
75
+ end
76
+ when 'list'
77
+ OptionParser.new do |opt|
78
+ opt.banner = 'Usage: luca-salary payments list [--mail] year month [date]'
79
+ opt.on('--mail', 'send to managers') { |_v| params['mode'] = 'mail' }
80
+ args = opt.parse(ARGV)
81
+ LucaCmd::Payment.list(args, params)
82
+ end
83
+ else
84
+ puts 'Proper subcommand needed.'
85
+ puts
86
+ puts 'Usage: luca-salary payment[s] (create|list) [--help|options]'
87
+ exit 1
61
88
  end
62
89
  else
63
- puts 'Invalid subcommand'
90
+ puts 'Proper subcommand needed.'
91
+ puts
92
+ puts 'Usage: luca-salary subcommand [options]'
93
+ puts ' profiles'
94
+ puts ' payments'
95
+ puts ' export: puts payment data for LucaBook import'
96
+ exit 1
64
97
  end
@@ -1,5 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'luca_record'
3
4
  require 'luca_salary/version'
4
5
 
5
6
  module LucaSalary
@@ -9,58 +9,18 @@ require 'luca_record'
9
9
 
10
10
  module LucaSalary
11
11
  class Base < LucaRecord::Base
12
- attr_reader :driver, :dict, :config, :pjdir
12
+ attr_reader :dict, :config, :pjdir
13
13
  @dirname = 'payments'
14
14
 
15
15
  def initialize(date = nil)
16
16
  @date = date.nil? ? Date.today : Date.parse(date)
17
- @pjdir = Pathname(LucaSupport::Config::Pjdir)
18
- @config = load_config(@pjdir / 'config.yml')
19
- @driver = set_driver
20
17
  @dict = load_dict
21
18
  end
22
19
 
23
- #
24
- # call country specific calculation
25
- #
26
- def calc
27
- self.class.prepare_dir!(datadir / 'payments', @date)
28
- country = @driver.new(@pjdir, @config, @date)
29
- LucaSalary::Profile.all do |profile|
30
- current_profile = parse_current(profile)
31
- h = country.calc_payment(current_profile)
32
- LucaSalary::Payment.new(@date.to_s).create(current_profile, h)
33
- end
34
- end
35
-
36
- def gen_aggregation!
37
- LucaSalary::Profile.all do |profile|
38
- id = profile.dig('id')
39
- payment = {}
40
- targetdir = @date.year.to_s + 'Z'
41
- past_data = LucaRecord::Base.find(id, "payments/#{targetdir}").first
42
- (1..12).map do |month|
43
- origin_dir = @date.year.to_s + [nil, 'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L'][month]
44
- origin = LucaRecord::Base.find(id, "payments/#{origin_dir}").first
45
- # TODO: to be updated null check
46
- if origin == {}
47
- month
48
- else
49
- origin.select { |k, _v| /^[1-4][0-9A-Fa-f]{,3}$/.match(k) }.each do |k, v|
50
- payment[k] = payment[k] ? payment[k] + v : v
51
- end
52
- nil
53
- end
54
- end
55
- self.class.create(past_data.merge!(payment), "payments/#{targetdir}")
56
- end
57
- end
58
-
59
20
  def select_code(dat, code)
60
21
  dat.filter { |k, _v| /^#{code}[0-9A-Fa-f]{,3}$/.match(k.to_s) }
61
22
  end
62
23
 
63
- #
64
24
  # Subtotal each items.
65
25
  # 1::
66
26
  # Base salary or wages.
@@ -91,19 +51,15 @@ module LucaSalary
91
51
 
92
52
  private
93
53
 
94
- def datadir
95
- @pjdir / 'data'
96
- end
97
-
98
54
  def load_dict
99
- LucaRecord::Dict.load_tsv_dict(@pjdir / 'dict' / 'code.tsv')
55
+ LucaRecord::Dict.load_tsv_dict(Pathname(PJDIR) / 'dict' / 'code.tsv')
100
56
  end
101
57
 
102
58
  def set_driver
103
- code = @config['countryCode']
59
+ code = CONFIG['country']
104
60
  if code
105
61
  require "luca_salary/#{code.downcase}"
106
- Kernel.const_get "LucaSalary::#{code.upcase}"
62
+ Kernel.const_get "LucaSalary::#{code.capitalise}"
107
63
  else
108
64
  nil
109
65
  end
@@ -9,26 +9,46 @@ require 'luca_salary'
9
9
  require 'luca_record'
10
10
 
11
11
  module LucaSalary
12
- class Monthly < LucaRecord::Base
12
+ class Monthly < LucaSalary::Base
13
+ @dirname = 'payments'
14
+
13
15
  def initialize(date = nil)
14
- @date = parse_date(date)
15
- @pjdir = Pathname(LucaSupport::Config::Pjdir)
16
+ @date = date.nil? ? Date.today : Date.parse(date)
17
+ @pjdir = Pathname(LucaSupport::PJDIR)
16
18
  @config = load_config(@pjdir + 'config.yml')
19
+ @driver = set_driver
17
20
  end
18
21
 
22
+ # call country specific calculation
19
23
  #
24
+ def calc
25
+ country = @driver.new(@pjdir, @config, @date)
26
+ # TODO: handle retirement
27
+ LucaSalary::Profile.all do |profile|
28
+ current_profile = parse_current(profile)
29
+ if self.class.search(@date.year, @date.month, @date.day, current_profile['id']).count > 0
30
+ puts "payment record already exists: #{current_profile['id']}"
31
+ return nil
32
+ end
33
+ h = country.calc_payment(current_profile)
34
+ h['profile_id'] = current_profile['id']
35
+ self.class.create(h, date: @date, codes: Array(current_profile['id']))
36
+ end
37
+ end
38
+
20
39
  # output payslips via mail or console
21
40
  #
22
41
  def report(mode = nil)
42
+ data = LucaSalary::Payment.new(@date.to_s).payslip
23
43
  if mode == 'mail'
24
44
  mail = Mail.new do
25
45
  subject '[luca salary] Monthly Payment'
26
46
  end
27
47
  mail.to = @config.dig('mail', 'report_mail')
28
- mail.text_part = YAML.dump(LucaSalary::Payment.new(@date.to_s).payslip)
48
+ mail.text_part = YAML.dump(LucaSupport::Code.readable(data))
29
49
  LucaSupport::Mail.new(mail, @pjdir).deliver
30
50
  else
31
- puts YAML.dump(LucaSalary::Payment.new(@date.to_s).payslip)
51
+ puts YAML.dump(LucaSupport::Code.readable(data))
32
52
  end
33
53
  end
34
54
 
@@ -13,47 +13,82 @@ module LucaSalary
13
13
 
14
14
  def initialize(date = nil)
15
15
  @date = Date.parse(date)
16
- @pjdir = Pathname(LucaSupport::Config::Pjdir)
16
+ @pjdir = Pathname(LucaSupport::PJDIR)
17
17
  @dict = LucaRecord::Dict.load_tsv_dict(@pjdir / 'dict' / 'code.tsv')
18
18
  end
19
19
 
20
- #
21
- # create record with LucaSalary::Profile instance and apyment data
22
- #
23
- def create(profile, payment)
24
- id = profile.dig('id')
25
- if self.class.search(@date.year, @date.month, @date.day, id).first
26
- puts "payment record already exists: #{id}"
27
- return nil
28
- end
29
-
30
- self.class.gen_record_file!('payments', @date, Array(id)) do |f|
31
- f.write(YAML.dump(payment.sort.to_h))
32
- end
33
- end
34
-
35
20
  def payslip
36
21
  {}.tap do |report|
37
22
  report['asof'] = "#{@date.year}/#{@date.month}"
23
+ report['payments'] = []
38
24
  report['records'] = []
39
25
 
40
26
  self.class.asof(@date.year, @date.month) do |payment|
27
+ profile = LucaSalary::Profile.find(payment['profile_id'])
28
+ summary = {
29
+ 'name' => profile['name'],
30
+ "#{@dict.dig('5', :label) || '5'}" => payment['5']
31
+ }
32
+
41
33
  slip = {}.tap do |line|
34
+ line['name'] = profile['name']
42
35
  payment.each do |k, v|
43
36
  next if k == 'id'
44
37
 
45
38
  line["#{@dict.dig(k, :label) || k}"] = v
46
39
  end
47
40
  end
41
+ report['payments'] << summary
48
42
  report['records'] << slip
49
43
  end
50
44
  end
51
45
  end
52
46
 
47
+ def self.year_total(year)
48
+ Profile.all do |profile|
49
+ id = profile.dig('id')
50
+ payment = { 'profile_id' => id }
51
+ # payment = {}
52
+ (1..12).each do |month|
53
+ search(year, month, nil, id).each do |origin|
54
+ # TODO: to be updated null check
55
+ if origin == {}
56
+ month
57
+ else
58
+ origin.select { |k, _v| /^[1-4][0-9A-Fa-f]{,3}$/.match(k) }.each do |k, v|
59
+ payment[k] = payment[k] ? payment[k] + v : v
60
+ end
61
+ nil
62
+ end
63
+ end
64
+ end
65
+ payment = local_convert(payment)
66
+ create(payment, date: Date.new(year, 12, 31), codes: [id], basedir: 'payments/total')
67
+ end
68
+ end
69
+
70
+ def self.local_convert(payment)
71
+ return payment if CONFIG['country'].nil?
72
+
73
+ require "luca_salary/#{CONFIG['country'].downcase}"
74
+ klass = Kernel.const_get("LucaSalary::#{CONFIG['country'].capitalize}")
75
+ klass.year_total(payment)
76
+ rescue
77
+ return payment
78
+ end
79
+
80
+ # Export json for LucaBook.
81
+ # Accrual_date can be change with `payment_term` on config.yml. Default value is 0.
82
+ # `payment_term: 1` means payment on the next month of calculation target.
83
+ #
53
84
  def export_json
54
- h = {}
55
- h[:debit] = {}
56
- h[:credit] = {}
85
+ accrual_date = if LucaSupport::CONFIG['payment_term']
86
+ pt = LucaSupport::CONFIG['payment_term']
87
+ Date.new(@date.prev_month(pt).year, @date.prev_month(pt).month, -1)
88
+ else
89
+ Date.new(@date.year, @date.month, -1)
90
+ end
91
+ h = { debit: {}, credit: {} }
57
92
  accumulate.each do |k, v|
58
93
  next if @dict.dig(k, :acct_label).nil?
59
94
 
@@ -61,11 +96,16 @@ module LucaSalary
61
96
  acct_label = @dict[k][:acct_label]
62
97
  h[pos][acct_label] = h[pos].key?(acct_label) ? h[pos][acct_label] + v : v
63
98
  end
64
- res = {}
65
- res['date'] = "#{@date.year}-#{@date.month}-#{@date.day}"
66
- res['debit'] = h[:debit].map { |k, v| { 'label' => k, 'value' => v } }
67
- res['credit'] = h[:credit].map { |k, v| { 'label' => k, 'value' => v } }
68
- puts JSON.dump(res)
99
+ [].tap do |res|
100
+ {}.tap do |item|
101
+ item['date'] = "#{accrual_date.year}-#{accrual_date.month}-#{accrual_date.day}"
102
+ item['debit'] = h[:debit].map { |k, v| { 'label' => k, 'value' => v } }
103
+ item['credit'] = h[:credit].map { |k, v| { 'label' => k, 'value' => v } }
104
+ item['x-editor'] = 'LucaSalary'
105
+ res << item
106
+ end
107
+ puts JSON.dump(readable(res))
108
+ end
69
109
  end
70
110
 
71
111
  private
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module LucaSalary
4
- VERSION = '0.1.14'
4
+ VERSION = '0.1.19'
5
5
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: lucasalary
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.1.14
4
+ version: 0.1.19
5
5
  platform: ruby
6
6
  authors:
7
7
  - Chuma Takahiro
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2020-10-07 00:00:00.000000000 Z
11
+ date: 2020-11-30 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: lucarecord
@@ -28,44 +28,44 @@ dependencies:
28
28
  name: bundler
29
29
  requirement: !ruby/object:Gem::Requirement
30
30
  requirements:
31
- - - "~>"
31
+ - - ">="
32
32
  - !ruby/object:Gem::Version
33
33
  version: '1.17'
34
34
  type: :development
35
35
  prerelease: false
36
36
  version_requirements: !ruby/object:Gem::Requirement
37
37
  requirements:
38
- - - "~>"
38
+ - - ">="
39
39
  - !ruby/object:Gem::Version
40
40
  version: '1.17'
41
41
  - !ruby/object:Gem::Dependency
42
- name: rake
42
+ name: minitest
43
43
  requirement: !ruby/object:Gem::Requirement
44
44
  requirements:
45
45
  - - "~>"
46
46
  - !ruby/object:Gem::Version
47
- version: 12.3.3
47
+ version: '5.0'
48
48
  type: :development
49
49
  prerelease: false
50
50
  version_requirements: !ruby/object:Gem::Requirement
51
51
  requirements:
52
52
  - - "~>"
53
53
  - !ruby/object:Gem::Version
54
- version: 12.3.3
54
+ version: '5.0'
55
55
  - !ruby/object:Gem::Dependency
56
- name: minitest
56
+ name: rake
57
57
  requirement: !ruby/object:Gem::Requirement
58
58
  requirements:
59
- - - "~>"
59
+ - - ">="
60
60
  - !ruby/object:Gem::Version
61
- version: '5.0'
61
+ version: 12.3.3
62
62
  type: :development
63
63
  prerelease: false
64
64
  version_requirements: !ruby/object:Gem::Requirement
65
65
  requirements:
66
- - - "~>"
66
+ - - ">="
67
67
  - !ruby/object:Gem::Version
68
- version: '5.0'
68
+ version: 12.3.3
69
69
  description: 'Salary calculation framework
70
70
 
71
71
  '