peoplegroup-connectors 0.4.7 → 1.0.0

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: fcabbbbfd790a8c46fd6eda5b30e7a42962e6420e05db68413cd94a9f96537a1
4
- data.tar.gz: 1049c26cf69b3b1e40c3ef3b2aabcfa83b4b6b9a10c3bb825f85abab37a47b89
3
+ metadata.gz: 3d996feff7137d07264509fcfe7f8bc3dcbd71eefd5d50fe87f09529c23eabba
4
+ data.tar.gz: bf3a847a3ecb13956246090be029241ba8965d3b69765a00afdf2cf6c8ba6172
5
5
  SHA512:
6
- metadata.gz: fd37579dadad639d505686d349b8bf149159602aa07175758bd3c42554cfea3eaec4648569b720c7e5abf62c96ae0b75c4a77af091c4af540d1170b9ec266494
7
- data.tar.gz: eda96caee25e0fa72511d939683fd87943c3439187aa55c9aa103614ddd0d811b2cd50c6888de95b2259109282803eae2c4467ed67ad417cb4ac99bc9d20099e
6
+ metadata.gz: 7e53ab3a7d2ac8609dbcfe770fb2a797c23053412dcacb52770c0a5ef177b1540543a5566e7ab7b75c83508b20e996b8b606a963ee56ac5700b64aaf1f3c8946
7
+ data.tar.gz: a1acda40d3c83d4428d146ea6e670aa2fd6cc0be663c87b2717cb806589cd9397a440e3aa98f5203516869eb8c01dbe0e0a8050d9f7af6d60f3037e10b6dad71
@@ -1,7 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require 'bamboozled'
4
- require_rel 'models/objectified_hash'
5
4
 
6
5
  module PeopleGroup
7
6
  module Connectors
@@ -127,7 +126,7 @@ module PeopleGroup
127
126
  alias_method :update_team_member, :update_employee
128
127
 
129
128
  def locations
130
- meta_fields.detect { |res| res['name'] == 'Location' }.dig('options').each_with_object([]) { |option, array| array << option['name'] if option['archived'] == 'no' } || []
129
+ meta_fields.detect { |res| res['name'] == 'Location' }['options'].each_with_object([]) { |option, array| array << option['name'] if option['archived'] == 'no' } || []
131
130
  end
132
131
 
133
132
  def employees
@@ -172,7 +171,7 @@ module PeopleGroup
172
171
  def update_job_details(employee_number, data)
173
172
  id = bamboo_id!(employee_number)
174
173
  current_data = job_details(employee_number) # it should only be one row as we just created this user
175
- row_id = current_data.first.dig('id')
174
+ row_id = current_data.first['id']
176
175
  retry_on_error { @client.employee.update_table_row(id, 'jobInfo', row_id, data) }
177
176
  end
178
177
 
@@ -231,11 +230,11 @@ module PeopleGroup
231
230
  end
232
231
 
233
232
  def resumes_folder_id(employee_number)
234
- @resumes_folder_id ||= files(employee_number).dig('categories').find { |folder| folder['name'] == 'Resumes and Applications' }.dig('id')
233
+ @resumes_folder_id ||= files(employee_number)['categories'].find { |folder| folder['name'] == 'Resumes and Applications' }['id']
235
234
  end
236
235
 
237
236
  def contract_folder_id(employee_number)
238
- @contract_folder_id ||= files(employee_number).dig('categories').find { |folder| folder['name'] == 'Contracts & Changes' }.dig('id')
237
+ @contract_folder_id ||= files(employee_number)['categories'].find { |folder| folder['name'] == 'Contracts & Changes' }['id']
239
238
  end
240
239
 
241
240
  def add_file(employee_number, file_name, file, folder_id)
@@ -344,16 +343,12 @@ module PeopleGroup
344
343
  def format_team_member(team_member)
345
344
  return nil if team_member.nil?
346
345
 
347
- formatted = {
346
+ {
348
347
  **team_member.except(*MALFORMED_FIELDS),
349
348
  'customExportNameLocationtoTeamPage' => team_member['customExportName/LocationtoTeamPage?'] || 'No',
350
349
  'customJobTitleSpecialtyMultiSelect' => team_member['customJobTitleSpecialty(Multi-Select)'],
351
350
  'customI9Processed' => team_member['customI-9Processed']
352
351
  }
353
-
354
- formatted = PeopleGroup::Connectors::Models::ObjectifiedHash.new(formatted) unless @use_report
355
-
356
- formatted
357
352
  end
358
353
  end
359
354
  end
@@ -14,24 +14,17 @@ module PeopleGroup
14
14
  end
15
15
 
16
16
  def find_gitlabber(field, query)
17
- return if !query || query.empty?
18
-
19
- possible_members = get_group_members('gitlab-com', query: query)
20
- if field === :email
21
- possible_members.first
22
- else
23
- possible_members.find { |team_member| team_member.public_send(field) === query }
24
- end
17
+ find_gitlabber_on(field, query, 'gitlab-com')
25
18
  end
26
19
 
27
20
  def find_gitlabber_on(field, query, group)
28
21
  return if !query || query.empty?
29
22
 
30
23
  possible_members = get_group_members(group, query: query)
31
- if field === :email
24
+ if field == :email
32
25
  possible_members.first
33
26
  else
34
- possible_members.find { |team_member| team_member.public_send(field) === query }
27
+ possible_members.find { |team_member| team_member.public_send(field) == query }
35
28
  end
36
29
  end
37
30
 
@@ -7,12 +7,12 @@ module PeopleGroup
7
7
  EmployeeNotFoundError = Class.new(StandardError)
8
8
 
9
9
  def initialize
10
- @workday = Workday.new
10
+ @workday = Workday::Client.new
11
11
  @bamboo = Bamboo.new
12
12
  end
13
13
 
14
14
  def employees
15
- @employees ||= @workday.workers
15
+ @employees ||= Workday.workers
16
16
  end
17
17
  alias_method :team_members, :employees
18
18
 
@@ -85,7 +85,7 @@ module PeopleGroup
85
85
 
86
86
  team_members.find do |team_member|
87
87
  emails = [team_member['workEmail'], team_member['bestEmail'], team_member['homeEmail']]
88
- emails.compact.any? { |match| email.casecmp(match) === 0 }
88
+ emails.compact.any? { |match| email.casecmp(match).zero? }
89
89
  end
90
90
  end
91
91
  alias_method :slack_email_lookup_with_fallback, :search_team_member_by_email
@@ -214,7 +214,8 @@ module PeopleGroup
214
214
  end
215
215
 
216
216
  def job_details(employee_number)
217
- @workday.manager_history(employee_number)
217
+ warn '[DEPRECATED] PeopleGroup::Connectors::Hris#job_details, use a custom workday report instead.'
218
+ Workday::Report.call(url: ENV.fetch('WORKDAY_MANAGER_REPORT', nil))
218
219
  end
219
220
 
220
221
  def resumes_folder_id(employee_number)
@@ -49,7 +49,7 @@ module PeopleGroup
49
49
  options = {
50
50
  channel: channel,
51
51
  text: text,
52
- as_user: true,
52
+ as_user: as_user,
53
53
  attachments: attachments,
54
54
  unfurl_links: false,
55
55
  thread_ts: thread_ts,
@@ -2,6 +2,6 @@
2
2
 
3
3
  module PeopleGroup
4
4
  module Connectors
5
- VERSION = '0.4.7'
5
+ VERSION = '1.0.0'
6
6
  end
7
7
  end
@@ -0,0 +1,157 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'savon'
4
+ require_relative 'xml'
5
+
6
+ module PeopleGroup
7
+ module Connectors
8
+ module Workday
9
+ # Workday client
10
+ class Client
11
+ include XML::GetOrganizations
12
+ include XML::RequestOneTimePayment
13
+
14
+ XML_NAMESPACES = {
15
+ 'xmlns' => nil,
16
+ 'xmlns:env' => 'http://schemas.xmlsoap.org/soap/envelope/',
17
+ 'xmlns:xsd' => 'http://www.w3.org/2001/XMLSchema',
18
+ 'xmlns:wsse' => 'http://docs.oasis-open.org/wss/2004/01/oasis-200401-wss-wssecurity-secext-1.0.xsd'
19
+ }.freeze
20
+
21
+ # The client instance
22
+ attr_reader :instance
23
+
24
+ # Client Options
25
+ attr_reader :options
26
+
27
+ # service uri
28
+ attr_reader :uri
29
+
30
+ # Current Workday API Version
31
+ API_VERSION = 'v37.0'
32
+
33
+ EmployeeNotFoundError = Class.new(StandardError)
34
+ ClientError = Class.new(StandardError)
35
+
36
+ def initialize(options = {})
37
+ @options = {
38
+ url: ENV.fetch('WORKDAY_SERVICE_URL', nil),
39
+ username: ENV.fetch('WORKDAY_ISU_USERNAME', nil),
40
+ password: ENV.fetch('WORKDAY_ISU_PASSWORD', nil),
41
+ debug: ENV.fetch('WORKDAY_SOAP_DEBUG_LOGS', false)
42
+ }.merge!(options)
43
+
44
+ @uri = URI.parse(@options[:url])
45
+ @uri.query = nil
46
+
47
+ @instance = client(services)
48
+ rescue URI::InvalidURIError
49
+ raise ClientError, "Invalid service URI: #{uri}"
50
+ end
51
+
52
+ def retry_on_error(&block)
53
+ Utils.retry_on_error(errors: [Net::ReadTimeout], delay: 3, &block)
54
+ end
55
+
56
+ def call(operation_name, options = {}, &block)
57
+ response = @instance.call(operation_name, { **options, **attributes }, &block)
58
+ { **response.body, code: response.http.code }
59
+ rescue Savon::UnknownOperationError => e
60
+ raise ClientError.new(e), cause: nil
61
+ end
62
+
63
+ def auto_paginated_call(operation_name, options = {}, &block)
64
+ data = nil
65
+ page = 1
66
+
67
+ loop do
68
+ response = @instance.call(operation_name, { **options, **attributes }, &block)
69
+ # The response hash contains only one key named after the operation performed
70
+ # eg. get_workers_response
71
+ _operation_response_name, operation_result = response.to_hash.first
72
+
73
+ page = operation_result.dig(:response_results, :page).to_i
74
+ total_pages = operation_result.dig(:response_results, :total_pages).to_i
75
+
76
+ # The operation data hash contains only one key named after the Workday object(s) retrieved
77
+ # eg. worker
78
+ _object_label, partial_data = operation_result[:response_data].first
79
+
80
+ page == 1 ? data = partial_data : data.concat(partial_data)
81
+
82
+ break if page == total_pages
83
+
84
+ page += 1
85
+ end
86
+
87
+ data
88
+ end
89
+
90
+ # Check if workday debug is enabled for the instance.
91
+ def debug_enabled?
92
+ @options[:debug] ? true : false
93
+ end
94
+
95
+ # Default Workday specific body paramaters
96
+ # xmlns:wd - The URN of the XMLNS file
97
+ # wd:version - The API Version of workday
98
+ def attributes
99
+ { attributes: { 'xmlns:wd' => 'urn:com.workday/bsvc', 'wd:version' => API_VERSION } }
100
+ end
101
+
102
+ # The respective XML Service WSDL URL
103
+ # https://wd2-impl-services1.workday.com/ccx/service/systemreport2/gitlab/Public_Web_Services
104
+ # @return [String] The combined url to access the service at
105
+ def wsdl_url
106
+ @wsdl_url ||= "#{@uri}/#{API_VERSION}?wsdl"
107
+ end
108
+
109
+ private
110
+
111
+ def username
112
+ @options[:username]
113
+ end
114
+
115
+ def password
116
+ @options[:password]
117
+ end
118
+
119
+ # The current tenant
120
+ def tenant
121
+ @tenant ||= @uri.path.split('/')[-2]
122
+ end
123
+
124
+ # Authentication for Soap web service
125
+ def wsse_auth
126
+ @wsse_auth ||= ["#{username}@#{tenant}", password]
127
+ end
128
+
129
+ # Enabled services from the service URL
130
+ # @return [String] Service or services joined with '+'. Example: 'Staffing' or 'Staffing+Compensation'
131
+ def services
132
+ @services ||= @uri.path.split('/')[-1] # .split('+')
133
+ end
134
+
135
+ # Returns the client based on the service.
136
+ # @param Service [String] The service to use, ie: Staffing, Compensation
137
+ # @param options [Hash] default options for the client.
138
+ #
139
+ # @return [Savon::Client] The client connected to the specified service.
140
+ def client(_service, options = {})
141
+ client_opts = {
142
+ log: debug_enabled?,
143
+ wsse_auth: wsse_auth,
144
+ pretty_print_xml: debug_enabled?,
145
+ convert_request_keys_to: :camelcase,
146
+ env_namespace: :env,
147
+ namespace_identifier: :wd,
148
+ wsdl: wsdl_url,
149
+ namespaces: XML_NAMESPACES
150
+ }.merge!(options)
151
+
152
+ Savon.client(**client_opts)
153
+ end
154
+ end
155
+ end
156
+ end
157
+ end
@@ -0,0 +1,96 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PeopleGroup
4
+ module Connectors
5
+ module Workday
6
+ class ReportError < StandardError
7
+ def initialize(message = '', request = nil)
8
+ @request = request
9
+ @message = message
10
+ super(message)
11
+ end
12
+ end
13
+
14
+ # Report
15
+ class Report
16
+ # The authorization header used for the report.
17
+ attr_reader :auth_header
18
+
19
+ # The parsed URI of the report URL
20
+ attr_reader :uri
21
+
22
+ # Create a new report instance
23
+ # @param url [String] The report URL without any parameters.
24
+ # @raises [ReportError]
25
+ def initialize(url:, password: nil)
26
+ @uri = URI.parse(url)
27
+
28
+ # Ensure URI is properly read
29
+ raise ReportError, "Error with report URL: #{url}. Is this a valid URL?" unless @uri.scheme && @uri.host && @uri.path
30
+
31
+ @uri.query = nil # clear any query params
32
+
33
+ # Use provided password or search in env.
34
+ pass = password || ENV.fetch('WORKDAY_ISU_PASSWORD', nil)
35
+
36
+ raise ReportError, "No username provided for report: #{url}" if username.nil?
37
+ raise ReportError, "No password provided for report: #{url}" if pass.nil?
38
+
39
+ # Encode credentials
40
+ auth = Base64.strict_encode64("#{username}:#{pass}")
41
+
42
+ # Base64 Encode
43
+ @auth_header = { Authorization: "Basic #{auth}" }
44
+ rescue URI::InvalidURIError
45
+ raise ReportError, "Invalid URI detected for report: '#{url}'."
46
+ end
47
+
48
+ # @param url [String] The report URL without any parameters
49
+ # @param prompts [Hash] Report input prompts/parameters.
50
+ # @return [Array|Hash] the JSON report response.
51
+ # @see run for more info.
52
+ def self.call(url:, password: nil, prompts: {})
53
+ new(url: url, password: password)&.run(prompts)
54
+ end
55
+
56
+ # Run the report with the given prompts
57
+ # @param [Hash] prompts The report key value pair of parameters or 'prompts' (as referred to in Workday).
58
+ # @return [Array|Hash] the JSON report response.
59
+ # @raise [StandardError] if the report returns with any status other than 200.
60
+ def run(prompts = {})
61
+ url = report_url(prompts)
62
+ RestClient.get(url, @auth_header) do |response, _req, _result|
63
+ # Raise a report error if we ran into any issues accessing the report
64
+ raise ReportError, inspect, response unless response&.code == 200
65
+
66
+ # parse the response body into JSON
67
+ json = JSON.parse(response.body)
68
+
69
+ # if return content of 'Report_Entry' or the whole set if key does not exist.
70
+ json['Report_Entry'] || json
71
+ end
72
+ end
73
+
74
+ # Extract username from URI path (needed for auth)
75
+ def username
76
+ @username ||= @uri.path.split('/')[-2]
77
+ end
78
+
79
+ # Combine the url and extra input parameters in an encoded format.
80
+ # @param [Hash] prompts The report prompts/parameters.
81
+ # @return [String] The @url (report url) combined with report type `?type='json'` and any extra parameters.
82
+ def report_url(prompts = {})
83
+ uri = @uri.clone # copy the URI object
84
+ params = prompts.merge!({ type: :json }) # ensure report format is always JSON
85
+ uri.query = URI.encode_www_form(params)
86
+ uri.to_s
87
+ end
88
+
89
+ # Inspect the Workday Report instance
90
+ def inspect
91
+ "#<#{self.class.name}:#{object_id} @uri='#{@uri}' @auth_header=#{@auth_header ? 'HIDDEN' : auth_header} @username=#{username}>"
92
+ end
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,85 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helpers'
4
+
5
+ module PeopleGroup
6
+ module Connectors
7
+ module Workday
8
+ module XML
9
+ # GetOrganization operation.
10
+ # @see https://community.workday.com/sites/default/files/file-hosting/productionapi/Staffing/v37.2/Get_Organizations.html
11
+ module GetOrganizations
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 { |location| location[: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
+ 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
85
+ end
@@ -0,0 +1,75 @@
1
+ # frozen_string_literal: true
2
+
3
+ module PeopleGroup
4
+ module Connectors
5
+ module Workday
6
+ module XML
7
+ # Common wrapper components for Workday XML queries
8
+ module Helpers
9
+ MAX_RESULTS_PER_PAGE = 999
10
+
11
+ def self.request_references(refs = {})
12
+ {
13
+ 'Request_References' => refs
14
+ }
15
+ end
16
+
17
+ def self.response_filter(filters = {})
18
+ {
19
+ 'Response_Filter' => filters
20
+ }
21
+ end
22
+
23
+ # Default Request Criteria XML Wrapper
24
+ # @param criteria [Hash] A list of key value pair XML attributes.
25
+ #
26
+ # @return [Hash] - The <wd:Request_Criteria> XML Component containing a set of options.
27
+ def self.request_criteria(criteria = {})
28
+ {
29
+ 'Request_Criteria' => criteria
30
+ }
31
+ end
32
+
33
+ def self.response_group(group = {})
34
+ { 'Response_Group' => group }
35
+ end
36
+
37
+ def self.default_response_group(additional = {})
38
+ groups = { 'Include_Hierarchy_Data' => false, **additional }
39
+ response_group groups
40
+ end
41
+
42
+ # Contructs a hash that we can pass into the #call Savon client method.
43
+ def self.construct_options(operation, message)
44
+ operation = operation.to_s.split('_').collect(&:capitalize).join('_') unless operation.is_a?(String)
45
+ {
46
+ message_tag: "#{operation}_Request",
47
+ message: message
48
+ }
49
+ end
50
+
51
+ def self.pagination_parameters(page = 1, results_per_page = MAX_RESULTS_PER_PAGE)
52
+ response_filter(
53
+ {
54
+ 'Page' => page,
55
+ 'Count' => results_per_page
56
+ }
57
+ )
58
+ end
59
+
60
+ # The <wd:Employee_Reference> object wrapper.
61
+ # @param id [Integer] The employee number of the team member.
62
+ # @return [Hash] The <wd:Employee_Reference> XML wrapper component.
63
+ def self.team_member_reference(id)
64
+ {
65
+ 'Employee_Reference' => {
66
+ 'ID' => id,
67
+ attributes!: { 'ID' => { 'wd:type' => 'Employee_ID' } }
68
+ }
69
+ }
70
+ end
71
+ end
72
+ end
73
+ end
74
+ end
75
+ end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'helpers'
4
+
5
+ module PeopleGroup
6
+ module Connectors
7
+ module Workday
8
+ module XML
9
+ # RequestOneTimePayment module
10
+ # Uses the [PeopleGroup::Connectors::Workday::Client] to add a bonus to the speified team member.
11
+ # @see https://community.workday.com/sites/default/files/file-hosting/productionapi/Compensation/v37.0/Request_One-Time_Payment.html
12
+ module RequestOneTimePayment
13
+ # The Operation for this Module
14
+ OPERATION = :request_one_time_payment
15
+ OPERATION_AS_PARAM = 'Request_One-Time_Payment'
16
+
17
+ # The amount in USD to give for bonuses, must be a decimal.
18
+ DISCRETIONARY_BONUS = 1000.0
19
+
20
+ # Adds a bonus to the team members
21
+ # @param employee_number [Integer] The Employee Number of the team member receiving the bonus.
22
+ # @param comment [String] The comment to leave on the business process.
23
+ def add_bonus(employee_number, comment)
24
+ options = {
25
+ **business_process_params(comment),
26
+ **one_time_payment_data(employee_number, comment)
27
+ }
28
+
29
+ request = Helpers.construct_options(OPERATION_AS_PARAM, options)
30
+ call(OPERATION, request)
31
+ end
32
+
33
+ private
34
+
35
+ # https://community.workday.com/sites/default/files/file-hosting/productionapi/Compensation/v37.0/Request_One-Time_Payment.html#Business_Process_ParametersType
36
+ # @param comment [String] The comment associated with the bonus.
37
+ # @param run_now: [Boolean] Defaults to true to process this transaction immediatley.
38
+ # @param auto_complete: [Boolean] Defaults to true to apply this bonus without needing approval.
39
+ #
40
+ # @return [Hash] The <wd:Business_Process_Parameters> for this request.
41
+ def business_process_params(comment, run_now: true, auto_complete: true)
42
+ {
43
+ 'Business_Process_Parameters' => {
44
+ 'Auto_Complete' => auto_complete,
45
+ 'Run_Now' => run_now,
46
+
47
+ # Comment Data
48
+ # https://community.workday.com/sites/default/files/file-hosting/productionapi/Compensation/v37.0/Request_One-Time_Payment.html#Business_Process_Comment_DataType
49
+ 'Comment_Data' => {
50
+ 'Comment' => comment
51
+ }
52
+ }
53
+ }
54
+ end
55
+
56
+ # The Effective Date parameter for when to apply the payment, defaults to today.
57
+ # @param date [Date] The day to apply the payment.
58
+ # @return [Hash] The <wd:Effective_Date> parameter set to the specified date.
59
+ def effective_date(date = Date.today)
60
+ {
61
+ 'Effective_Date' => date.to_s
62
+ }
63
+ end
64
+
65
+ # The payment specific data for what should be applied.
66
+ # @param comment [String] The comment for the discretionary bonus.
67
+ # @param amount [Double] The amount to include in the bonus with decimal accuracy.
68
+ def one_time_payment_sub_data(comment, amount = DISCRETIONARY_BONUS)
69
+ {
70
+ 'One-Time_Payment_Sub_Data' => {
71
+ **payment_plan_reference,
72
+ 'Amount' => amount,
73
+ 'Comment' => comment
74
+ }
75
+ }
76
+ end
77
+
78
+ # The type of one time payment we want to group this under.
79
+ def payment_plan_reference
80
+ {
81
+ 'One_Time_Payment_Plan_Reference' => {
82
+ 'ID' => 'Discretionary Bonus',
83
+ attributes!: { 'ID' => { 'wd:type' => 'One-Time_Payment_Plan_ID' } }
84
+ }
85
+ }
86
+ end
87
+
88
+ # https://community.workday.com/sites/default/files/file-hosting/productionapi/Compensation/v37.0/Request_One-Time_Payment.html#Request_One-Time_Payment_DataType
89
+ # @param employee_number [Integer] The team member's employee number.
90
+ # @param comment [String] A comment describing why the payment is being requested.
91
+ # @return [Hash] The <wd:One-Time_Payment_Data> component.
92
+ def one_time_payment_data(employee_number, comment)
93
+ {
94
+ 'One-Time_Payment_Data' => {
95
+ **Helpers.team_member_reference(employee_number),
96
+ **effective_date,
97
+ **one_time_payment_sub_data(comment)
98
+ }
99
+ }
100
+ end
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,12 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'xml/get_organizations'
4
+ require_relative 'xml/request_one_time_payment'
5
+
6
+ module PeopleGroup
7
+ module Connectors
8
+ module Workday
9
+ module XML; end
10
+ end
11
+ end
12
+ end