51 lines
2.4 KiB
Ruby
51 lines
2.4 KiB
Ruby
#
|
|
# Rechnungen aus verschachteltem JSON in eine Datei mit Satzarten (Datei-, Kopf-, Positions-, Schlusssatz)
|
|
#
|
|
# bin/etl run examples/rechnungen_satzarten.rb [--out=tmp]
|
|
#
|
|
require 'fileutils'
|
|
require 'datapipeline'
|
|
|
|
DATA = File.expand_path('data', __dir__)
|
|
out = DataPipeline.params.fetch(:out, 'tmp')
|
|
FileUtils.mkdir_p(out)
|
|
|
|
# Feld: [Name, Typ, Länge, Pflicht]; Typ AN Text, N Zahl (11.2 = 9 Vor-, 2 Nachkommastellen), DAT YYYYMMDD
|
|
LAYOUT = DataPipeline::Layout.new do
|
|
record 'DATEI', [['SATYP', 'AN', 5, true], ['ERSTELLT', 'DAT', 8, true], ['ANZAHL', 'N', 6, true]]
|
|
record 'KOPF', [['SATYP', 'AN', 5, true], ['LFDNR', 'N', 6, true], ['BELNR', 'AN', 10, true],
|
|
['DATUM', 'DAT', 8, true], ['KUNNR', 'AN', 10, true], ['SUMME', 'N', '11.2', true]]
|
|
record 'POS', [['SATYP', 'AN', 5, true], ['LFDNR', 'N', 6, true], ['BELNR', 'AN', 10, true],
|
|
['POSNR', 'N', 3, true], ['TEXT', 'AN', 30], ['BETRAG', 'N', '11.2', true]]
|
|
record 'ENDE', [['SATYP', 'AN', 5, true], ['ANZAHL', 'N', 6, true]]
|
|
end
|
|
|
|
DataPipeline.new do
|
|
read container(:json, file: "#{DATA}/rechnungen.json")
|
|
|
|
# 1. eine Rechnung -> Kopfsatz + n Positionssätze
|
|
map do |rechnung|
|
|
positionen = rechnung['positionen'].each_with_index.map do |pos, i|
|
|
{ 'satyp' => 'POS', 'belnr' => rechnung['nr'], 'posnr' => format('%03d', i + 1),
|
|
'text' => pos['text'], 'betrag' => format('%.2f', pos['menge'] * pos['preis']) }
|
|
end
|
|
kopf = { 'satyp' => 'KOPF', 'belnr' => rechnung['nr'], 'datum' => rechnung['datum'].delete('-'),
|
|
'kunnr' => rechnung['kunde'], 'summe' => format('%.2f', positionen.sum { |p| p['betrag'].to_f }) }
|
|
[kopf, *positionen]
|
|
end
|
|
|
|
# 2. laufende Nummer über alle Kopf- und Positionssätze
|
|
map { |row, ctx| row.merge('lfdnr' => format('%06d', ctx.index + 1)) }
|
|
|
|
# 3. gegen das Layout prüfen: zu langer Text wird gemeldet, die Zeile bleibt
|
|
validate LAYOUT, warn: true
|
|
|
|
# Datei- und Schlusssatz kennen die Anzahl der Datensätze
|
|
header { |ctx| { 'satyp' => 'DATEI', 'erstellt' => Date.today.strftime('%Y%m%d'), 'anzahl' => format('%06d', ctx.written) } }
|
|
trailer { |ctx| { 'satyp' => 'ENDE', 'anzahl' => format('%06d', ctx.written) } }
|
|
|
|
# Felder in der Reihenfolge des Layouts; Positionen zusätzlich als NDJSON
|
|
write container(:csv, file: "#{out}/rechnungen.txt", layout: LAYOUT, csv: { headers: false, col_sep: '|' })
|
|
write container(:json, file: "#{out}/positionen.ndjson"), only: ->(row) { row['satyp'] == 'POS' }
|
|
end.run
|