arbetsformedlingen 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (37) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +17 -0
  3. data/.rspec +2 -0
  4. data/.travis.yml +5 -0
  5. data/Gemfile +4 -0
  6. data/LICENSE.txt +21 -0
  7. data/README.md +138 -0
  8. data/Rakefile +6 -0
  9. data/arbetsformedlingen.gemspec +33 -0
  10. data/bin/console +14 -0
  11. data/bin/setup +8 -0
  12. data/config/locales/errors.yml +16 -0
  13. data/data/country-codes.csv +237 -0
  14. data/data/municipality-codes.csv +292 -0
  15. data/lib/arbetsformedlingen.rb +56 -0
  16. data/lib/arbetsformedlingen/client.rb +25 -0
  17. data/lib/arbetsformedlingen/codes/country_code.rb +24 -0
  18. data/lib/arbetsformedlingen/codes/drivers_license_code.rb +33 -0
  19. data/lib/arbetsformedlingen/codes/experience_required_code.rb +21 -0
  20. data/lib/arbetsformedlingen/codes/municipality_code.rb +31 -0
  21. data/lib/arbetsformedlingen/codes/salary_type_code.rb +21 -0
  22. data/lib/arbetsformedlingen/models/application_method.rb +20 -0
  23. data/lib/arbetsformedlingen/models/company.rb +50 -0
  24. data/lib/arbetsformedlingen/models/document.rb +28 -0
  25. data/lib/arbetsformedlingen/models/dry/predicates.rb +66 -0
  26. data/lib/arbetsformedlingen/models/dry/types.rb +34 -0
  27. data/lib/arbetsformedlingen/models/model.rb +25 -0
  28. data/lib/arbetsformedlingen/models/packet.rb +39 -0
  29. data/lib/arbetsformedlingen/models/position.rb +56 -0
  30. data/lib/arbetsformedlingen/models/publication.rb +30 -0
  31. data/lib/arbetsformedlingen/models/qualification.rb +23 -0
  32. data/lib/arbetsformedlingen/models/salary.rb +20 -0
  33. data/lib/arbetsformedlingen/models/schedule.rb +56 -0
  34. data/lib/arbetsformedlingen/output_builder.rb +226 -0
  35. data/lib/arbetsformedlingen/response.rb +34 -0
  36. data/lib/arbetsformedlingen/version.rb +3 -0
  37. metadata +178 -0
@@ -0,0 +1,56 @@
1
+ module Arbetsformedlingen
2
+ ScheduleSchema = Dry::Validation.Form do
3
+ configure do
4
+ config.type_specs = true
5
+ config.messages_file = File.expand_path('../../../../config/locales/errors.yml', __FILE__)
6
+
7
+ predicates(Predicates)
8
+ end
9
+
10
+ required(:summary, Types::StrippedString).filled
11
+ required(:full_time, Types::Strict::Bool).filled
12
+ required(:position_duration_code, Types::PositionDuration).filled
13
+ required(:start_date, Types::StrippedString).filled(:yyyy_mm_dd?)
14
+
15
+ optional(:end_date, Types::StrippedString)
16
+ end
17
+
18
+ class Schedule < Model
19
+ DURATION_MAP = {
20
+ regular: -1,
21
+ from_0_days_to_10_days: 8,
22
+ from_11_days_to_3_months: 7,
23
+ from_june_to_august: 4, # summer months (June to August)
24
+ from_3_months_to_6_months: 3,
25
+ from_6_months_to_longer: 2
26
+ }.freeze
27
+
28
+ def initialize(hash)
29
+ data = hash.dup
30
+ duration_code = duration_code(data[:start_date], data[:end_date])
31
+ data[:position_duration_code] = duration_code
32
+ data[:full_time] = duration_code == -1
33
+ super(ScheduleSchema.call(data))
34
+ end
35
+
36
+ private
37
+
38
+ def duration_code(start_date, end_date)
39
+ return DURATION_MAP.fetch(:regular) if end_date.nil?
40
+
41
+ days = end_date - start_date
42
+
43
+ return DURATION_MAP.fetch(:from_0_days_to_10_days) if days <= 10
44
+ return DURATION_MAP.fetch(:from_11_days_to_3_months) if days > 10 && days < 92
45
+ return DURATION_MAP.fetch(:from_june_to_august) if summer_job?(start_date, end_date)
46
+ return DURATION_MAP.fetch(:from_3_months_to_6_months) if days >= 92 && days < 183
47
+
48
+ DURATION_MAP.fetch(:from_6_months_to_longer)
49
+ end
50
+
51
+ def summer_job?(start_date, end_date)
52
+ start_date.month == 6 && start_date.day == 1 &&
53
+ end_date.month == 8 && end_date.day == 31
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,226 @@
1
+ require 'builder'
2
+
3
+ module Arbetsformedlingen
4
+ class OutputBuilder
5
+ def initialize(packet)
6
+ @packet = packet
7
+ end
8
+
9
+ def to_xml
10
+ # TODO: Set option so that åäö isn't encoded
11
+ builder = Builder::XmlMarkup.new(indent: 2)
12
+ append_envelope(builder, @packet.to_h)
13
+ end
14
+
15
+ private
16
+
17
+ def append_envelope(builder, packet_data)
18
+ document = packet_data.fetch(:document)
19
+
20
+ builder.Envelope(
21
+ xmlns: 'http://arbetsformedlingen.se/LedigtArbete',
22
+ version: '0.52'
23
+ ) do |envelope|
24
+ append_sender(envelope, document)
25
+ append_transaction_info(envelope, document)
26
+ append_packet(envelope, packet_data)
27
+ end
28
+ end
29
+
30
+ def append_sender(envelope, document)
31
+ envelope.Sender(id: document.fetch(:customer_id), email: document.fetch(:email))
32
+ end
33
+
34
+ def append_transaction_info(envelope, document)
35
+ envelope.TransactInfo(timeStamp: document.fetch(:timestamp)) do |ti|
36
+ ti.TransactId(document.fetch(:id))
37
+ end
38
+ end
39
+
40
+ def append_packet(envelope, packet_data)
41
+ envelope.Packet do |pack|
42
+ pack.PacketInfo { |pi| pi.PacketId(packet_data.fetch(:id)) }
43
+ pack.Payload { |payload| append_job_position(payload, packet_data) }
44
+ end
45
+ end
46
+
47
+ def append_job_position(payload, packet_data)
48
+ payload.JobPositionPosting(status: "#{'in' unless packet_data.fetch(:active)}active") do |pos|
49
+ cin = packet_data[:position][:company].fetch(:cin_arbetsformedlingen)
50
+ job_pos_id = "#{cin}-#{packet_data.fetch(:job_id)}"
51
+ pos.JobPositionPostingId(job_pos_id)
52
+
53
+ append_hiring_org(payload, packet_data)
54
+ append_post_detail(payload, packet_data)
55
+ position = packet_data.fetch(:position)
56
+ append_job_position_info(payload, position)
57
+ append_how_to_apply(payload, position)
58
+
59
+ pos.NumberToFill(packet_data.fetch(:number_to_fill))
60
+
61
+ pos.JPPExtension do |jpp|
62
+ company = position.fetch(:company)
63
+ jpp.HiringOrgDescription(company.fetch(:description))
64
+ jpp.OccupationGroup(code: packet_data.fetch(:ssyk_id), codename: 'OccupationNameID')
65
+ end
66
+ end
67
+ end
68
+
69
+ def append_how_to_apply(node, position)
70
+ node.HowToApply(distribute: 'external') do |how_to|
71
+ how_to.ApplicationMethods do |methods|
72
+ methods.ByWeb do |by_web|
73
+ method = position.fetch(:application_method)
74
+ by_web.URL(method.fetch(:url))
75
+ by_web.SummaryText(method.fetch(:summary))
76
+ end
77
+ end
78
+ end
79
+ end
80
+
81
+ def append_hiring_org(payload, packet_data)
82
+ company = packet_data.dig(:position, :company)
83
+
84
+ payload.HiringOrg do |org|
85
+ org.HiringOrgName(company.fetch(:name))
86
+ org.HiringOrgId(company.fetch(:cin_arbetsformedlingen), idOwner: company.fetch(:cin))
87
+ append_company_contact(org, company)
88
+ end
89
+ end
90
+
91
+ def append_company_contact(node, company)
92
+ data = company.fetch(:address)
93
+ node.Contact do |contact|
94
+ contact.PostalAddress do |address|
95
+ address.PostalCode(data.fetch(:zip))
96
+ address.Municipality(data.fetch(:municipality))
97
+ append_delivery_address(node, data)
98
+ end
99
+ end
100
+ end
101
+
102
+ def append_post_detail(node, packet_data)
103
+ publication = packet_data.fetch(:publication)
104
+
105
+ node.PostDetail do |detail|
106
+ if publication[:publish_at]
107
+ detail.StartDate { |n| n.Date(publication.fetch(:publish_at)) }
108
+ end
109
+ detail.EndDate { |n| n.Date(publication.fetch(:unpublish_at)) }
110
+
111
+ detail.PostedBy do |posted|
112
+ posted.Contact do |contact|
113
+ contact.PersonName { |pn| pn.FormattedName(publication.fetch(:name)) }
114
+ contact.tag!('E-mail', publication.fetch(:email))
115
+ end
116
+ end
117
+ end
118
+ end
119
+
120
+ def append_job_position_info(node, position)
121
+ node.JobPositionInformation do |job_pos|
122
+ job_pos.JobPositionTitle(position.fetch(:title))
123
+ append_job_position_description(job_pos, position)
124
+ append_job_position_requirements(job_pos, position)
125
+ end
126
+ end
127
+
128
+ def append_job_position_requirements(node, position)
129
+ node.JobPositionRequirements do |job_pos_req|
130
+ position.fetch(:qualifications).each do |qualification|
131
+ if qualification.fetch(:required)
132
+ job_pos_req.QualificationsRequired do |req_qual|
133
+ append_inner_qualification(req_qual, qualification)
134
+ end
135
+ else
136
+ job_pos_req.QualificationsPreferred do |req_qual|
137
+ append_inner_qualification(req_qual, qualification)
138
+ end
139
+ end
140
+ end
141
+ end
142
+ end
143
+
144
+ def append_inner_qualification(node, qualification)
145
+ q = qualification
146
+
147
+ node.P(q.fetch(:summary)) unless q.fetch(:summary, '').empty?
148
+
149
+ unless q[:experience].nil?
150
+ node.Qualification(type: 'experience', yearsOfExperience: q.fetch(:experience))
151
+ end
152
+
153
+ if q[:drivers_license] && !q[:drivers_license].empty?
154
+ node.Qualification(
155
+ type: 'license',
156
+ description: 'DriversLicense',
157
+ category: q.fetch(:drivers_license)
158
+ )
159
+ end
160
+
161
+ node.Qualification(type: 'equipment', description: 'Car') if q[:car]
162
+ end
163
+
164
+ def append_job_position_description(node, position)
165
+ node.JobPositionDescription do |job_pos_desc|
166
+ job_pos_desc.JobPositionPurpose(position.fetch(:purpose))
167
+ job_pos_desc.JobPositionLocation do |job_pos_loc|
168
+ append_job_position_address(job_pos_loc, position.fetch(:address))
169
+ end
170
+ job_pos_desc.Classification do |classification|
171
+ append_schedule(classification, position.fetch(:schedule))
172
+ end
173
+ job_pos_desc.CompensationDescription do |comp_desc|
174
+ salary = position.fetch(:salary)
175
+ comp_desc.Pay do |pay|
176
+ pay.SalaryMonthly(salary.fetch(:type), currency: salary.fetch(:currency))
177
+ end
178
+ comp_desc.SummaryText(salary.fetch(:summary))
179
+ end
180
+ end
181
+ end
182
+
183
+ def append_schedule(node, schedule)
184
+ node.Schedule do |sch|
185
+ if schedule.fetch(:full_time)
186
+ sch.FullTime('')
187
+ else
188
+ sch.PartTime('')
189
+ end
190
+ sch.SummaryText(schedule.fetch(:summary))
191
+ end
192
+
193
+ node.Duration do |dur|
194
+ if schedule.fetch(:full_time)
195
+ dur.Regular('')
196
+ else
197
+ dur.Temporary { |t| t.TermLength(schedule.fetch(:position_duration_code)) }
198
+ end
199
+ dur.SummaryText(schedule.fetch(:summary))
200
+ end
201
+ end
202
+
203
+ def append_delivery_address(node, data)
204
+ return node unless data.key?(:full_address) || data.key?(:street)
205
+
206
+ node.DeliveryAddress do |d_address|
207
+ d_address.AddressLine(data.fetch(:full_address)) if data.key?(:full_address)
208
+ d_address.StreetName(data.fetch(:street)) if data.key?(:street)
209
+ end
210
+ end
211
+
212
+ def append_job_position_address(node, address)
213
+ node.PostalAddress do |a_node|
214
+ a_node.CountryCode('SE')
215
+ a_node.PostalCode(address.fetch(:zip)) if address.key?(:zip)
216
+ a_node.Municipality(address.fetch(:municipality))
217
+ append_delivery_address(a_node, address)
218
+ end
219
+
220
+ node.LocationSummary do |loc_sum|
221
+ loc_sum.Municipality(address.fetch(:municipality))
222
+ loc_sum.CountryCode(address.fetch(:country_code))
223
+ end
224
+ end
225
+ end
226
+ end
@@ -0,0 +1,34 @@
1
+ module Arbetsformedlingen
2
+ class Response
3
+ attr_reader :code, :messages, :body, :request_body
4
+
5
+ def initialize(httparty_response, request_boby)
6
+ @code = httparty_response.code
7
+ @body = httparty_response.body
8
+ @request_body = request_body
9
+ @valid = @code == 202
10
+ @messages = build_messages(httparty_response.to_a)
11
+ end
12
+
13
+ def valid?
14
+ @valid
15
+ end
16
+
17
+ private
18
+
19
+ def build_messages(messages)
20
+ messages.map do |message|
21
+ # HTTParty returns an array if there is only one key-value pair in the response
22
+ # so we need to check for it here and normalize
23
+ if message.is_a?(Array)
24
+ { message: message.last, error_code: nil }
25
+ else
26
+ error_code = message['ErrorCode']
27
+ @valid = false if error_code
28
+
29
+ { message: message['Message'], error_code: error_code }
30
+ end
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module Arbetsformedlingen
2
+ VERSION = '0.1.0'.freeze
3
+ end
metadata ADDED
@@ -0,0 +1,178 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: arbetsformedlingen
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.1.0
5
+ platform: ruby
6
+ authors:
7
+ - Jacob Burenstam
8
+ autorequire:
9
+ bindir: exe
10
+ cert_chain: []
11
+ date: 2017-06-07 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: httparty
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '0.13'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '0.13'
27
+ - !ruby/object:Gem::Dependency
28
+ name: dry-validation
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - "~>"
32
+ - !ruby/object:Gem::Version
33
+ version: '0.10'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - "~>"
39
+ - !ruby/object:Gem::Version
40
+ version: '0.10'
41
+ - !ruby/object:Gem::Dependency
42
+ name: builder
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - "~>"
46
+ - !ruby/object:Gem::Version
47
+ version: '3.2'
48
+ type: :runtime
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - "~>"
53
+ - !ruby/object:Gem::Version
54
+ version: '3.2'
55
+ - !ruby/object:Gem::Dependency
56
+ name: bundler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - "~>"
60
+ - !ruby/object:Gem::Version
61
+ version: '1.14'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - "~>"
67
+ - !ruby/object:Gem::Version
68
+ version: '1.14'
69
+ - !ruby/object:Gem::Dependency
70
+ name: rake
71
+ requirement: !ruby/object:Gem::Requirement
72
+ requirements:
73
+ - - "~>"
74
+ - !ruby/object:Gem::Version
75
+ version: '10.0'
76
+ type: :development
77
+ prerelease: false
78
+ version_requirements: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - "~>"
81
+ - !ruby/object:Gem::Version
82
+ version: '10.0'
83
+ - !ruby/object:Gem::Dependency
84
+ name: rspec
85
+ requirement: !ruby/object:Gem::Requirement
86
+ requirements:
87
+ - - "~>"
88
+ - !ruby/object:Gem::Version
89
+ version: '3.0'
90
+ type: :development
91
+ prerelease: false
92
+ version_requirements: !ruby/object:Gem::Requirement
93
+ requirements:
94
+ - - "~>"
95
+ - !ruby/object:Gem::Version
96
+ version: '3.0'
97
+ - !ruby/object:Gem::Dependency
98
+ name: byebug
99
+ requirement: !ruby/object:Gem::Requirement
100
+ requirements:
101
+ - - ">="
102
+ - !ruby/object:Gem::Version
103
+ version: '0'
104
+ type: :development
105
+ prerelease: false
106
+ version_requirements: !ruby/object:Gem::Requirement
107
+ requirements:
108
+ - - ">="
109
+ - !ruby/object:Gem::Version
110
+ version: '0'
111
+ description: Arbetsförmedlingen API client (Swedish Public Employment Service)
112
+ email:
113
+ - burenstam@gmail.com
114
+ executables: []
115
+ extensions: []
116
+ extra_rdoc_files: []
117
+ files:
118
+ - ".gitignore"
119
+ - ".rspec"
120
+ - ".travis.yml"
121
+ - Gemfile
122
+ - LICENSE.txt
123
+ - README.md
124
+ - Rakefile
125
+ - arbetsformedlingen.gemspec
126
+ - bin/console
127
+ - bin/setup
128
+ - config/locales/errors.yml
129
+ - data/country-codes.csv
130
+ - data/municipality-codes.csv
131
+ - lib/arbetsformedlingen.rb
132
+ - lib/arbetsformedlingen/client.rb
133
+ - lib/arbetsformedlingen/codes/country_code.rb
134
+ - lib/arbetsformedlingen/codes/drivers_license_code.rb
135
+ - lib/arbetsformedlingen/codes/experience_required_code.rb
136
+ - lib/arbetsformedlingen/codes/municipality_code.rb
137
+ - lib/arbetsformedlingen/codes/salary_type_code.rb
138
+ - lib/arbetsformedlingen/models/application_method.rb
139
+ - lib/arbetsformedlingen/models/company.rb
140
+ - lib/arbetsformedlingen/models/document.rb
141
+ - lib/arbetsformedlingen/models/dry/predicates.rb
142
+ - lib/arbetsformedlingen/models/dry/types.rb
143
+ - lib/arbetsformedlingen/models/model.rb
144
+ - lib/arbetsformedlingen/models/packet.rb
145
+ - lib/arbetsformedlingen/models/position.rb
146
+ - lib/arbetsformedlingen/models/publication.rb
147
+ - lib/arbetsformedlingen/models/qualification.rb
148
+ - lib/arbetsformedlingen/models/salary.rb
149
+ - lib/arbetsformedlingen/models/schedule.rb
150
+ - lib/arbetsformedlingen/output_builder.rb
151
+ - lib/arbetsformedlingen/response.rb
152
+ - lib/arbetsformedlingen/version.rb
153
+ homepage: https://github.com/buren/arbetsformedlingen
154
+ licenses:
155
+ - MIT
156
+ metadata: {}
157
+ post_install_message:
158
+ rdoc_options: []
159
+ require_paths:
160
+ - lib
161
+ - config
162
+ required_ruby_version: !ruby/object:Gem::Requirement
163
+ requirements:
164
+ - - ">="
165
+ - !ruby/object:Gem::Version
166
+ version: '0'
167
+ required_rubygems_version: !ruby/object:Gem::Requirement
168
+ requirements:
169
+ - - ">="
170
+ - !ruby/object:Gem::Version
171
+ version: '0'
172
+ requirements: []
173
+ rubyforge_project:
174
+ rubygems_version: 2.6.11
175
+ signing_key:
176
+ specification_version: 4
177
+ summary: Arbetsförmedlingen API client
178
+ test_files: []