#!/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 # 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.exist?(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].to_s + " " + 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