lucasalary 0.1.15 → 0.1.20
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/CHANGELOG.md +17 -0
- data/README.md +26 -1
- data/exe/luca-salary +76 -44
- data/lib/luca_salary/base.rb +4 -48
- data/lib/luca_salary/monthly.rb +25 -5
- data/lib/luca_salary/payment.rb +64 -21
- data/lib/luca_salary/version.rb +1 -1
- metadata +13 -13
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7c27c381516005d8ae6962c4d0a44ff0a351f9fd097da547f6dfdcb88d7a6b0d
|
4
|
+
data.tar.gz: 1b116229596958583c4c06b9fef3675cd53a1c7e70e04ef80fdc3733f6417b36
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 970e6cacf4d6b781831b53b714c7c4a7ec01de8c58fcc6ae59f396b52875d1024c36008276ed13caefc9a9809bfa363428e7c4884240e7f3f055434fdf76dfb4
|
7
|
+
data.tar.gz: a1658dfc041cb48eb30f7eca5c762b2f4940f0ffd1dc7d1e03fe16e25075c650d0a0a1f6752ebab387b83f01a047c720aafebf6f6b1b56d1c68b62e35a0feeca
|
data/CHANGELOG.md
CHANGED
@@ -0,0 +1,17 @@
|
|
1
|
+
## LucaSalary 0.1.20
|
2
|
+
|
3
|
+
* Implement `year_total`
|
4
|
+
* Breaking change: export key 'value' -> 'amount'
|
5
|
+
|
6
|
+
## LucaSalary 0.1.19
|
7
|
+
|
8
|
+
* Support `payment_term` on config.yml for accounting export.
|
9
|
+
|
10
|
+
## LucaSalary 0.1.18
|
11
|
+
|
12
|
+
* Add summary to payslip. Refactor monthly payment.
|
13
|
+
|
14
|
+
## LucaSalary 0.1.17
|
15
|
+
|
16
|
+
* Breaking change: restructure CLI in sub-sub command format.
|
17
|
+
* Add 'x-editor' on export to LucaBook
|
data/README.md
CHANGED
@@ -1,2 +1,27 @@
|
|
1
1
|
# Luca Salary
|
2
|
-
|
2
|
+
|
3
|
+
[](https://badge.fury.io/rb/lucasalary)
|
4
|
+
|
5
|
+
LucaSalary is Abstraction framework coworking with each country module. As income tax differs in each county, most of implementation need to be developed separately.
|
6
|
+
At this time, [Japan module](https://github.com/chumaltd/luca-salary-jp) is under development as a practical reference.
|
7
|
+
|
8
|
+
## Usage
|
9
|
+
|
10
|
+
Create blank profile of employees.
|
11
|
+
|
12
|
+
```
|
13
|
+
$ luca-salary profiles create EmployeeName
|
14
|
+
```
|
15
|
+
|
16
|
+
Profile is in YAML format. Need to describe along with each country requirement.
|
17
|
+
Once profiles completed, monthly payment records can be generated as follows:
|
18
|
+
|
19
|
+
```
|
20
|
+
$ luca-salary payments create yyyy m
|
21
|
+
```
|
22
|
+
|
23
|
+
Report is available with generated data as follows:
|
24
|
+
|
25
|
+
```
|
26
|
+
$ luca-salary payments list [--mail] yyyy m
|
27
|
+
```
|
data/exe/luca-salary
CHANGED
@@ -5,61 +5,93 @@ require 'optparse'
|
|
5
5
|
require 'luca_salary'
|
6
6
|
require 'luca_salary/monthly'
|
7
7
|
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
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
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
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
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
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
|
36
|
-
|
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
|
|
39
45
|
LucaRecord::Base.valid_project?
|
40
46
|
cmd = ARGV.shift
|
47
|
+
params = {}
|
41
48
|
|
42
49
|
case cmd
|
43
|
-
when
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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
|
54
64
|
end
|
55
|
-
when '
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
62
88
|
end
|
63
89
|
else
|
64
|
-
puts '
|
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
|
65
97
|
end
|
data/lib/luca_salary/base.rb
CHANGED
@@ -9,58 +9,18 @@ require 'luca_record'
|
|
9
9
|
|
10
10
|
module LucaSalary
|
11
11
|
class Base < LucaRecord::Base
|
12
|
-
attr_reader :
|
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(
|
55
|
+
LucaRecord::Dict.load_tsv_dict(Pathname(PJDIR) / 'dict' / 'code.tsv')
|
100
56
|
end
|
101
57
|
|
102
58
|
def set_driver
|
103
|
-
code =
|
59
|
+
code = CONFIG['country']
|
104
60
|
if code
|
105
61
|
require "luca_salary/#{code.downcase}"
|
106
|
-
Kernel.const_get "LucaSalary::#{code.
|
62
|
+
Kernel.const_get "LucaSalary::#{code.capitalise}"
|
107
63
|
else
|
108
64
|
nil
|
109
65
|
end
|
data/lib/luca_salary/monthly.rb
CHANGED
@@ -9,26 +9,46 @@ require 'luca_salary'
|
|
9
9
|
require 'luca_record'
|
10
10
|
|
11
11
|
module LucaSalary
|
12
|
-
class Monthly <
|
12
|
+
class Monthly < LucaSalary::Base
|
13
|
+
@dirname = 'payments'
|
14
|
+
|
13
15
|
def initialize(date = nil)
|
14
|
-
@date =
|
15
|
-
@pjdir = Pathname(LucaSupport::
|
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(
|
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(
|
51
|
+
puts YAML.dump(LucaSupport::Code.readable(data))
|
32
52
|
end
|
33
53
|
end
|
34
54
|
|
data/lib/luca_salary/payment.rb
CHANGED
@@ -13,44 +13,82 @@ module LucaSalary
|
|
13
13
|
|
14
14
|
def initialize(date = nil)
|
15
15
|
@date = Date.parse(date)
|
16
|
-
@pjdir = Pathname(LucaSupport::
|
16
|
+
@pjdir = Pathname(LucaSupport::PJDIR)
|
17
17
|
@dict = LucaRecord::Dict.load_tsv_dict(@pjdir / 'dict' / 'code.tsv')
|
18
18
|
end
|
19
19
|
|
20
|
-
# create record with LucaSalary::Profile instance and apyment data
|
21
|
-
#
|
22
|
-
def create(profile, payment)
|
23
|
-
id = profile.dig('id')
|
24
|
-
if self.class.search(@date.year, @date.month, @date.day, id).first
|
25
|
-
puts "payment record already exists: #{id}"
|
26
|
-
return nil
|
27
|
-
end
|
28
|
-
|
29
|
-
self.class.create_record!(payment, @date, Array(id))
|
30
|
-
end
|
31
|
-
|
32
20
|
def payslip
|
33
21
|
{}.tap do |report|
|
34
22
|
report['asof'] = "#{@date.year}/#{@date.month}"
|
23
|
+
report['payments'] = []
|
35
24
|
report['records'] = []
|
36
25
|
|
37
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
|
+
|
38
33
|
slip = {}.tap do |line|
|
34
|
+
line['name'] = profile['name']
|
39
35
|
payment.each do |k, v|
|
40
36
|
next if k == 'id'
|
41
37
|
|
42
38
|
line["#{@dict.dig(k, :label) || k}"] = v
|
43
39
|
end
|
44
40
|
end
|
41
|
+
report['payments'] << summary
|
45
42
|
report['records'] << slip
|
46
43
|
end
|
47
44
|
end
|
48
45
|
end
|
49
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
|
+
#
|
50
84
|
def export_json
|
51
|
-
|
52
|
-
|
53
|
-
|
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: {} }
|
54
92
|
accumulate.each do |k, v|
|
55
93
|
next if @dict.dig(k, :acct_label).nil?
|
56
94
|
|
@@ -58,11 +96,16 @@ module LucaSalary
|
|
58
96
|
acct_label = @dict[k][:acct_label]
|
59
97
|
h[pos][acct_label] = h[pos].key?(acct_label) ? h[pos][acct_label] + v : v
|
60
98
|
end
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
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, 'amount' => v } }
|
103
|
+
item['credit'] = h[:credit].map { |k, v| { 'label' => k, 'amount' => v } }
|
104
|
+
item['x-editor'] = 'LucaSalary'
|
105
|
+
res << item
|
106
|
+
end
|
107
|
+
puts JSON.dump(readable(res))
|
108
|
+
end
|
66
109
|
end
|
67
110
|
|
68
111
|
private
|
data/lib/luca_salary/version.rb
CHANGED
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.
|
4
|
+
version: 0.1.20
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Chuma Takahiro
|
8
8
|
autorequire:
|
9
9
|
bindir: exe
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2021-01-12 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:
|
42
|
+
name: minitest
|
43
43
|
requirement: !ruby/object:Gem::Requirement
|
44
44
|
requirements:
|
45
45
|
- - "~>"
|
46
46
|
- !ruby/object:Gem::Version
|
47
|
-
version:
|
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:
|
54
|
+
version: '5.0'
|
55
55
|
- !ruby/object:Gem::Dependency
|
56
|
-
name:
|
56
|
+
name: rake
|
57
57
|
requirement: !ruby/object:Gem::Requirement
|
58
58
|
requirements:
|
59
|
-
- - "
|
59
|
+
- - ">="
|
60
60
|
- !ruby/object:Gem::Version
|
61
|
-
version:
|
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:
|
68
|
+
version: 12.3.3
|
69
69
|
description: 'Salary calculation framework
|
70
70
|
|
71
71
|
'
|
@@ -108,7 +108,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
108
108
|
- !ruby/object:Gem::Version
|
109
109
|
version: '0'
|
110
110
|
requirements: []
|
111
|
-
rubygems_version: 3.
|
111
|
+
rubygems_version: 3.2.3
|
112
112
|
signing_key:
|
113
113
|
specification_version: 4
|
114
114
|
summary: Salary calculation framework
|