# # Produktiver Lauf: Buchungen prüfen, Summe und Saldo abgleichen, wiederholbar in die Buchhaltungs-Datenbank # und als CSV in Windows-1252 für das Zielsystem schreiben; mit Sperre, Laufhistorie und Slack bei Problemen # # bin/etl run examples/buchungen_export.rb [--out=tmp] [--dry-run] # require 'fileutils' require 'datapipeline' DATA = File.expand_path('data', __dir__) out = DataPipeline.params.fetch(:out, 'tmp') FileUtils.mkdir_p(out) # Zugangsdaten stehen nicht im Skript, sondern in der Verbindungsdatei (Default: config/connections.yml) DataPipeline.connections_file = File.expand_path('config/connections.yml', __dir__) # Slack nur, wenn ein Token gesetzt ist; so fällt ein fehlender Token schon vor dem Lauf auf slack = DataPipeline::Slack.new(channel: '#etl') if ENV['SLACK_TOKEN'] DataPipeline.new do name 'buchungen_export' lock "#{out}/.buchungen.lock" # läuft schon ein Export, bricht dieser ab (Exit-Code 3) history "#{out}/runs.ndjson" # eine JSON-Zeile je Lauf, auch nach Abbruch read container(:csv, file: "#{DATA}/buchungen.csv", csv: { col_sep: ';' }) # 1. prüfen: ohne on_error bricht ein Fehler den Lauf ab, es entsteht keine halbe Buchung validate 'belnr' => { required: true, format: /\A\d{10}\z/ }, 'konto' => { required: true, format: /\A\d+\z/ }, 'betrag' => { required: true, type: :decimal } # 2. umbauen mit Kurzschritten und einem map default 'text' => 'ohne Text' rename 'text' => 'buchungstext' cast 'buzei' => :integer, 'datum' => :date, 'betrag' => :decimal map do |row| row.merge('shkzg' => row['betrag'].negative? ? 'H' : 'S', 'dmbtr' => format('%.2f', row['betrag'].abs)) end select %w[belnr buzei datum konto shkzg dmbtr betrag buchungstext] # 3. abgleichen, bevor etwas geschrieben wird totals 'betrag' reconcile do |stats| quelle, ergebnis = stats.total('betrag', :read), stats.total('betrag') next "Summe geändert: Quelle #{DataPipeline.decimal(quelle)}, Ergebnis #{DataPipeline.decimal(ergebnis)}" if quelle != ergebnis "Soll und Haben ungleich, Saldo #{DataPipeline.decimal(ergebnis)}" unless ergebnis.zero? end # 4. schreiben: Upsert macht den Lauf wiederholbar; die CSV-Datei im Zeichensatz des Zielsystems write connection(:buchhaltung) write container(:csv, file: "#{out}/buchungen_sap.csv", encoding: 'Windows-1252', csv: { col_sep: ';' }, columns: %w[belnr buzei datum konto shkzg dmbtr buchungstext]) on_finish do |stats| puts "#{stats.name}: #{stats}" slack&.send_message("*#{stats.name}*: #{stats}") unless stats.ok? end end.run