peoplegroup-connectors 0.2.2 → 0.3.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/README.md +7 -4
- data/lib/peoplegroup/connectors/bamboo.rb +49 -93
- data/lib/peoplegroup/connectors/gitlab.rb +1 -1
- data/lib/peoplegroup/connectors/hris.rb +232 -33
- data/lib/peoplegroup/connectors/models/objectified_hash.rb +92 -0
- data/lib/peoplegroup/connectors/version.rb +1 -1
- data/lib/peoplegroup/connectors/workday.rb +110 -60
- data/lib/peoplegroup/connectors/xml/get_organizations.rb +84 -0
- data/lib/peoplegroup/connectors/xml/helpers.rb +72 -0
- data/lib/peoplegroup/connectors/xml/request_one_time_payment.rb +103 -0
- data/lib/peoplegroup/connectors/xml.rb +14 -0
- metadata +40 -7
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2188d8525330b4b9f74686e1b07e6debd9d488c91071194747a422e81507bd97
|
4
|
+
data.tar.gz: 5f378bc5cf32c4fec762168bc61573c8dfc423695b812377394176c1bf6a46cb
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 188a66b33ede08a2af9e4e2bfd7de4f1905347fd9d63d09e7a2bc77022ebc190da720010c5b9111b25ab6923cb2b3e685876447d2d1dd2d59e745c806ccc6b12
|
7
|
+
data.tar.gz: a48bca2c21e59036339b9ac1445e14336c162ced1b497bd1236fe5563b0cdf58aa00f202f9ea7e7f1d727107226b0b1990b6263ff11ac5921013ab36a4a19371
|
data/README.md
CHANGED
@@ -12,11 +12,15 @@ gem 'peoplegroup-connectors'
|
|
12
12
|
|
13
13
|
And then execute:
|
14
14
|
|
15
|
-
|
15
|
+
```bash
|
16
|
+
bundle install
|
17
|
+
```
|
16
18
|
|
17
19
|
Or install it yourself as:
|
18
20
|
|
19
|
-
|
21
|
+
```bash
|
22
|
+
gem install peoplegroup-connectors
|
23
|
+
```
|
20
24
|
|
21
25
|
## Usage
|
22
26
|
|
@@ -35,8 +39,7 @@ For local development and manual testing, copy the contents of the `.env.example
|
|
35
39
|
|
36
40
|
## Contributing and Code of Conduct
|
37
41
|
|
38
|
-
Bug reports and pull requests are welcome on GitLab at https://gitlab.com/gitlab-com/people-group/peopleops-eng/connectors-gem
|
39
|
-
|
42
|
+
Bug reports and pull requests are welcome on GitLab at <https://gitlab.com/gitlab-com/people-group/peopleops-eng/connectors-gem>. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the [Contributor Covenant](http://contributor-covenant.org) code of conduct.
|
40
43
|
|
41
44
|
## License
|
42
45
|
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'bamboozled'
|
4
|
+
require_rel 'models/objectified_hash'
|
4
5
|
|
5
6
|
module PeopleGroup
|
6
7
|
module Connectors
|
@@ -112,45 +113,6 @@ module PeopleGroup
|
|
112
113
|
manager
|
113
114
|
end
|
114
115
|
|
115
|
-
def people_managers_in_functions(departments: [], divisions: [])
|
116
|
-
active_and_current_team_members.select do |team_member|
|
117
|
-
(departments.include?(team_member['department']) || divisions.include?(team_member['division'])) && has_direct_reports?(team_member['employeeNumber'])
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
def all_reports_for(id)
|
122
|
-
direct_reports_to_check = direct_reports_for(id)
|
123
|
-
reports_to_return = []
|
124
|
-
while direct_reports_to_check.present?
|
125
|
-
current_node = direct_reports_to_check.shift
|
126
|
-
reports_to_return << current_node
|
127
|
-
direct_reports_to_check.concat(direct_reports_for(current_node['employeeNumber']))
|
128
|
-
end
|
129
|
-
reports_to_return
|
130
|
-
end
|
131
|
-
|
132
|
-
def direct_reports_for(id)
|
133
|
-
active_and_current_team_members.select { |team_member| team_member['supervisorId'] == id.to_s }
|
134
|
-
end
|
135
|
-
|
136
|
-
def has_direct_reports?(id)
|
137
|
-
active_and_current_team_members.any? { |team_member| team_member['supervisorId'] == id.to_s }
|
138
|
-
end
|
139
|
-
|
140
|
-
def reports_in_departments(departments, exclude_email)
|
141
|
-
active_and_current_team_members.select do |team_member|
|
142
|
-
# exclude_email is for example to filter out the PBP itself in case their department is also the department they manage
|
143
|
-
departments.include?(team_member['department']) && team_member['workEmail'] != exclude_email
|
144
|
-
end
|
145
|
-
end
|
146
|
-
|
147
|
-
def reports_in_divisions(divisions, exclude_email)
|
148
|
-
active_and_current_team_members.select do |team_member|
|
149
|
-
# exclude_email is for example to filter out the PBP itself in case their department is also the department they manage
|
150
|
-
divisions.include?(team_member['division']) && team_member['workEmail'] != exclude_email
|
151
|
-
end
|
152
|
-
end
|
153
|
-
|
154
116
|
def create_employee(employee_details_hash)
|
155
117
|
invalidate_cache
|
156
118
|
retry_on_error { @client.employee.add(employee_details_hash) }
|
@@ -164,41 +126,23 @@ module PeopleGroup
|
|
164
126
|
end
|
165
127
|
alias_method :update_team_member, :update_employee
|
166
128
|
|
167
|
-
def departments
|
168
|
-
meta_fields
|
169
|
-
.detect { |res| res['name'] == 'Department' }
|
170
|
-
.dig('options')
|
171
|
-
.each_with_object([]) { |option, array| array << option['name'] if option['archived'] == 'no' } || []
|
172
|
-
end
|
173
|
-
|
174
|
-
def divisions
|
175
|
-
meta_fields
|
176
|
-
.detect { |res| res['name'] == 'Division' }
|
177
|
-
.dig('options')
|
178
|
-
.each_with_object([]) { |option, array| array << option['name'] if option['archived'] == 'no' } || []
|
179
|
-
end
|
180
|
-
|
181
129
|
def locations
|
182
130
|
meta_fields.detect { |res| res['name'] == 'Location' }.dig('options').each_with_object([]) { |option, array| array << option['name'] if option['archived'] == 'no' } || []
|
183
131
|
end
|
184
132
|
|
185
|
-
def fields
|
186
|
-
return @fields if @fields
|
187
|
-
|
188
|
-
meta_field_aliases = retry_on_error { @client.meta.fields }.map { |f| f['alias'] }
|
189
|
-
@fields = (Bamboozled::API::FieldCollection.all_names + meta_field_aliases).compact.uniq
|
190
|
-
@fields.delete('flsaCode') # temp fix for problems with BambooHR
|
191
|
-
@fields
|
192
|
-
end
|
193
|
-
|
194
133
|
def employees
|
195
|
-
|
134
|
+
return @employees unless @employees.nil?
|
135
|
+
|
136
|
+
report = retry_on_error do
|
196
137
|
if @use_report
|
197
|
-
@
|
138
|
+
@client.report.find(@use_report, 'JSON', true)
|
198
139
|
else
|
199
|
-
@
|
140
|
+
@client.report.custom(fields, 'JSON')
|
200
141
|
end
|
201
142
|
end
|
143
|
+
|
144
|
+
filtered = report.reject { |team_member| team_member['lastName'] == 'Test-Gitlab' }
|
145
|
+
@employees = filtered.map { |team_member| format_team_member(team_member) }
|
202
146
|
end
|
203
147
|
alias_method :team_members, :employees
|
204
148
|
|
@@ -220,11 +164,6 @@ module PeopleGroup
|
|
220
164
|
retry_on_error { @client.employee.add_table_row(id, 'customEquity', data) }
|
221
165
|
end
|
222
166
|
|
223
|
-
def stock_options_data(employee_number)
|
224
|
-
id = bamboo_id!(employee_number)
|
225
|
-
retry_on_error { @client.employee.table_data(id, 'customEquity') }
|
226
|
-
end
|
227
|
-
|
228
167
|
def add_job_details(employee_number, data)
|
229
168
|
id = bamboo_id!(employee_number)
|
230
169
|
retry_on_error { @client.employee.add_table_row(id, 'jobInfo', data) }
|
@@ -247,19 +186,10 @@ module PeopleGroup
|
|
247
186
|
retry_on_error { @client.employee.table_data(id, 'employmentStatus') }
|
248
187
|
end
|
249
188
|
|
250
|
-
def time_off_types
|
251
|
-
@time_off_types ||= retry_on_error { @client.meta.time_off_types }
|
252
|
-
end
|
253
|
-
|
254
189
|
def time_off_type(name)
|
255
190
|
time_off_types['timeOffTypes'].find { |type| type['name'] == name }
|
256
191
|
end
|
257
192
|
|
258
|
-
def time_off_estimate(employee_number, end_date = Date.today)
|
259
|
-
id = bamboo_id!(employee_number)
|
260
|
-
retry_on_error { @client.employee.time_off_estimate(id, end_date) }
|
261
|
-
end
|
262
|
-
|
263
193
|
def time_off_adjustment(employee_number, options)
|
264
194
|
id = bamboo_id!(employee_number)
|
265
195
|
retry_on_error { @client.employee.time_off_balance_adjustment(id, options) }
|
@@ -355,11 +285,6 @@ module PeopleGroup
|
|
355
285
|
retry_on_error { @client.employee.table_data(id, 'employmentStatus') }
|
356
286
|
end
|
357
287
|
|
358
|
-
def currency_conversion(employee_number)
|
359
|
-
id = bamboo_id!(employee_number)
|
360
|
-
retry_on_error { @client.employee.table_data(id, 'customCurrencyConversion') }
|
361
|
-
end
|
362
|
-
|
363
288
|
def add_bonus(employee_number, comment)
|
364
289
|
team_member = search_employee_by_field(field: 'employeeNumber', value: employee_number)
|
365
290
|
data = {
|
@@ -372,21 +297,30 @@ module PeopleGroup
|
|
372
297
|
retry_on_error { @client.employee.add_table_row(team_member['id'], 'customBonus', data) }
|
373
298
|
end
|
374
299
|
|
375
|
-
def add_rating_details(team_member_id, performance, potential)
|
376
|
-
data = {
|
377
|
-
customEffectiveDate4: Date.today.to_s,
|
378
|
-
customPerformanceFactor: performance,
|
379
|
-
customPotentialFactor: potential
|
380
|
-
}
|
381
|
-
retry_on_error { @client.employee.add_table_row(team_member_id, 'customPerformanceandPotentialTable', data) }
|
382
|
-
end
|
383
|
-
|
384
300
|
private
|
385
301
|
|
302
|
+
def fields
|
303
|
+
return @fields if @fields
|
304
|
+
|
305
|
+
meta_field_aliases = retry_on_error { @client.meta.fields }.map { |f| f['alias'] }
|
306
|
+
@fields = (Bamboozled::API::FieldCollection.all_names + meta_field_aliases).compact.uniq
|
307
|
+
@fields.delete('flsaCode') # temp fix for problems with BambooHR
|
308
|
+
@fields
|
309
|
+
end
|
310
|
+
|
386
311
|
def meta_fields
|
387
312
|
@meta_fields ||= retry_on_error { @client.meta.lists }
|
388
313
|
end
|
389
314
|
|
315
|
+
def time_off_types
|
316
|
+
@time_off_types ||= retry_on_error { @client.meta.time_off_types }
|
317
|
+
end
|
318
|
+
|
319
|
+
def time_off_estimate(employee_number, end_date = Date.today)
|
320
|
+
id = bamboo_id!(employee_number)
|
321
|
+
retry_on_error { @client.employee.time_off_estimate(id, end_date) }
|
322
|
+
end
|
323
|
+
|
390
324
|
def files(employee_number)
|
391
325
|
id = bamboo_id!(employee_number)
|
392
326
|
@files ||= retry_on_error { @client.employee.files(id) }
|
@@ -399,6 +333,28 @@ module PeopleGroup
|
|
399
333
|
def invalidate_cache
|
400
334
|
@employees = nil
|
401
335
|
end
|
336
|
+
|
337
|
+
MALFORMED_FIELDS = [
|
338
|
+
'customExportName/LocationtoTeamPage?',
|
339
|
+
'customI-9Processed',
|
340
|
+
'customJobTitleSpecialty(Multi-Select)'
|
341
|
+
].freeze
|
342
|
+
|
343
|
+
# Convert BHR team member to Workday.
|
344
|
+
def format_team_member(team_member)
|
345
|
+
return nil if team_member.nil?
|
346
|
+
|
347
|
+
formatted = {
|
348
|
+
**team_member.except(*MALFORMED_FIELDS),
|
349
|
+
'customExportNameLocationtoTeamPage' => team_member['customExportName/LocationtoTeamPage?'] || 'No',
|
350
|
+
'customJobTitleSpecialtyMultiSelect' => team_member['customJobTitleSpecialty(Multi-Select)'],
|
351
|
+
'customI9Processed' => team_member['customI-9Processed']
|
352
|
+
}
|
353
|
+
|
354
|
+
formatted = PeopleGroup::Connectors::Models::ObjectifiedHash.new(formatted) unless @use_report
|
355
|
+
|
356
|
+
formatted
|
357
|
+
end
|
402
358
|
end
|
403
359
|
end
|
404
360
|
end
|
@@ -8,11 +8,26 @@ module PeopleGroup
|
|
8
8
|
|
9
9
|
def initialize
|
10
10
|
@workday = Workday.new
|
11
|
+
@bamboo = Bamboo.new
|
11
12
|
end
|
12
13
|
|
13
14
|
def employees
|
14
|
-
@workday.workers
|
15
|
+
@employees ||= @workday.workers
|
15
16
|
end
|
17
|
+
alias_method :team_members, :employees
|
18
|
+
|
19
|
+
def active_employees
|
20
|
+
employees.select { |employee| employee['status'] == 'Active' }
|
21
|
+
end
|
22
|
+
alias_method :active_team_members, :active_employees
|
23
|
+
|
24
|
+
def active_and_current_employees
|
25
|
+
today = Date.current
|
26
|
+
employees.select do |employee|
|
27
|
+
employee['status'] == 'Active' && Date.parse(employee['hireDate']) <= today
|
28
|
+
end
|
29
|
+
end
|
30
|
+
alias_method :active_and_current_team_members, :active_and_current_employees
|
16
31
|
|
17
32
|
def get_employee_details(employee_number)
|
18
33
|
employees.find { |emp| emp['employeeNumber'] == employee_number.to_s }
|
@@ -25,49 +40,233 @@ module PeopleGroup
|
|
25
40
|
|
26
41
|
employee_details
|
27
42
|
end
|
43
|
+
alias_method :get_team_member_details!, :get_employee_details!
|
28
44
|
|
29
|
-
|
45
|
+
def search_employee(name)
|
46
|
+
return if name.empty?
|
30
47
|
|
31
|
-
|
32
|
-
|
33
|
-
|
48
|
+
employees.find do |emp|
|
49
|
+
[
|
50
|
+
emp['displayName']&.downcase,
|
51
|
+
"#{emp['firstName']&.downcase} #{emp['lastName']&.downcase}",
|
52
|
+
"#{emp['preferredName']&.downcase} #{emp['lastName']&.downcase}",
|
53
|
+
"#{emp['firstName']&.downcase} #{emp['customPreferredLastName']&.downcase}",
|
54
|
+
"#{emp['preferredName']&.downcase} #{emp['customPreferredLastName']&.downcase}",
|
55
|
+
emp['fullName5']&.downcase # this is firstName middleName lastName
|
56
|
+
].include?(name.downcase)
|
57
|
+
end
|
58
|
+
end
|
59
|
+
alias_method :search_team_member, :search_employee
|
60
|
+
|
61
|
+
def search_employee!(name)
|
62
|
+
employee = search_employee(name)
|
63
|
+
raise EmployeeNotFoundError, "No employee found with name #{name}" if employee.nil?
|
34
64
|
|
35
|
-
|
36
|
-
'employeeNumber' => worker.dig(:worker_data, :worker_id),
|
37
|
-
'firstName' => worker.dig(:worker_data, :personal_data, :name_data, :legal_name_data, :name_detail_data, :first_name),
|
38
|
-
'lastName' => worker.dig(:worker_data, :personal_data, :name_data, :legal_name_data, :name_detail_data, :last_name),
|
39
|
-
'preferredName' => worker.dig(:worker_data, :personal_data, :name_data, :preferred_name_data, :name_detail_data, :first_name),
|
40
|
-
'customPreferredLastName' => worker.dig(:worker_data, :personal_data, :name_data, :preferred_name_data, :name_detail_data, :last_name),
|
41
|
-
**format_emails(worker),
|
42
|
-
'status' => worker.dig(:worker_data, :employment_data, :worker_status_data, :active) == '1' ? 'Active' : 'Inactive',
|
43
|
-
'hireDate' => worker.dig(:worker_data, :employment_data, :worker_status_data, :hire_date).to_s,
|
44
|
-
'jobTitle' => worker.dig(:worker_data, :employment_data, :worker_job_data, :position_data, :position_title)
|
45
|
-
}
|
65
|
+
employee
|
46
66
|
end
|
67
|
+
alias_method :search_team_member!, :search_employee!
|
47
68
|
|
48
|
-
def
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
}
|
69
|
+
def search_employee_by_field(field:, value:)
|
70
|
+
employees.find { |employee| employee[field] == value.to_s }
|
71
|
+
end
|
72
|
+
alias_method :search_team_member_by_field, :search_employee_by_field
|
53
73
|
|
54
|
-
|
74
|
+
def search_employee_by_field!(field:, value:)
|
75
|
+
employee = search_employee_by_field(field: field, value: value)
|
76
|
+
raise EmployeeNotFoundError, "No employee found with #{field}: #{value}" if employee.nil?
|
55
77
|
|
56
|
-
|
78
|
+
employee
|
79
|
+
end
|
80
|
+
alias_method :search_team_member_by_field!, :search_employee_by_field!
|
57
81
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
email_addresses = [email_addresses] unless email_addresses.is_a?(Array)
|
62
|
-
# rubocop:enable Style/ArrayCoercion
|
82
|
+
# Find the associated team member without checking case on their e-mail fields.
|
83
|
+
def search_team_member_by_email(email)
|
84
|
+
return nil unless email
|
63
85
|
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
formatted_emails['homeEmail'] = email_address_data[:email_address] if email_type == 'HOME'
|
86
|
+
team_members.find do |team_member|
|
87
|
+
emails = [team_member['workEmail'], team_member['bestEmail'], team_member['homeEmail']]
|
88
|
+
emails.compact.any? { |match| email.casecmp(match) === 0 }
|
68
89
|
end
|
90
|
+
end
|
91
|
+
alias_method :slack_email_lookup_with_fallback, :search_team_member_by_email
|
92
|
+
|
93
|
+
# Find the team member by email and raise error if not found.
|
94
|
+
def search_team_member_by_email!(email)
|
95
|
+
team_member = search_team_member_by_email(email)
|
96
|
+
|
97
|
+
return team_member if team_member
|
98
|
+
|
99
|
+
raise EmployeeNotFoundError, "Could not find team member with the e-mail: #{email}"
|
100
|
+
end
|
101
|
+
alias_method :slack_email_lookup_with_fallback!, :search_team_member_by_email!
|
102
|
+
|
103
|
+
def team_members_by_department(department)
|
104
|
+
active_and_current_team_members.select { |team_member| team_member['department'] == department }
|
105
|
+
end
|
106
|
+
|
107
|
+
def team_members_by_division(division)
|
108
|
+
active_and_current_team_members.select { |team_member| team_member['division'] == division }
|
109
|
+
end
|
110
|
+
|
111
|
+
def fetch_manager(team_member)
|
112
|
+
active_team_members.find { |tm| tm['employeeNumber'] == team_member['supervisorId'] }
|
113
|
+
end
|
114
|
+
|
115
|
+
def fetch_manager!(team_member)
|
116
|
+
manager = fetch_manager(team_member)
|
117
|
+
raise EmployeeNotFoundError, "Manager not found for employee #{team_member['employeeNumber']}" if manager.nil?
|
118
|
+
|
119
|
+
manager
|
120
|
+
end
|
121
|
+
|
122
|
+
def fetch_second_level_manager(team_member)
|
123
|
+
fetch_manager(fetch_manager(team_member))
|
124
|
+
end
|
125
|
+
|
126
|
+
def fetch_second_level_manager!(team_member)
|
127
|
+
manager = fetch_second_level_manager(team_member)
|
128
|
+
raise EmployeeNotFoundError, "No second level manager found for employee #{team_member['employeeNumber']}" if manager.nil?
|
129
|
+
|
130
|
+
manager
|
131
|
+
end
|
132
|
+
|
133
|
+
def create_employee(employee_details_hash)
|
134
|
+
@bamboo.create_employee(employee_details_hash)
|
135
|
+
end
|
136
|
+
alias_method :create_team_member, :create_employee
|
137
|
+
|
138
|
+
def update_employee(employee_number, employee_details_hash)
|
139
|
+
# Only used in activate_team_members.rb, which is not needed in Workday
|
140
|
+
@bamboo.update_employee(employee_number, employee_details_hash)
|
141
|
+
end
|
142
|
+
alias_method :update_team_member, :update_employee
|
143
|
+
|
144
|
+
def locations
|
145
|
+
@workday.locations
|
146
|
+
end
|
147
|
+
|
148
|
+
def add_stock_options(employee_number, data)
|
149
|
+
@bamboo.add_stock_options(employee_number, data)
|
150
|
+
end
|
151
|
+
|
152
|
+
def add_job_details(employee_number, data)
|
153
|
+
@bamboo.add_job_details(employee_number, data)
|
154
|
+
end
|
155
|
+
|
156
|
+
def update_job_details(employee_number, data)
|
157
|
+
@bamboo.update_job_details(employee_number, data)
|
158
|
+
end
|
159
|
+
|
160
|
+
def add_compensation_details(employee_number, data)
|
161
|
+
@bamboo.add_compensation_details(employee_number, data)
|
162
|
+
end
|
163
|
+
|
164
|
+
def employment_statuses(employee_number)
|
165
|
+
# Terminated/Active in one field already. Those are the statuses we need to care about
|
166
|
+
# Look for a hire date that is after the start date (not original hire date) to account for rehires
|
167
|
+
# Affected: joining.rb
|
168
|
+
|
169
|
+
# Probation period, CXC contract ending, temporary contract ending won't be needed
|
170
|
+
# We can ignore garden leave (terminate access to systems without pausing benefits/stock vesting)
|
171
|
+
# Affected:
|
172
|
+
# - anniversary.rb
|
173
|
+
# - cxc_contract_renewal_email.rb
|
174
|
+
# - extension_netherlands_contract_email.rb
|
175
|
+
# - probation_email.rb
|
176
|
+
# - probation_status.rb
|
177
|
+
|
178
|
+
raise NotImplementedError
|
179
|
+
end
|
180
|
+
|
181
|
+
def time_off_type(name)
|
182
|
+
# Staying in bamboo for workday first release
|
183
|
+
# Will need to have talks with pto by roots admin
|
184
|
+
@bamboo.time_off_type(name)
|
185
|
+
end
|
186
|
+
|
187
|
+
def time_off_adjustment(employee_number, options)
|
188
|
+
# Staying in bamboo for workday first release
|
189
|
+
# Will need to have talks with pto by roots admin
|
190
|
+
@bamboo.time_off_adjustment(employee_number, options)
|
191
|
+
end
|
192
|
+
|
193
|
+
def accrued_days(employee_number)
|
194
|
+
# Staying in bamboo for workday first release
|
195
|
+
# Will need to have talks with pto by roots admin
|
196
|
+
@bamboo.accrued_days(employee_number)
|
197
|
+
end
|
198
|
+
|
199
|
+
def time_off_policies
|
200
|
+
@bamboo.time_off_policies
|
201
|
+
end
|
202
|
+
|
203
|
+
def employee_time_off_policies(employee_number)
|
204
|
+
@bamboo.employee_time_off_policies(employee_number)
|
205
|
+
end
|
206
|
+
alias_method :team_member_time_off_policies, :employee_time_off_policies
|
207
|
+
|
208
|
+
def add_time_off_policy(employee_number, time_off_policy_id, accrual_start_date)
|
209
|
+
@bamboo.add_time_off_policy(employee_number, time_off_policy_id, accrual_start_date)
|
210
|
+
end
|
211
|
+
|
212
|
+
def remove_time_off_policy(employee_number, time_off_policy_id)
|
213
|
+
@bamboo.remove_time_off_policy(employee_number, time_off_policy_id)
|
214
|
+
end
|
215
|
+
|
216
|
+
def job_details(employee_number)
|
217
|
+
@workday.manager_history(employee_number)
|
218
|
+
end
|
219
|
+
|
220
|
+
def resumes_folder_id(employee_number)
|
221
|
+
@bamboo.resumes_folder_id(employee_number)
|
222
|
+
end
|
223
|
+
|
224
|
+
def contract_folder_id(employee_number)
|
225
|
+
@bamboo.contract_folder_id(employee_number)
|
226
|
+
end
|
227
|
+
|
228
|
+
def add_file(employee_number, file_name, file, folder_id)
|
229
|
+
@bamboo.add_file(employee_number, file_name, file, folder_id)
|
230
|
+
end
|
231
|
+
|
232
|
+
def add_employment_status(employee_number, data)
|
233
|
+
# Only used in parental_pto_to_bamboo.rb
|
234
|
+
# That integration adds the following statuses: Parental Leave, End of Parental Leave, Active
|
235
|
+
# Asked what to do about this one on Slack: https://gitlab.slack.com/archives/C02FHJ9BYTZ/p1650666853127429
|
236
|
+
|
237
|
+
raise NotImplementedError
|
238
|
+
end
|
239
|
+
|
240
|
+
def add_currency_conversion(employee_number, data)
|
241
|
+
@bamboo.add_currency_conversion(employee_number, data)
|
242
|
+
end
|
243
|
+
|
244
|
+
def add_on_target_earnings(employee_number, data)
|
245
|
+
@bamboo.add_on_target_earnings(employee_number, data)
|
246
|
+
end
|
247
|
+
|
248
|
+
def add_signing_bonus(employee_number, data)
|
249
|
+
@bamboo.add_signing_bonus(employee_number, data)
|
250
|
+
end
|
251
|
+
|
252
|
+
def add_family_member(employee_number, data)
|
253
|
+
@bamboo.add_family_member(employee_number, data)
|
254
|
+
end
|
255
|
+
|
256
|
+
def add_additional_data(employee_number, data)
|
257
|
+
@bamboo.add_additional_data(employee_number, data)
|
258
|
+
end
|
259
|
+
|
260
|
+
def additional_data(employee_number)
|
261
|
+
@bamboo.additional_data(employee_number)
|
262
|
+
end
|
263
|
+
|
264
|
+
def add_bonus(employee_number, comment)
|
265
|
+
@workday.add_bonus(employee_number, comment)
|
266
|
+
end
|
69
267
|
|
70
|
-
|
268
|
+
def departments
|
269
|
+
@workday.departments
|
71
270
|
end
|
72
271
|
end
|
73
272
|
end
|
@@ -0,0 +1,92 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'json'
|
4
|
+
|
5
|
+
module PeopleGroup
|
6
|
+
module Connectors
|
7
|
+
module Models
|
8
|
+
# Turns a hash into a Ruby object.
|
9
|
+
# This will assign the key of the hash as an instance method, and the value as the return value.
|
10
|
+
# This will also convert any nested hashes into another ObjectifiedHash.
|
11
|
+
class ObjectifiedHash
|
12
|
+
# The raw hash prior to objectification.
|
13
|
+
attr_reader :hash
|
14
|
+
|
15
|
+
# The data with any objectified children.
|
16
|
+
attr_reader :data
|
17
|
+
|
18
|
+
def initialize(hash)
|
19
|
+
@hash = hash.transform_keys(&:to_sym)
|
20
|
+
@data = hash.transform_keys(&:to_s)
|
21
|
+
hash.each do |key, value|
|
22
|
+
if value.is_a?(Hash) && value.size.positive?
|
23
|
+
value = self.class.new(value)
|
24
|
+
elsif value.is_a?(Array) && value.first.is_a?(Hash)
|
25
|
+
value = value.map { |data| self.class.new(data) }
|
26
|
+
end
|
27
|
+
|
28
|
+
instance_variable_set("@#{key}", value)
|
29
|
+
self.class.send(:attr_reader, key) # rubocop:disable GitlabSecurity/PublicSend
|
30
|
+
@data[key] = value
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
# Mimicks the Hash#dig method.
|
35
|
+
# @param [Array] Nested keys of the object.
|
36
|
+
# @return The value of the key at the nested location.
|
37
|
+
def dig(*args)
|
38
|
+
@data.dig(*args)
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create a new instance from an array of Hashes
|
42
|
+
def self.from_array(array)
|
43
|
+
array.map { |data| new(data) if data.is_a?(Hash) }
|
44
|
+
end
|
45
|
+
|
46
|
+
# Maintains normal json data structure
|
47
|
+
def [](key)
|
48
|
+
@data[key.to_s]
|
49
|
+
end
|
50
|
+
|
51
|
+
# Allows us to override the json data
|
52
|
+
def []=(key, value)
|
53
|
+
@data[key.to_s] = value
|
54
|
+
end
|
55
|
+
|
56
|
+
# Return the stored raw hash value.
|
57
|
+
def to_h
|
58
|
+
@hash
|
59
|
+
end
|
60
|
+
|
61
|
+
# Returns the hash with keys converted to strings.
|
62
|
+
def to_json # rubocop:disable Lint/ToJSON
|
63
|
+
JSON.parse(hash.to_json)
|
64
|
+
end
|
65
|
+
|
66
|
+
# Returns both the hash and json styled keys.
|
67
|
+
def keys
|
68
|
+
@keys ||= @hash.keys + @data.keys
|
69
|
+
end
|
70
|
+
|
71
|
+
private
|
72
|
+
|
73
|
+
# For handling Object.respond_to?(:method)
|
74
|
+
def respond_to_missing?(method, include_private = false)
|
75
|
+
super
|
76
|
+
end
|
77
|
+
|
78
|
+
# Enforces read only access as well as mimicking the return value of a hash.
|
79
|
+
# @param method [Symbol] The method to be called.
|
80
|
+
# @param args [Array] The arguments attempted to be passed into the method.
|
81
|
+
# @raises [NoMethodError] if the method is attempting to set data on the object.
|
82
|
+
# @return nil
|
83
|
+
def method_missing(method, *args)
|
84
|
+
return raise NoMethodError, "This object is read-only." if method.end_with?('=')
|
85
|
+
return nil if method.start_with?('[')
|
86
|
+
|
87
|
+
raise NoMethodError, "Undefined method `#{method}` called for #{inspect}"
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
end
|
@@ -1,87 +1,110 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'savon'
|
4
|
+
require_rel 'xml'
|
4
5
|
|
5
6
|
module PeopleGroup
|
6
7
|
module Connectors
|
7
8
|
class Workday
|
8
|
-
|
9
|
+
include XML
|
10
|
+
attr_accessor :staffing_client, :compensation_client, :subdomain, :tenant
|
9
11
|
|
12
|
+
# Current Workday API Version
|
10
13
|
API_VERSION = 'v37.0'
|
11
|
-
MAX_RESULTS_PER_PAGE = 999
|
12
14
|
|
13
15
|
EmployeeNotFoundError = Class.new(StandardError)
|
14
16
|
|
15
17
|
def initialize
|
16
18
|
@subdomain = ENV['WORKDAY_SUBDOMAIN']
|
17
19
|
@tenant = ENV['WORKDAY_TENANT']
|
18
|
-
|
19
|
-
|
20
|
-
|
20
|
+
@api_base_path = "https://#{@subdomain}.workday.com/ccx/service"
|
21
|
+
@isu_username = ENV['WORKDAY_ISU_USERNAME']
|
22
|
+
@isu_password = ENV['WORKDAY_ISU_PASSWORD']
|
21
23
|
|
22
|
-
|
23
|
-
|
24
|
+
soap_debug_logs_enabled = ENV['WORKDAY_SOAP_DEBUG_LOGS'] == 'true'
|
25
|
+
options = {
|
24
26
|
log: soap_debug_logs_enabled,
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
namespaces: {
|
31
|
-
'xmlns' => nil,
|
32
|
-
'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/',
|
33
|
-
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
|
34
|
-
'xmlns:wsse' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'
|
35
|
-
}
|
36
|
-
)
|
27
|
+
pretty_print_xml: soap_debug_logs_enabled
|
28
|
+
}
|
29
|
+
|
30
|
+
@staffing_client = client('Staffing', options)
|
31
|
+
@compensation_client = client('Compensation', options)
|
37
32
|
end
|
38
33
|
|
39
34
|
def workers
|
40
|
-
|
41
|
-
@workers ||= auto_paginated_call(:get_workers, { message_tag: 'wd:Get_Workers_Request', attributes: attributes })
|
42
|
-
end
|
35
|
+
@workers ||= safe_report(report_name: ENV['WORKDAY_WORKERS_REPORT'])
|
43
36
|
end
|
44
37
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
attributes!: {
|
58
|
-
'wd:Request_References' => {
|
59
|
-
'wd:Skip_Non_Existing_Instances' => 'true',
|
60
|
-
'wd:Ignore_Invalid_References' => 'true'
|
61
|
-
}
|
62
|
-
}
|
63
|
-
}
|
64
|
-
|
65
|
-
retry_on_error do
|
66
|
-
auto_paginated_call(:get_workers, { message_tag: 'wd:Get_Workers_Request', message: message, attributes: attributes })
|
38
|
+
# Grabs manager history for the specified team member or all team members.
|
39
|
+
# @return [Array<PeopleGroup::Connectors::Models::ObjectifiedHash>]
|
40
|
+
# keys:
|
41
|
+
# Reports_to - Employee_ID of who they were reporting too at this time.
|
42
|
+
# Managed_From_Date - When they became the team members manager.
|
43
|
+
# Managed_To_Date - The date they were no longer the team members manager. This field is missing if they are the current manager.
|
44
|
+
def manager_history(employee_number = nil)
|
45
|
+
options = { report_name: ENV['WORKDAY_MANAGER_REPORT'] }
|
46
|
+
|
47
|
+
if employee_number
|
48
|
+
options[:params] = { 'Employee_ID' => employee_number } unless employee_number.nil?
|
49
|
+
options[:nested_key] = 'Manager_Change_group'
|
67
50
|
end
|
51
|
+
|
52
|
+
safe_report(**options)
|
68
53
|
end
|
69
54
|
|
70
55
|
private
|
71
56
|
|
57
|
+
# A generalized report method for Workday.
|
58
|
+
# @param! [String] report_name the name of the report.
|
59
|
+
# @param [Hash] params key value parameters for the report.
|
60
|
+
# @return [Array<PeopleGroup::Models::ObjectifiedHash>]
|
61
|
+
def report(report_name:, params: {}, nested_key: nil)
|
62
|
+
url = report_url(report_name, params)
|
63
|
+
p "Accessing Report: #{url}" if ENV['WORKDAY_SOAP_DEBUG_LOGS']
|
64
|
+
response = retry_on_error { RestClient.get(url, basic_auth_header) }
|
65
|
+
data = JSON.parse(response.body)['Report_Entry']
|
66
|
+
data = data.first[nested_key] if nested_key.is_a?(String) && data.first[nested_key]
|
67
|
+
data.map { |obj| XML::ObjectifiedHash.new obj }
|
68
|
+
end
|
69
|
+
|
70
|
+
# A wrapper for the report logic to handle any Workday specific errors.
|
71
|
+
# @param [Hash] args
|
72
|
+
def safe_report(**args)
|
73
|
+
report(**args)
|
74
|
+
rescue StandardError => e
|
75
|
+
# TODO add common Workday errors here.
|
76
|
+
raise e
|
77
|
+
end
|
78
|
+
|
79
|
+
def report_url(report_name, params)
|
80
|
+
options = URI.encode_www_form({ format: 'json', **params })
|
81
|
+
name = report_name.tr(' ', '_')
|
82
|
+
"#{@api_base_path}/customreport2/#{@tenant}/#{@isu_username}/#{name}?#{options}"
|
83
|
+
end
|
84
|
+
|
72
85
|
def retry_on_error(&block)
|
73
86
|
Utils.retry_on_error(errors: [Net::ReadTimeout], delay: 3, &block)
|
74
87
|
end
|
75
88
|
|
76
|
-
def
|
89
|
+
def client_from_operation(operation_name)
|
90
|
+
return @staffing_client if @staffing_client.operations.include?(operation_name)
|
91
|
+
|
92
|
+
@compensation_client
|
93
|
+
end
|
94
|
+
|
95
|
+
def call(operation_name, options = {}, &block)
|
96
|
+
client = client_from_operation(operation_name)
|
97
|
+
response = client.call(operation_name, { **options, **attributes }, &block)
|
98
|
+
{ **response.body, code: response.http.code }
|
99
|
+
end
|
100
|
+
|
101
|
+
def auto_paginated_call(operation_name, options = {}, &block)
|
102
|
+
client = client_from_operation(operation_name)
|
77
103
|
data = nil
|
78
104
|
page = 1
|
79
105
|
|
80
106
|
loop do
|
81
|
-
|
82
|
-
message_with_pagination_parameters = { **message, **pagination_parameters(page) }
|
83
|
-
response = @client.call(operation_name, { **locals, message: message_with_pagination_parameters }, &block)
|
84
|
-
|
107
|
+
response = client.call(operation_name, { **options, **attributes }, &block)
|
85
108
|
# The response hash contains only one key named after the operation performed
|
86
109
|
# eg. get_workers_response
|
87
110
|
_operation_response_name, operation_result = response.to_hash.first
|
@@ -107,20 +130,47 @@ module PeopleGroup
|
|
107
130
|
data
|
108
131
|
end
|
109
132
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
}
|
116
|
-
}
|
133
|
+
# Default Workday specific body paramaters
|
134
|
+
# xmlns:wd - The URN of the XMLNS file
|
135
|
+
# wd:version - The API Version of workday
|
136
|
+
def attributes
|
137
|
+
{ attributes: { 'xmlns:wd' => 'urn:com.workday/bsvc', 'wd:version' => API_VERSION } }
|
117
138
|
end
|
118
139
|
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
140
|
+
# Returns the client based on the service.
|
141
|
+
# @param Service [String] The service to use, ie: Staffing, Compensation
|
142
|
+
# @param options [Hash] default options for the client.
|
143
|
+
#
|
144
|
+
# @return [Savon::Client] The client connected to the specified service.
|
145
|
+
def client(service, options = {})
|
146
|
+
client_opts = {
|
147
|
+
log: false,
|
148
|
+
wsse_auth: [
|
149
|
+
"#{@isu_username}@#{@tenant}",
|
150
|
+
@isu_password
|
151
|
+
],
|
152
|
+
pretty_print_xml: false,
|
153
|
+
convert_request_keys_to: :camelcase,
|
154
|
+
env_namespace: :env,
|
155
|
+
namespace_identifier: :wd,
|
156
|
+
wsdl: "#{@api_base_path}/#{@tenant}/#{service}/#{API_VERSION}?wsdl",
|
157
|
+
namespaces: {
|
158
|
+
'xmlns' => nil,
|
159
|
+
'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/',
|
160
|
+
'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
|
161
|
+
'xmlns:wsse' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'
|
162
|
+
},
|
163
|
+
**options
|
123
164
|
}
|
165
|
+
|
166
|
+
Savon.client(**client_opts)
|
167
|
+
end
|
168
|
+
|
169
|
+
def basic_auth_header
|
170
|
+
return @basic_auth_header unless @basic_auth_header.nil?
|
171
|
+
|
172
|
+
base_64 = Base64.strict_encode64("#{@isu_username}:#{@isu_password}")
|
173
|
+
@basic_auth_header = { Authorization: "Basic #{base_64}" }
|
124
174
|
end
|
125
175
|
end
|
126
176
|
end
|
@@ -0,0 +1,84 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_rel 'helpers'
|
4
|
+
|
5
|
+
module PeopleGroup
|
6
|
+
module Connectors
|
7
|
+
module XML
|
8
|
+
# https://community.workday.com/sites/default/files/file-hosting/productionapi/Staffing/v37.2/Get_Organizations.html
|
9
|
+
module GetOrganizations
|
10
|
+
include Helpers
|
11
|
+
|
12
|
+
# The Operation for this Module
|
13
|
+
OPERATION = :get_organizations
|
14
|
+
|
15
|
+
# List all of the active departments.
|
16
|
+
def departments
|
17
|
+
get('Cost_Center')
|
18
|
+
end
|
19
|
+
|
20
|
+
# List all of the active regions.
|
21
|
+
def regions
|
22
|
+
get('Region')
|
23
|
+
end
|
24
|
+
|
25
|
+
# List all of the active entities.
|
26
|
+
def locations
|
27
|
+
@locations ||= get('Company').map(&:reference_id)
|
28
|
+
end
|
29
|
+
|
30
|
+
# List all of the active pay groups.
|
31
|
+
def pay_groups
|
32
|
+
get('Pay_Group')
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
|
37
|
+
# Call the specified endpoint.
|
38
|
+
# @param type [String] The type of organization to request.
|
39
|
+
# @return [Array<PeopleGroup::Connectors::Models::ObjectifiedHash>] An array of the requested organzation types resources.
|
40
|
+
def get(type, additional = {})
|
41
|
+
options = {
|
42
|
+
**org_request_criteria_by_type(type),
|
43
|
+
**Helpers.default_response_group,
|
44
|
+
**Helpers.pagination_parameters,
|
45
|
+
**additional
|
46
|
+
}
|
47
|
+
|
48
|
+
request = Helpers.construct_options(OPERATION, options)
|
49
|
+
auto_paginated_call(OPERATION, request).map do |data|
|
50
|
+
ObjectifiedHash.new data[:organization_data]
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
54
|
+
# Get the Request_Criteria via type
|
55
|
+
# @param type [String] Company, Cost Center, Pay Group, Region
|
56
|
+
#
|
57
|
+
# @return [Request_Criteria] The Organiztion_Request_Criteria for the specified type.
|
58
|
+
def org_request_criteria_by_type(type)
|
59
|
+
org_criteria = organization_request_criteria(type: type)
|
60
|
+
Helpers.request_criteria(org_criteria)
|
61
|
+
end
|
62
|
+
|
63
|
+
# Get the Organization_Request_Criteria as a hash.
|
64
|
+
# @param type [String] The type of Organization to perform the search on.
|
65
|
+
# @param include_inactive [boolean] Whether to include inactive organizations, defaults to false.
|
66
|
+
# @param field_and_param_data [Hash] A Hash containing the key value pair of Field and Parameter Criteria Data.
|
67
|
+
def organization_request_criteria(type:, include_inactive: false, field_and_param_data: nil)
|
68
|
+
hash = {
|
69
|
+
'Organization_Type_Reference' => {
|
70
|
+
'ID' => type,
|
71
|
+
:attributes! => { 'ID' => { 'wd:type' => 'Organization_Type_ID' } }
|
72
|
+
},
|
73
|
+
'Include_Inactive' => include_inactive
|
74
|
+
}
|
75
|
+
|
76
|
+
# Only add parameter criteria if present.
|
77
|
+
hash['Field_And_Parameter_Criteria_Data'] = field_and_param_data unless field_and_param_data.nil?
|
78
|
+
|
79
|
+
hash
|
80
|
+
end
|
81
|
+
end
|
82
|
+
end
|
83
|
+
end
|
84
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module PeopleGroup
|
4
|
+
module Connectors
|
5
|
+
module XML
|
6
|
+
module Helpers
|
7
|
+
MAX_RESULTS_PER_PAGE = 999
|
8
|
+
|
9
|
+
def self.request_references(refs = {})
|
10
|
+
{
|
11
|
+
'Request_References' => refs
|
12
|
+
}
|
13
|
+
end
|
14
|
+
|
15
|
+
def self.response_filter(filters = {})
|
16
|
+
{
|
17
|
+
'Response_Filter' => filters
|
18
|
+
}
|
19
|
+
end
|
20
|
+
|
21
|
+
# Default Request Criteria XML Wrapper
|
22
|
+
# @param criteria [Hash] A list of key value pair XML attributes.
|
23
|
+
#
|
24
|
+
# @return [Hash] - The <wd:Request_Criteria> XML Component containing a set of options.
|
25
|
+
def self.request_criteria(criteria = {})
|
26
|
+
{
|
27
|
+
'Request_Criteria' => criteria
|
28
|
+
}
|
29
|
+
end
|
30
|
+
|
31
|
+
def self.response_group(group = {})
|
32
|
+
{ 'Response_Group' => group }
|
33
|
+
end
|
34
|
+
|
35
|
+
def self.default_response_group(additional = {})
|
36
|
+
groups = { 'Include_Hierarchy_Data' => false, **additional }
|
37
|
+
response_group groups
|
38
|
+
end
|
39
|
+
|
40
|
+
# Contructs a hash that we can pass into the #call Savon client method.
|
41
|
+
def self.construct_options(operation, message)
|
42
|
+
operation = operation.to_s.split('_').collect(&:capitalize).join('_') unless operation.is_a?(String)
|
43
|
+
{
|
44
|
+
message_tag: "#{operation}_Request",
|
45
|
+
message: message
|
46
|
+
}
|
47
|
+
end
|
48
|
+
|
49
|
+
def self.pagination_parameters(page = 1, results_per_page = MAX_RESULTS_PER_PAGE)
|
50
|
+
response_filter(
|
51
|
+
{
|
52
|
+
'Page' => page,
|
53
|
+
'Count' => results_per_page
|
54
|
+
}
|
55
|
+
)
|
56
|
+
end
|
57
|
+
|
58
|
+
# The <wd:Employee_Reference> object wrapper.
|
59
|
+
# @param id [Integer] The employee number of the team member.
|
60
|
+
# @return [Hash] The <wd:Employee_Reference> XML wrapper component.
|
61
|
+
def self.team_member_reference(id)
|
62
|
+
{
|
63
|
+
'Employee_Reference' => {
|
64
|
+
'ID' => id,
|
65
|
+
attributes!: { 'ID' => { 'wd:type' => 'Employee_ID' } }
|
66
|
+
}
|
67
|
+
}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,103 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_rel 'helpers'
|
4
|
+
|
5
|
+
module PeopleGroup
|
6
|
+
module Connectors
|
7
|
+
module XML
|
8
|
+
# https://community.workday.com/sites/default/files/file-hosting/productionapi/Compensation/v37.0/Request_One-Time_Payment.html
|
9
|
+
module RequestOneTimePayment
|
10
|
+
include Helpers
|
11
|
+
|
12
|
+
# The Operation for this Module
|
13
|
+
OPERATION = :request_one_time_payment
|
14
|
+
OPERATION_AS_PARAM = 'Request_One-Time_Payment'
|
15
|
+
|
16
|
+
# The amount in USD to give for bonuses, must be a decimal.
|
17
|
+
DISCRETIONARY_BONUS = 1000.0
|
18
|
+
|
19
|
+
# Adds a bonus to the team members
|
20
|
+
# @param employee_number [Integer] The Employee Number of the team member receiving the bonus.
|
21
|
+
# @param comment [String] The comment to leave on the business process.
|
22
|
+
def add_bonus(employee_number, comment)
|
23
|
+
options = {
|
24
|
+
**business_process_params(comment),
|
25
|
+
**one_time_payment_data(employee_number, comment)
|
26
|
+
}
|
27
|
+
|
28
|
+
request = Helpers.construct_options(OPERATION_AS_PARAM, options)
|
29
|
+
call(OPERATION, request)
|
30
|
+
end
|
31
|
+
|
32
|
+
private
|
33
|
+
|
34
|
+
# https://community.workday.com/sites/default/files/file-hosting/productionapi/Compensation/v37.0/Request_One-Time_Payment.html#Business_Process_ParametersType
|
35
|
+
# @param comment [String] The comment associated with the bonus.
|
36
|
+
# @param run_now: [Boolean] Defaults to true to process this transaction immediatley.
|
37
|
+
# @param auto_complete: [Boolean] Defaults to true to apply this bonus without needing approval.
|
38
|
+
#
|
39
|
+
# @return [Hash] The <wd:Business_Process_Parameters> for this request.
|
40
|
+
def business_process_params(comment, run_now: true, auto_complete: true)
|
41
|
+
{
|
42
|
+
'Business_Process_Parameters' => {
|
43
|
+
'Auto_Complete' => auto_complete,
|
44
|
+
'Run_Now' => run_now,
|
45
|
+
|
46
|
+
# Comment Data
|
47
|
+
# https://community.workday.com/sites/default/files/file-hosting/productionapi/Compensation/v37.0/Request_One-Time_Payment.html#Business_Process_Comment_DataType
|
48
|
+
'Comment_Data' => {
|
49
|
+
'Comment' => comment
|
50
|
+
}
|
51
|
+
}
|
52
|
+
}
|
53
|
+
end
|
54
|
+
|
55
|
+
# The Effective Date parameter for when to apply the payment, defaults to today.
|
56
|
+
# @param date [Date] The day to apply the payment.
|
57
|
+
# @return [Hash] The <wd:Effective_Date> parameter set to the specified date.
|
58
|
+
def effective_date(date = Date.today)
|
59
|
+
{
|
60
|
+
'Effective_Date' => date.to_s
|
61
|
+
}
|
62
|
+
end
|
63
|
+
|
64
|
+
# The payment specific data for what should be applied.
|
65
|
+
# @param comment [String] The comment for the discretionary bonus.
|
66
|
+
# @param amount [Double] The amount to include in the bonus with decimal accuracy.
|
67
|
+
def one_time_payment_sub_data(comment, amount = DISCRETIONARY_BONUS)
|
68
|
+
{
|
69
|
+
'One-Time_Payment_Sub_Data' => {
|
70
|
+
**payment_plan_reference,
|
71
|
+
'Amount' => amount,
|
72
|
+
'Comment' => comment
|
73
|
+
}
|
74
|
+
}
|
75
|
+
end
|
76
|
+
|
77
|
+
# The type of one time payment we want to group this under.
|
78
|
+
def payment_plan_reference
|
79
|
+
{
|
80
|
+
'One_Time_Payment_Plan_Reference' => {
|
81
|
+
'ID' => 'Discretionary Bonus',
|
82
|
+
attributes!: { 'ID' => { 'wd:type' => 'One-Time_Payment_Plan_ID' } }
|
83
|
+
}
|
84
|
+
}
|
85
|
+
end
|
86
|
+
|
87
|
+
# https://community.workday.com/sites/default/files/file-hosting/productionapi/Compensation/v37.0/Request_One-Time_Payment.html#Request_One-Time_Payment_DataType
|
88
|
+
# @param employee_number [Integer] The team member's employee number.
|
89
|
+
# @param comment [String] A comment describing why the payment is being requested.
|
90
|
+
# @return [Hash] The <wd:One-Time_Payment_Data> component.
|
91
|
+
def one_time_payment_data(employee_number, comment)
|
92
|
+
{
|
93
|
+
'One-Time_Payment_Data' => {
|
94
|
+
**Helpers.team_member_reference(employee_number),
|
95
|
+
**effective_date,
|
96
|
+
**one_time_payment_sub_data(comment)
|
97
|
+
}
|
98
|
+
}
|
99
|
+
end
|
100
|
+
end
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_rel 'xml'
|
4
|
+
require_rel 'models/objectified_hash'
|
5
|
+
|
6
|
+
module PeopleGroup::Connectors
|
7
|
+
module XML
|
8
|
+
include GetOrganizations
|
9
|
+
include RequestOneTimePayment
|
10
|
+
|
11
|
+
# Rename Objectified hash for ease of use.
|
12
|
+
ObjectifiedHash = PeopleGroup::Connectors::Models::ObjectifiedHash
|
13
|
+
end
|
14
|
+
end
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: peoplegroup-connectors
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- lien van den steen
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-06-08 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: gitlab
|
@@ -94,6 +94,20 @@ dependencies:
|
|
94
94
|
- - "~>"
|
95
95
|
- !ruby/object:Gem::Version
|
96
96
|
version: '2.5'
|
97
|
+
- !ruby/object:Gem::Dependency
|
98
|
+
name: rest-client
|
99
|
+
requirement: !ruby/object:Gem::Requirement
|
100
|
+
requirements:
|
101
|
+
- - "~>"
|
102
|
+
- !ruby/object:Gem::Version
|
103
|
+
version: '2.1'
|
104
|
+
type: :runtime
|
105
|
+
prerelease: false
|
106
|
+
version_requirements: !ruby/object:Gem::Requirement
|
107
|
+
requirements:
|
108
|
+
- - "~>"
|
109
|
+
- !ruby/object:Gem::Version
|
110
|
+
version: '2.1'
|
97
111
|
- !ruby/object:Gem::Dependency
|
98
112
|
name: savon
|
99
113
|
requirement: !ruby/object:Gem::Requirement
|
@@ -234,6 +248,20 @@ dependencies:
|
|
234
248
|
- - "~>"
|
235
249
|
- !ruby/object:Gem::Version
|
236
250
|
version: '0.21'
|
251
|
+
- !ruby/object:Gem::Dependency
|
252
|
+
name: simplecov-cobertura
|
253
|
+
requirement: !ruby/object:Gem::Requirement
|
254
|
+
requirements:
|
255
|
+
- - "~>"
|
256
|
+
- !ruby/object:Gem::Version
|
257
|
+
version: '2.1'
|
258
|
+
type: :development
|
259
|
+
prerelease: false
|
260
|
+
version_requirements: !ruby/object:Gem::Requirement
|
261
|
+
requirements:
|
262
|
+
- - "~>"
|
263
|
+
- !ruby/object:Gem::Version
|
264
|
+
version: '2.1'
|
237
265
|
- !ruby/object:Gem::Dependency
|
238
266
|
name: byebug
|
239
267
|
requirement: !ruby/object:Gem::Requirement
|
@@ -263,10 +291,15 @@ files:
|
|
263
291
|
- lib/peoplegroup/connectors/google_sheets.rb
|
264
292
|
- lib/peoplegroup/connectors/greenhouse.rb
|
265
293
|
- lib/peoplegroup/connectors/hris.rb
|
294
|
+
- lib/peoplegroup/connectors/models/objectified_hash.rb
|
266
295
|
- lib/peoplegroup/connectors/pto_roots.rb
|
267
296
|
- lib/peoplegroup/connectors/slack.rb
|
268
297
|
- lib/peoplegroup/connectors/version.rb
|
269
298
|
- lib/peoplegroup/connectors/workday.rb
|
299
|
+
- lib/peoplegroup/connectors/xml.rb
|
300
|
+
- lib/peoplegroup/connectors/xml/get_organizations.rb
|
301
|
+
- lib/peoplegroup/connectors/xml/helpers.rb
|
302
|
+
- lib/peoplegroup/connectors/xml/request_one_time_payment.rb
|
270
303
|
- lib/peoplegroup/utils.rb
|
271
304
|
homepage: https://gitlab.com/gitlab-com/people-group/peopleops-eng/connectors-gem
|
272
305
|
licenses:
|
@@ -275,7 +308,7 @@ metadata:
|
|
275
308
|
homepage_uri: https://gitlab.com/gitlab-com/people-group/peopleops-eng/connectors-gem
|
276
309
|
source_code_uri: https://gitlab.com/gitlab-com/people-group/peopleops-eng/connectors-gem
|
277
310
|
changelog_uri: https://gitlab.com/gitlab-com/people-group/peopleops-eng/connectors-gem
|
278
|
-
post_install_message:
|
311
|
+
post_install_message:
|
279
312
|
rdoc_options: []
|
280
313
|
require_paths:
|
281
314
|
- lib
|
@@ -283,15 +316,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
283
316
|
requirements:
|
284
317
|
- - ">="
|
285
318
|
- !ruby/object:Gem::Version
|
286
|
-
version: '
|
319
|
+
version: '3.0'
|
287
320
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
288
321
|
requirements:
|
289
322
|
- - ">="
|
290
323
|
- !ruby/object:Gem::Version
|
291
324
|
version: '0'
|
292
325
|
requirements: []
|
293
|
-
rubygems_version: 3.2.
|
294
|
-
signing_key:
|
326
|
+
rubygems_version: 3.2.33
|
327
|
+
signing_key:
|
295
328
|
specification_version: 4
|
296
329
|
summary: Library for our shared connectors.
|
297
330
|
test_files: []
|