lucasalary 0.1.14 → 0.1.19

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 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
  '