ba_upload 0.3.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: 15804867b016b8c972bbb18385a2d1caec4a9729776ea2d1877dcddaeb73b15d
4
- data.tar.gz: 930ef0bf4b3817c43616873144a43e46e482d5baa1efc5f5db63038dd44fd6e0
3
+ metadata.gz: cceefb5de17f2e510d450c83e0ab44a231c23f8024f3b86c1cf4e55c9e2bee32
4
+ data.tar.gz: 29f249da985d7e5213f1cdc8f56f0d1dfc3e352f26823731413ba66506e6672d
5
5
  SHA512:
6
- metadata.gz: 4b906aa0b7c5860142c9c2d01f1f6955b8ccf8217448fd9164e293081f697ff9d43de2411989ff266a4729eb79a087a73fd56124e79159ec146fb29bd72fe6a2
7
- data.tar.gz: fa9aaa0351fdf5758cb2a770e2b0105c6f599bae516f416ffa1ef25de58727f20b0180574086079474050042b253789dded4d58ce78e0edb55f81c146107e2cf
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
 
@@ -87,6 +88,188 @@ connection.misc(partner_id: 'P000XXXXXX')
87
88
 
88
89
  ```
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
+
90
273
  ## License
91
274
 
92
275
  The gem is available as open source under the terms of the [MIT License](http://opensource.org/licenses/MIT).
@@ -1,3 +1,3 @@
1
1
  module BaUpload
2
- VERSION = "0.3.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.3.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: 2022-09-08 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,7 +90,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
90
90
  - !ruby/object:Gem::Version
91
91
  version: '0'
92
92
  requirements: []
93
- rubygems_version: 3.0.3
93
+ rubygems_version: 3.2.33
94
94
  signing_key:
95
95
  specification_version: 4
96
96
  summary: Upload API for Bundesagentur fuer Arbeit (hrbaxml.arbeitsagentur)