ba_upload 0.2.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1c3d318a7143720be35bdd9460675ffa7eeed8d8ebd592c6c120d00067e3893e
4
- data.tar.gz: 67bbda23c6a00cd00e3b5e1f0310ff27833f782303f37e75e9e7a18b68783986
3
+ metadata.gz: cceefb5de17f2e510d450c83e0ab44a231c23f8024f3b86c1cf4e55c9e2bee32
4
+ data.tar.gz: 29f249da985d7e5213f1cdc8f56f0d1dfc3e352f26823731413ba66506e6672d
5
5
  SHA512:
6
- metadata.gz: 5a797ecf7a9fed238ecf08706adefa033f3ebdce51e2007a9215a77faf0a6d51cce0d143d7c1a1f19b2abf020437459e67cea69207474cacf00eaf96a54831e6
7
- data.tar.gz: ceb746f9650c23e19b46a14c6f1d5d3837b4e80af1c4dba5e684cf744448f60ad88cbb520bb26561775f2c584f6a34d6e4b5794c55846187d8d8ab4565144eec
6
+ metadata.gz: d4ba083e3d90f2d5d750127892b914dba63678a0b4423755b08c455d467cba9e28e02eb4bd5a4d900e19572f258d1befe2109a67af5c144fe10a36e8597552c2
7
+ data.tar.gz: c45d8070153d9aca7842ede6b4dea90098608e6b078bdf4e4370a698e837b021382ddccdfe31b4f47c0121ea76e5e8749e15a86a6f8841f9022b635c7f8d6bca
data/README.md CHANGED
@@ -33,7 +33,8 @@ require 'ba_upload'
33
33
  connection = BaUpload.open_connection(file_path: 'config/Zertifikat-1XXXX.p12', passphrase: 'YOURPASSPHRASE')
34
34
 
35
35
  # Upload a xml-file
36
- connection.upload(file: File.open('/opt/vam-transfer/data/DSP000132700_2016-08-08_05-00-09.xml'))
36
+ file_path = "/opt/vam-transfer/data/DSP000132700_2016-08-08_05-00-09.xml"
37
+ connection.upload(file: file_path))
37
38
 
38
39
  # later cronjob to download all error files
39
40
 
@@ -61,7 +62,7 @@ Save to a file and just run it with the xml file as argument
61
62
 
62
63
  BA provides a often updated Position description databae ("VAM" Berufe). The Gem can help to download it:
63
64
 
64
- ```
65
+ ```ruby
65
66
  connection.misc.each do |link|
66
67
  target = "vendor/ba/#{link.href}"
67
68
  next if File.exist?(target)
@@ -70,6 +71,205 @@ connection.misc.each do |link|
70
71
  end
71
72
  ```
72
73
 
74
+ ### Usage with multiple client certificats
75
+
76
+ Since September 2022, users with multiple client certificats issued to the same email address need to provide their respective partner ID when using the API.
77
+ For user with only one certificat issued to their email address, providing the partner ID is optional.
78
+
79
+ The partner ID can be provided as an optional keyword argument to the `upload`, `error_files` and `misc` methods:
80
+
81
+ ```ruby
82
+ require 'ba_upload'
83
+ connection = BaUpload.open_connection(file_path: 'config/Zertifikat-1XXXX.p12', passphrase: 'YOURPASSPHRASE')
84
+
85
+ connection.upload(file: 'your_file_path', partner_id: 'P000XXXXXX')
86
+ connection.error_files(partner_id: 'P000XXXXXX')
87
+ connection.misc(partner_id: 'P000XXXXXX')
88
+
89
+ ```
90
+
91
+ ## Appendix: Berufe
92
+
93
+ Sooner or later, you have to provide a TitleCode = Vocation "Beruf" for each job. To fetch and process the Berufe, we create a ActiveRecord Model in our database:
94
+
95
+ Here an example of a implementation at Empfehlungsbund. You can also use our [search mask](https://login.empfehlungsbund.de/arbeitsagentur) to search for occupations.
96
+
97
+ We put the "help" / "validation" messages, that we found in the appropriate scopes, too, as "Ausbildungen" and "Duale Studiengänge" need different types of professions.
98
+
99
+ <details>
100
+ <summary>ActiveRecord Model for Ba::Profession</summary>
101
+
102
+ ```ruby
103
+ # migration:
104
+ create_table :ba_professions do |t|
105
+ t.string "bkz"
106
+ t.string "typ"
107
+ t.string "lbkgruppe"
108
+ t.string "hochschulberuf"
109
+ t.string "kuenstler"
110
+ t.string "bezeichnung_nl"
111
+ t.string "bezeichnung_nk"
112
+ t.string "suchname_nl"
113
+ t.datetime "created_at"
114
+ t.datetime "updated_at"
115
+ t.integer "ebene"
116
+ t.integer "qualifikationsniveau"
117
+ t.datetime "deleted_on"
118
+ end
119
+
120
+ class Ba::Profession < ApplicationRecord
121
+ has_many :jobs
122
+
123
+ scope :undeleted, -> { where 'deleted_on is null' }
124
+ scope :berufe, -> { where typ: 'B' }
125
+ scope :ausbildungen, -> { where typ: 'A' }
126
+ scope :sorted, -> { order(Arel.sql('deleted_on is not null, bezeichnung_nl')) }
127
+ # Bei Auswahl von „Ausbildung“ (EducationType=0) sind die Berufe mit dem
128
+ # Qualifikationsniveau 2 zulässig. Zusätzlich sind hier alle Berufe folgender
129
+ # berufskundlicher Gruppen erlaubt: [...]
130
+ scope :reine_ausbildungen, -> {
131
+ where(qualifikationsniveau: 2).or(
132
+ where(lbkgruppe: [1150, 3110, 5130])
133
+ ).ausbildungen
134
+ }
135
+ # Wird ein Stellenangebot vom Typ „Duales Studium“ (EducationType=1) übermittelt, sind der
136
+ # Studiengang und der ggf. vorhandene Ausbildungsberuf getrennt anzugeben. Als
137
+ # Studiengang (Course) sind Berufe mit ausschließlich dem Qualifikationsniveau 4 zulässig.
138
+ # Diese Berufe entstammen alle der berufskundlichen Gruppe 3120 („A Grundständige
139
+ # Studienfächer/-gänge“). Der als Ausbildung (TitleCode) angegebene Beruf darf
140
+ # dementsprechend nicht ausschließlich das Qualifikationsniveau 4 haben.
141
+ scope :duale_studiengaenge, -> { ausbildungen.where ebene: 3, qualifikationsniveau: 4 }
142
+
143
+ def duales_studium?
144
+ ebene == 3 && qualifikationsniveau == 4 && typ == 'A'
145
+ end
146
+
147
+ def self.download_from_ba
148
+ require 'tty/prompt'
149
+ prompt = TTY::Prompt.new
150
+ link = Ba::Distributor.ba_connection.misc.last do |link|
151
+ link.click
152
+ target = "public/ba/#{link.href}"
153
+ response = link.click
154
+ File.open(target, "wb+") { |f| f.write(response.body) }
155
+
156
+ puts "Unzipping vam_beruf_kurz.xml..."
157
+ `unzip -o -d public/ba/ #{target} vam_beruf_kurz.xml`
158
+ end
159
+
160
+ def self.import(path: 'public/ba/vam_beruf_kurz.xml')
161
+ doc = Nokogiri::XML.parse(File.open(path))
162
+ berufe_vorher = Ba::Beruf.undeleted.pluck(:id)
163
+ doc.search('beruf').each do |beruf_doc|
164
+ beruf = where(id: beruf_doc['id']).first_or_initialize
165
+
166
+ beruf.bkz = beruf_doc['bkz']
167
+
168
+ beruf.typ = beruf_doc.at('typ').text == 't' ? 'B' : 'A'
169
+ beruf.qualifikationsniveau = beruf_doc.at('qualifikationsNiveau[niveau]')['niveau']
170
+ beruf_doc.search(*%w[lbkgruppe hochschulberuf ebene kuenstler bezeichnung_nl bezeichnung_nk suchname_nl]).each do |i|
171
+ beruf.send("#{i.name}=", i.text)
172
+ end
173
+ beruf.save
174
+ berufe_vorher.delete(beruf.id)
175
+ end
176
+ Ba::Beruf.where(id: berufe_vorher).update_all deleted_on: Time.zone.now if berufe_vorher.any?
177
+ end
178
+ scope :duale_studiengaenge, -> { where ebene: 3, qualifikationsniveau: 4 }
179
+
180
+ def display_name
181
+ prefix = if deleted_on?
182
+ "[!VERALTET!] "
183
+ end
184
+ if typ == 'A'
185
+ if ebene == 3 && qualifikationsniveau == 4
186
+ "#{prefix}#{bezeichnung_nk} (DUALES STUDIUM/praxisorientiert)"
187
+ else
188
+ "#{prefix}#{bezeichnung_nk} (AUSBILDUNG)"
189
+ end
190
+ else
191
+ "#{prefix}#{bezeichnung_nk}"
192
+ end
193
+ end
194
+
195
+ def as_json(opts = {})
196
+ {
197
+ id: id,
198
+ display_name: display_name
199
+ }
200
+ end
201
+ ```
202
+
203
+ </details>
204
+
205
+ ## Appendix: How to construct a Job-Posting XML file to upload
206
+
207
+ - Download the most recent JobPosting xsd from https://baxml.arbeitsagentur.de/geschuetzt/download/
208
+ - You can visualize the xsd here: http://www.xml-tools.net/schemaviewer.html
209
+ - Now, you can construct the file with xml-builder:
210
+
211
+ <details>
212
+ <summary>Example for constructing a feed using XmlBuilder</summary>
213
+
214
+ ```ruby
215
+ xml = Builder::XmlMarkup.new(indent: 1)
216
+ xml.instruct!
217
+ xml.tag!("HRBAXMLJobPositionPosting") do
218
+ xml.tag!("Header") do
219
+ xml.tag!("SupplierId", SUPPLIER_ID)
220
+ xml.tag!("Timestamp", Time.zone.now.to_s(:db).tr(" ", "T"))
221
+ xml.tag!("Amount", obs.count)
222
+ # F: Full
223
+ # D: Diff
224
+ if @only_jobs
225
+ xml.tag!("TypeOfLoad", "D")
226
+ else
227
+ xml.tag!("TypeOfLoad", "F")
228
+ end
229
+ end
230
+ xml.tag!("Data") do
231
+ jobs.each do |job|
232
+ generate_xml_for_job(xml, job)
233
+ end
234
+
235
+ jobs_to_delete.each do |job|
236
+ xml.tag! "DeleteEntry" do
237
+ xml.tag! "EntryId", id
238
+ end
239
+ end
240
+ end
241
+ end
242
+ xml
243
+ ```
244
+ </details>
245
+
246
+ - Then, you should validate your feed:
247
+
248
+ ```ruby
249
+ xsd = Nokogiri::XML::Schema(File.open("vendor/ba/HRBAXML_JobPosition_Current.xsd"))
250
+ doc = Nokogiri::XML(xml.to_s)
251
+ xsd.validate(doc)
252
+ ```
253
+
254
+ - Then, you can put that into a file - so you will need to generate a filename **according to the spec**:
255
+
256
+ <details>
257
+ <summary>Generate a filename</summary>
258
+ ```ruby
259
+ # for historic reasons, you could transmit a bunch of files with the same timestamp using an index/offset, but usually, just putting 0 here should be enought
260
+ index = 0
261
+ number_of_feeds_to_push_now = 1
262
+ ended = index == (number_of_feeds_to_push_now - 1)
263
+ flag = ended ? "E" : "C"
264
+ date = Time.zone.now.strftime "%Y-%m-%d_%H-%M-%S_F#{'%03d' % (index + 1)}#{flag}"
265
+ "DS#{SUPPLIER_ID}_#{date}.xml"
266
+ ```
267
+ </details>
268
+
269
+ - Upload the file using this Gem. You should wait a "couple of minutes" (tip: enqueue a activeJob for 10 minutes later), to fetch the resulting **error file**, and analyse that.
270
+
271
+
272
+
73
273
  ## License
74
274
 
75
275
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -14,23 +14,26 @@ module BaUpload
14
14
  @m.cert = @cert.path
15
15
  end
16
16
 
17
- def upload(file: nil)
18
- m.get 'https://hrbaxml.arbeitsagentur.de/in/'
17
+ def upload(file: nil, partner_id: nil)
18
+ url = base_url(partner_id) + "in/"
19
+ m.get url
19
20
  form = m.page.forms.first
20
21
  form.file_uploads.first.file_name = file
21
22
  form.submit
22
23
  end
23
24
 
24
- def error_files
25
- m.get 'https://hrbaxml.arbeitsagentur.de/'
25
+ def error_files(partner_id: nil)
26
+ url = base_url(partner_id)
27
+ m.get url
26
28
  links = m.page.links_with(text: /ESP|ESV/)
27
29
  links.map do |link|
28
30
  ErrorFile.new(link)
29
31
  end
30
32
  end
31
33
 
32
- def misc
33
- m.get 'https://hrbaxml.arbeitsagentur.de/'
34
+ def misc(partner_id: nil)
35
+ url = base_url(partner_id)
36
+ m.get url
34
37
  m.page.links_with(text: /sonstiges/).first.click
35
38
  m.page.links.reject { |i| i.href[/^\?|mailto:/] || i.href == '/' }
36
39
  end
@@ -38,5 +41,13 @@ module BaUpload
38
41
  def shutdown
39
42
  m.shutdown
40
43
  end
44
+
45
+ private
46
+
47
+ def base_url(partner_id)
48
+ url = "https://hrbaxml.arbeitsagentur.de/"
49
+ url += "daten/#{partner_id}/" unless partner_id.nil?
50
+ url
51
+ end
41
52
  end
42
53
  end
@@ -1,3 +1,3 @@
1
1
  module BaUpload
2
- VERSION = "0.2.0"
2
+ VERSION = "0.4.0"
3
3
  end
data/lib/ba_upload.rb CHANGED
@@ -1,6 +1,11 @@
1
1
  require "ba_upload/version"
2
2
  require "openssl"
3
3
 
4
+ if OpenSSL::VERSION >= '3.0.0'
5
+ # import legacy
6
+ OpenSSL::Provider.load("legacy")
7
+ end
8
+
4
9
  module BaUpload
5
10
  def self.export_certificate(file_path:, passphrase:)
6
11
  cert = OpenSSL::PKCS12.new(File.read(file_path), passphrase)
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ba_upload
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.0
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Stefan Wienert
8
8
  autorequire:
9
9
  bindir: exe
10
10
  cert_chain: []
11
- date: 2021-02-03 00:00:00.000000000 Z
11
+ date: 2023-09-26 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: mechanize
@@ -90,8 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
90
  - !ruby/object:Gem::Version
91
91
  version: '0'
92
92
  requirements: []
93
- rubyforge_project:
94
- rubygems_version: 2.7.6
93
+ rubygems_version: 3.2.33
95
94
  signing_key:
96
95
  specification_version: 4
97
96
  summary: Upload API for Bundesagentur fuer Arbeit (hrbaxml.arbeitsagentur)