138 lines
5.7 KiB
Ruby
138 lines
5.7 KiB
Ruby
|
#!/usr/bin/env ruby
|
||
|
require 'cmxl' # for parsing the MT940 file
|
||
|
require 'roo' # for parsing the Excel file
|
||
|
require 'humanize' # for converting numbers to words
|
||
|
|
||
|
Humanize.configure do |config|
|
||
|
config.default_locale = :de # [:en, :es, :fr, :tr, :de, :id], default: :en
|
||
|
config.decimals_as = :number # [:digits, :number], default: :digits
|
||
|
end
|
||
|
|
||
|
# usage ./beitragsquittung.rb <MT940 file> <Excel file> <member id>
|
||
|
# the member ID is optional. If not provided, all members are processed
|
||
|
# the time range is used from the MT940 file
|
||
|
|
||
|
def generate_reciept(mitgliedsnummer, name, vorname, strasse, hausnummer, plz, ort, betrag, betrag_in_worten, start_datum, end_datum, payments)
|
||
|
puts "Generating reciept for #{mitgliedsnummer} #{name} #{vorname} #{strasse} #{hausnummer} #{plz} #{ort} #{betrag} #{betrag_in_worten} #{start_datum} #{end_datum}"
|
||
|
|
||
|
# create a csv file with the payments
|
||
|
CSV.open("payments.csv", "wb") do |csv|
|
||
|
csv << ["datum", "betrag", "type", "verzicht"]
|
||
|
payments.each do |payment|
|
||
|
csv << [payment[:date], payment[:amount], payment[:type], payment[:verzicht]]
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# call latex to generate the pdf
|
||
|
system("pdflatex \"\\newcommand{\\vorname}{#{vorname}} \\newcommand{\\nachname}{#{name}} \\newcommand{\\strasse}{#{strasse + " " + hausnummer.to_s}} \\newcommand{\\plz}{#{plz}} \\newcommand{\\ort}{#{ort}} \\newcommand{\\betrag}{#{betrag}} \\newcommand{\\betraginworten}{#{betrag_in_worten}} \\newcommand{\\startdatum}{#{start_datum}} \\newcommand{\\findatum}{#{end_datum}} \\newcommand{\\mitgliedsnummer}{#{mitgliedsnummer}} \\input{document.tex}\"")
|
||
|
end
|
||
|
|
||
|
def get_payments_from_member_id(mitgliedsnummer)
|
||
|
|
||
|
Cmxl.config[:statement_separator] = /\n-.\n/m
|
||
|
Cmxl.config[:raise_line_format_errors] = true
|
||
|
|
||
|
# get the filename from the command line arguments
|
||
|
filename = ARGV[0]
|
||
|
|
||
|
# check if the filename is valid or not presented
|
||
|
if filename.nil? || !File.exists?(filename)
|
||
|
puts "Enter the filename of the MT940 statement to parse"
|
||
|
filename = gets.chomp
|
||
|
end
|
||
|
|
||
|
# parse the MT940 file
|
||
|
statements = Cmxl.parse(File.read(filename), :encoding => 'ISO-8859-1')
|
||
|
|
||
|
# The statement contains transactions called Mitgliedsbeitrag. They are labled like this: "MREF"=>"Mitgliedsbeitrag Nr. 17" where 17 is the mitgliedsnummer.
|
||
|
# for each payment of the mitgliedsbeitrag create an entry with the date and amount in the following datastructure
|
||
|
payments = []
|
||
|
|
||
|
statements.each do |s|
|
||
|
s.transactions.each do |t|
|
||
|
reference = ""
|
||
|
t.sepa.each do |sf|
|
||
|
# add the second entry of the sepa reference (sf) to the reference string if the first entry contans "MREF"
|
||
|
if sf[0] == "MREF"
|
||
|
reference = sf[1]
|
||
|
end
|
||
|
|
||
|
end
|
||
|
|
||
|
# if the mitgliedsnummer is a single digit number, the reference is "Mitgliedsbeitrag Nr. 01" and not "Mitgliedsbeitrag Nr. 1"
|
||
|
# so we have to check for both cases
|
||
|
if reference.include?("Mitgliedsbeitrag Nr. #{mitgliedsnummer.to_s.rjust(2, '0')}") || reference.include?("Mitgliedsbeitrag Nr. #{mitgliedsnummer}")
|
||
|
# puts t.entry_date
|
||
|
# puts t.amount
|
||
|
payment = {
|
||
|
date: t.entry_date,
|
||
|
amount: t.amount,
|
||
|
type: "Mitgliedsbeitrag",
|
||
|
verzicht: "nein"
|
||
|
}
|
||
|
payments << payment
|
||
|
# puts
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
# get the start and end date of the statements
|
||
|
start_datum = statements[0].opening_balance.date.strftime("%d.%m.%Y")
|
||
|
end_datum = statements[statements.length() -1].closing_balance.date.strftime("%d.%m.%Y")
|
||
|
|
||
|
return payments, start_datum, end_datum
|
||
|
end
|
||
|
|
||
|
def get_member_details_from_mitgliedsnummer(mitgliedsnummer)
|
||
|
# get the member details from the excel file
|
||
|
# the member details are stored in the first sheet
|
||
|
# the first row contains the headings of the columns
|
||
|
# the second row contains the values for the first member
|
||
|
# the headings are: Nr., Nachname, Vorname, Geburtsdatum, Straße, Hausnummer, Postleitzahl, Ort
|
||
|
# the values are: 1, Mustermann, Max, 01.01.1970, Musterstraße, 1, 12345, Musterstadt
|
||
|
xlsx = Roo::Spreadsheet.open(ARGV[1])
|
||
|
|
||
|
# create data structure
|
||
|
member = {
|
||
|
name: xlsx.sheet(0).row(mitgliedsnummer+1)[1],
|
||
|
vorname: xlsx.sheet(0).row(mitgliedsnummer+1)[2],
|
||
|
strasse: xlsx.sheet(0).row(mitgliedsnummer+1)[4],
|
||
|
hausnummer: xlsx.sheet(0).row(mitgliedsnummer+1)[5],
|
||
|
plz: xlsx.sheet(0).row(mitgliedsnummer+1)[6],
|
||
|
ort: xlsx.sheet(0).row(mitgliedsnummer+1)[7]
|
||
|
}
|
||
|
return member
|
||
|
end
|
||
|
|
||
|
mitgliedsnummer = ARGV[2].to_i
|
||
|
if mitgliedsnummer.nil?
|
||
|
# get number of members from the excel file it is the first value of the last row
|
||
|
xlsx = Roo::Spreadsheet.open(ARGV[1])
|
||
|
anzahl_members = xlsx.sheet(0).last_row[0]
|
||
|
puts "Number of members: #{anzahl_members}"
|
||
|
|
||
|
# loop over all members
|
||
|
for mitgliedsnummer in 1..anzahl_members
|
||
|
puts "Processing member #{mitgliedsnummer}"
|
||
|
get_payments_from_member_id(mitgliedsnummer)
|
||
|
generate_reciept(mitgliedsnummer, name, vorname, strasse, plz, ort, betrag, betrag_in_worten, start_datum, end_datum)
|
||
|
end
|
||
|
else
|
||
|
# get the payments from the MT940 file
|
||
|
payments, start_datum, end_datum = get_payments_from_member_id(mitgliedsnummer)
|
||
|
puts "Payments:"
|
||
|
total_payment = 0
|
||
|
payments.each do |payment|
|
||
|
puts payment[:date] + " " + payment[:amount].to_s
|
||
|
total_payment += payment[:amount]
|
||
|
end
|
||
|
|
||
|
# convert the total payment to a string in german
|
||
|
betrag_in_worten = total_payment.humanize
|
||
|
puts "Total payment:" + total_payment.to_s + " in worten: " + betrag_in_worten
|
||
|
puts
|
||
|
|
||
|
member = get_member_details_from_mitgliedsnummer(mitgliedsnummer)
|
||
|
generate_reciept(mitgliedsnummer, member[:name], member[:vorname], member[:strasse], member[:hausnummer], member[:plz], member[:ort], total_payment, betrag_in_worten, start_datum, end_datum, payments)
|
||
|
end
|