Girocode Zufügen
Zuletzt geändert: 09.11.2023 07:51

GiroCode in eigene Berichte #

EULANDA® unterstützt in den mitgelieferten Rechnungs-Formularen den GiroCode. Dies ist ein QR-Code der die Zahlungsinformationen enthält. Also Dinge wie Rechnungsnummer, Betrag, Empfängerbank usw. Viele Banken-Apps unterstützen das Einscannen des GiroCodes, so dass der Zahlende nichts mehr manuell eingeben muss. Er kann die gescannten Daten kontrollieren und muss dann nur noch in der App die Zahlung mit seinen üblichen Verfahren freigeben.

Hat man jedoch ein eigenes altes Formular, indem der GiroCode noch nicht enthalten ist, so kann dieser nun mit Hilfe diesen Anleitung nachgerüstet werden.

Nachrüstung des GiroCode #

HINWEIS

Es wegen Kenntnisse im Umgang mit dem Berichts-Designer vorausgesetzt. Wenn Sie sich nicht zutrauen, diese Anleitung umzusetzen, sprechen Sie den Support an. Wir bieten die Integration in Ihr Rechnungsformular zu pauschal 65 EUR an.

  1. Öffnen Sie zunächst das gewünschte Rechnungsformular im Berichts-Designer.
  2. Wählen Sie im Formular die Stelle aus, an der Sie den GiroCode positionieren möchten. Es muss ein normales nicht datenbankgebundenes Bild sein. Das Bild muss den Namen GiroCodeImageNetto haben, wenn der GiroCode bei Nettorechnungen verwendet werden soll. Bei bruttoorientierten Rechnungen entsprechend GiroCodeImageBrutto. Ziehen Sie das Bildelement auf die gewünschte Größe und stellen Sie dann die folgenden Eigenschaften ein:
    • Zentriert
    • Seitenverhältnis beibehalten
    • Dehnen
  3. Sie können über die Eigenschaft Bild... ein Platzhalter bzw. Dummy-Bild laden, damit Sie später sehen, worum es sich bei dem Element handelt.
  4. Legen Sie im Bereich Berechnungen auf dem Hauptformular Rechnungskopf unter Programme die Funktion GiroCodePayload an. Zunächst im Menü Ansicht die Option Verwendete Module aktivieren. Hierzu im linken oberen Bereich den Eintrag Programme anwählen, und dann rechts mit der rechten Maus den Eintrag Neue Prozedur anwählen. Löschen Sie im unteren Fenster den kompletten Prozedur-Rumpf und fügen Sie an dessen Stelle das folgende Skript ein:
function GiroCodePayload:String;
var
  Delimiter : String;
  StrValue : String;
  Payload : String;
  MagicCode: String;
  PZ : String;
  x : Integer;
  loop : Integer;
  Ch : Char;
  GiroCodeEncoding : String;
  UseSecurePurposeMessage : Boolean;
  PurposeMessage : String;
  SecurePurposeMessage : String;
  TempStr : String;
  PaymentAmount : String;
  i : Integer;
begin
  Delimiter :=  Chr(10);
  MagicCode :=  '271500';
  
  // false = PurposeMessage otherwise SecurePurposeMessage
  UseSecurePurposeMessage := false;

  // If UseSecurePurposeMessage = false this is used as template otherwise invoice number 
  PurposeMessage := 'RE: $$INVOICENO$$ ZWECK: $$OBJECT$$';

  // Patch allowed variables, so template could be an external string in future     
  PurposeMessage := ReplaceAllStr(PurposeMessage, '$$INVOICENO$$', RechnungsKopf['KOPFNUMMER']);      
  PurposeMessage := ReplaceAllStr(PurposeMessage, '$$OBJECT$$', RechnungsKopf['OBJEKT']);
  PurposeMessage := ReplaceAllStr(PurposeMessage, '$$INVOICENAME$$', RechnungsKopf['VORGANGNAME']);
  PurposeMessage := ReplaceAllStr(PurposeMessage, '$$VAT$$', RechnungsKopf['USTID']);
  PurposeMessage := ReplaceAllStr(PurposeMessage, '$$TAXNO$$', RechnungsKopf['STEUERNUMMER']);
  PurposeMessage := ReplaceAllStr(PurposeMessage, '$$ORDERNO$$', RechnungsKopf['BESTELLNUMMER']);
  PurposeMessage := ReplaceAllStr(PurposeMessage, '$$DEBITNO$$', RechnungsKopf['FIBUKONTO']);
  PurposeMessage := ReplaceAllStr(PurposeMessage, '$$CURRENCY$$', RechnungsKopf['WAEHRUNG']);
   
  TempStr := '';
  for i := 1 to Length(PurposeMessage) do
  begin
    // reference: https://www.bundesbank.de/resource/blob/848450/74179e7094d69dca3632eb8605db95c2/mL/technische-spezifikationen-sepa-ueberweisungen-2022-11-data.pdf

    // GERMAN Substitiutions 
    // if Copy(PurposeMessage,i,1)  = 'Ä' then TempStr := TempStr + 'Ae' else  
    // if Copy(PurposeMessage,i,1) = 'Ö' then TempStr := TempStr + 'Oe' else
    // if Copy(PurposeMessage,i,1) = 'Ü' then TempStr := TempStr + 'Ue' else
    // if Copy(PurposeMessage,i,1) = 'ä' then TempStr := TempStr + 'ae' else  
    // if Copy(PurposeMessage,i,1) = 'ö' then TempStr := TempStr + 'oe' else
    // if Copy(PurposeMessage,i,1) = 'ü' then TempStr := TempStr + 'ue' else
    // if Copy(PurposeMessage,i,1) = 'ß' then TempStr := TempStr + 'ss' else
    
    // Germans chars allowed
    if Copy(PurposeMessage,i,1) = 'Ä' then TempStr := TempStr + 'Ä' else
    if Copy(PurposeMessage,i,1) = 'Ö' then TempStr := TempStr + 'Ö' else
    if Copy(PurposeMessage,i,1) = 'Ü' then TempStr := TempStr + 'Ü' else
    if Copy(PurposeMessage,i,1) = 'ä' then TempStr := TempStr + 'ä' else
    if Copy(PurposeMessage,i,1) = 'ö' then TempStr := TempStr + 'ö' else
    if Copy(PurposeMessage,i,1) = 'ü' then TempStr := TempStr + 'ü' else
    if Copy(PurposeMessage,i,1) = 'ß' then TempStr := TempStr + 'ß' else 
       
    // Symbols allowed    
    if Copy(PurposeMessage,i,1) = '/' then TempStr := TempStr + '/' else
    if Copy(PurposeMessage,i,1) = '?' then TempStr := TempStr + '?' else
    if Copy(PurposeMessage,i,1) = ':' then TempStr := TempStr + ':' else
    if Copy(PurposeMessage,i,1) = '(' then TempStr := TempStr + '(' else        
    if Copy(PurposeMessage,i,1) = ')' then TempStr := TempStr + ')' else
    if Copy(PurposeMessage,i,1) = '.' then TempStr := TempStr + '.' else
    if Copy(PurposeMessage,i,1) = ',' then TempStr := TempStr + ',' else
    if Copy(PurposeMessage,i,1) = '+' then TempStr := TempStr + '+' else
    if Copy(PurposeMessage,i,1) = '-' then TempStr := TempStr + '-' else    
    if Copy(PurposeMessage,i,1) = '''' then TempStr := TempStr + '''' else 
    
    // Additional symbols allowed 
    if Copy(PurposeMessage,i,1) = '*' then TempStr := TempStr + '*' else    
    if Copy(PurposeMessage,i,1) = '&' then TempStr := TempStr + '&' else    
    if Copy(PurposeMessage,i,1) = '$' then TempStr := TempStr + '$' else
    if Copy(PurposeMessage,i,1) = '%' then TempStr := TempStr + '%' else
    
    // Substitiutions 
    if Copy(PurposeMessage,i,1) = '_' then TempStr := TempStr + '-' else
    if Copy(PurposeMessage,i,1) = ';' then TempStr := TempStr + ',' else
    if Copy(PurposeMessage,i,1) = '=' then TempStr := TempStr + ':' else
    if Copy(PurposeMessage,i,1) = '#' then TempStr := TempStr + '*' else
    if Copy(PurposeMessage,i,1) = '{' then TempStr := TempStr + '(' else        
    if Copy(PurposeMessage,i,1) = '}' then TempStr := TempStr + ')' else
    if Copy(PurposeMessage,i,1) = '[' then TempStr := TempStr + '(' else        
    if Copy(PurposeMessage,i,1) = ']' then TempStr := TempStr + ')' else
    
    // Sets allowed
    if (Copy(PurposeMessage,i,1) >= 'A') and (Copy(PurposeMessage,i,1) <= 'Z') then TempStr := TempStr + Copy(PurposeMessage,i,1) else
    if (Copy(PurposeMessage,i,1) >= 'a') and (Copy(PurposeMessage,i,1) <= 'z') then TempStr := TempStr + Copy(PurposeMessage,i,1) else    
    if (Copy(PurposeMessage,i,1) >= '0') and (Copy(PurposeMessage,i,1) <= '9') then TempStr := TempStr + Copy(PurposeMessage,i,1) else
        
    // All other is not allowed and gets blanks      
    TempStr := TempStr + ' ';          
  end; 
  PurposeMessage := ReplaceAllStr(TempStr, '  ', ' ');  
   
  
  SecurePurposeMessage := '';
  for i:=1 to length(RechnungsKopf['KopfNummer']) do
  begin
    Ch := Copy(RechnungsKopf['KopfNummer'], i, 1);
    StrValue := GiroCodeValue(Ch);
    if StrValue <> '' then SecurePurposeMessage := SecurePurposeMessage + StrValue;    
  end;  
  SecurePurposeMessage := SecurePurposeMessage + MagicCode;
  
  x := 0;
  while Length(SecurePurposeMessage) > 9 do
  begin
    x := StrToInt(Copy(SecurePurposeMessage, 1, 9)) mod 97;
    SecurePurposeMessage := IntToStr(x) + Copy(SecurePurposeMessage, 10, 255);
  end;
  x := 98 - (StrToInt(SecurePurposeMessage) mod 97);
  PZ := SetStr('R', '0', IntToStr(x), 2); 
  SecurePurposeMessage := 'RF'+PZ+RechnungsKopf['KopfNummer'];

  // 0 = Ansi, 1=UTF8
  GirocodeEncoding := '1';      
      
  PurposeMessage := Copy(PurposeMessage, 1, 60);   
    
  if UseSecurePurposeMessage then
    PurposeMessage := ''
  else
    SecurePurposeMessage := '';  
    
  PaymentAmount := RechnungsKopf['VkBrutto'];
  // If no commas are found, leave it as it was
  // If a decimal separator is found proceed as follows:
  //   1. Delete all zeros in the cent range
  //   2. Convert comma to decimal point to not depend on regional settings
  //   3. If the decimal point is the last character, it will be deleted    
  if (Pos(',',PaymentAmount) > 0) or (Pos('.',PaymentAmount) > 0) then  
    PaymentAmount := KillFiller('R','.', ReplaceAllStr(KillFiller('R','0',PaymentAmount),',','.'));     
        
  Payload := 
    'BCD' + Delimiter +
    '002' + Delimiter +
    GiroCodeEncoding + Delimiter +
    'SCT' + Delimiter +
    Grundwerte['Bank.1.BIC'] + Delimiter +             
    Grundwerte['Firmenstamm.Firma'] + Delimiter +
    Grundwerte['Bank.1.IBAN'] + Delimiter +
    'EUR' + PaymentAmount + Delimiter +                              
    'EPAY' + Delimiter + 
    SecurePurposeMessage + Delimiter +
    PurposeMessage + Delimiter;
              
  Result := Payload;
end;  
  1. Auf die gleiche Weise legen Sie die Funktion GiroCodeValue an:
function GiroCodeValue(Value:String):String;
begin
  // Converts a 1-character string to a numeric value valid for the girocode 
  // as a string, which can then be added to a total string number.
  if Length(Value) = 1 then
  begin
    Value := Uppercase(Value);
    if (value >= '0') and (value <= '9') then Result := Value else
    if Value = 'A' then Result := '10' else 
    if Value = 'B' then Result := '11' else
    if Value = 'C' then Result := '12' else
    if Value = 'D' then Result := '13' else
    if Value = 'E' then Result := '14' else
    if Value = 'F' then Result := '15' else      
    if Value = 'G' then Result := '16' else
    if Value = 'H' then Result := '17' else    
    if Value = 'I' then Result := '18' else
    if Value = 'J' then Result := '19' else
    if Value = 'K' then Result := '20' else
    if Value = 'L' then Result := '21' else
    if Value = 'M' then Result := '22' else
    if Value = 'N' then Result := '23' else
    if Value = 'O' then Result := '24' else
    if Value = 'P' then Result := '25' else
    if Value = 'Q' then Result := '26' else
    if Value = 'R' then Result := '27' else
    if Value = 'S' then Result := '28' else
    if Value = 'T' then Result := '29' else
    if Value = 'U' then Result := '30' else
    if Value = 'V' then Result := '31' else
    if Value = 'W' then Result := '32' else
    if Value = 'X' then Result := '33' else
    if Value = 'Y' then Result := '34' else
    if Value = 'Z' then Result := '35' else
    Result := '';
  end else Result := '';                              
end;
  1. Legen Sie ebenfalls eine Ereignis-Behandlung GiroCodeOnPrint an. Hierbei ist der Abschnitt zu berücksichtigen. auf dem Sie ihren GiroCode zuvor als Bild eingefügt haben. Dies kann der Haupt: Rechnungskopf oder Nettosummer: Rechnungskopf sein. Wählen Sie die entsprechende Lasche unten an. Schalten Sie im Menü Ansicht auf die Option Ereignisse um. Sie erhalten dann im linken oberen Teil alle in dem Berichts-Abschnitt verwendeten Elemente angezeigt. Wählen Sie dort das Bild GiroCodeImageNetto aus. Im rechten Teil werden dann alle möglichen Ereignisse zu dem Element angezeigt. Mit der rechten Maus auf dem Eintrag OnPrint den Punkt Neu anwählen und dann wie zuvor das folgende Skript einfügen.
procedure GiroCodeImageNettoOnPrint;
begin
  ImageBarcode(Report, GirocodeImageNetto, 'QRCODE', GiroCodePayload, '');
end;

Testen des GiroCode #

Vor dem Speichern des Formulars testen Sie dies in der Vorschau. Wenn ein Fehler angezeigt wird, wurde bei der Umsetzung etwas nicht berücksichtigt. Kontrollieren Sie dann die Schritte erneut.

Prüfen Sie auch den GiroCode mit einer Smartphone-App. Es gibt diverse QR-Code Apps, hiermit lassen sich dann die Klartextdaten eines QR-Codes anzeigen, so dass Sie IBAN, Betrag und Rechnungsnummer kontrollieren können. Der Betrag wird im GiroCode ohne Komma dargestellt, also nicht über die großen Beträge wundern.

Wenn diese Prüfung das gewünschte Ergebnis gebracht hat, ist es empfehlenswert eine Überweisung mit der eigenen Bank zu testen. Diese wird natürlich nicht abgesendet, aber dann können Sie die vorausgefüllten Felder genau überprüfen.

Hilfestellung #

HINWEIS

Bei der Umsetzung in eigene Formulare helfen wir gerne, wir berechnen hier jedoch unsere GiroCode-Pauschale, wie oben angegeben.

Vielleicht hilft ihnen aber auch schon das originale Rechnungs-Formular.

Das Bildelement ist im den Original-Formular bei der nettoorientierten Rechnung im Bereich der Endsumme positioniert. Der angezeigte QR-Code ist ein Dummy, hat also keine Auswirkung auf den beim Ausdruck verwendeten GiroCode. Hier könnten Sie ein beliebiges Bild einfügen. Durch einen QR-Code ist aber eher ersichtlich, worum es sich hier handelt.

image-20231109074950488

Wenn Sie dies öffnen und auf die Karteikarte Berechnung wechseln, können Sie sich diese Skripts und vor allem wo diese stehen müssen, genau ansehen. Anbei ein Bild hierzu:

image-20231109074536691

Ähnlich verhält es sich dann beim Ereignis. Hier dazu das passende Bild bei einer nettoorientierten Rechnung.

image-20231109074721811

Funktionsweise #

Die Ereignisbehandlung wird aufgerufen, sobald das eingefügte leere Bild mit dem Elementen-Namen GiroCode gedruckt werden soll. Die Prozedur ImageBarcode erzeugt einen Barcode und fügt diesen in den Bild-Container ein. Der Name des Elements wird als zweiter Parameter angegeben. Im dritten Parameter wird der Typ des Barcodes angegeben, hier QRCODE. Der vierte Parameter enthält die Klartext-Information, welche in einen QR-Code gewandelt werden soll. Dies ist der Funktionsname der oben eingefügten Funktion. Der letzte Parameter wird als leerer String übergeben, er dient zur Übergabe von Optionen.