/* $Id: GVRKUms.java,v 1.3 2012/01/27 22:52:25 willuhn Exp $
This file is part of HBCI4Java
Copyright (C) 2001-2008 Stefan Palme
HBCI4Java is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
HBCI4Java is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*/
package org.kapott.hbci.GV_Result;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
import org.kapott.hbci.exceptions.HBCI_Exception;
import org.kapott.hbci.manager.HBCIUtils;
import org.kapott.hbci.manager.HBCIUtilsInternal;
import org.kapott.hbci.passport.HBCIPassport;
import org.kapott.hbci.structures.Konto;
import org.kapott.hbci.structures.Saldo;
import org.kapott.hbci.structures.Value;
import org.kapott.hbci.swift.Swift;
/** <p>Ergebnisse der Abfrage von Kontoumsatzinformationen.
Ein Objekt dieser Klasse entspricht einen Kontoauszug.
Ein Kontoauszug ist in einzelne Buchungstage unterteilt.
F�r jeden einzelnen Buchungstag wiederum gibt es eine Anzahl von Umsatzzeilen
(das entspricht je einem Eintrag auf dem "normalen" Kontoauszug auf Papier).
Jede einzelne Umsatzzeile wiederum enth�lt die einzelnen Informationen zu genau
einer Transaktion. </p>
<p>Es k�nnen auch alle Umsatzzeilen in einer einzigen Liste abgefragt werden (also nicht
in Buchungstage unterteilt .</p>*/
public class GVRKUms
extends HBCIJobResultImpl
{
/** Eine "Zeile" des Kontoauszuges (enth�lt Daten einer Transaktion) */
public static class UmsLine
implements Serializable
{
/** Datum der Wertstellung */
public Date valuta;
/** Buchungsdatum */
public Date bdate;
/** Gebuchter Betrag */
public Value value;
/** Handelt es sich um eine Storno-Buchung? */
public boolean isStorno;
/** Der Saldo <em>nach</em> dem Buchen des Betrages <code>value</code> */
public Saldo saldo;
/** Kundenreferenz */
public String customerref;
/** Kreditinstituts-Referenz */
public String instref;
/** Urspr�nglicher Betrag (bei ausl�ndischen Buchungen; optional) */
public Value orig_value;
/** Betrag f�r Geb�hren des Geldverkehrs (optional) */
public Value charge_value;
/** Art der Buchung (bankinterner Code). Nur wenn hier ein Wert ungleich
* <code>999</code> drinsteht, enthalten die Attribute <code>text</code>,
* <code>primanota</code>, <code>usage</code>, <code>other</code> und
* <code>addkey</code> sinnvolle Werte. Andernfalls sind all diese
* Informationen m�glicherweise im Feld <code>additional</code> enthalten,
* allerdings in einem nicht definierten Format (siehe auch
* <code>additional</code>). */
public String gvcode;
/** <p>Zusatzinformationen im Rohformat. Wenn Zusatzinformationen zu dieser
Transaktion in einem unbekannten Format vorliegen, dann enth�lt dieser
String diese Daten (u.U. ist dieser String leer, aber nicht <code>null</code>).
Das ist genau dann der Fall, wenn der Wert von <code>gvcode</code> gleich <code>999</code> ist.</p>
<p>Wenn die Zusatzinformationen aber ausgewertet werden k�nnen (und <code>gvcode!=999</code>),
so ist dieser String <code>null</code>, und die Felder <code>text</code>, <code>primanota</code>,
<code>usage</code>, <code>other</code> und <code>addkey</code>
enthalten die entsprechenden Werte (siehe auch <code>gvcode</code>)</p> */
public String additional;
/** Beschreibung der Art der Buchung (optional).
* Nur wenn <code>gvcode!=999</code>! (siehe auch <code>additional</code>
* und <code>gvcode</code>)*/
public String text;
/** Primanotakennzeichen (optional).
* Nur wenn <code>gvcode!=999</code>! (siehe auch <code>additional</code>
* und <code>gvcode</code>) */
public String primanota;
/** Liste von Strings mit den Verwendungszweckzeilen.
* Nur wenn <code>gvcode!=999</code>! (siehe auch <code>additional</code>
* und <code>gvcode</code>)*/
public List<String> usage;
/** Gegenkonto der Buchung (optional).
* Nur wenn <code>gvcode!=999</code>! (siehe auch <code>additional</code>
* und <code>gvcode</code>) */
public Konto other;
/** Erweiterte Informationen zur Art der Buchung (bankintern, optional).
* Nur wenn <code>gvcode!=999</code>! (siehe auch <code>additional</code>
* und <code>gvcode</code>) */
public String addkey;
/** Gibt an, ob ein Umsatz ein SEPA-Umsatz ist **/
public boolean isSepa;
public UmsLine()
{
usage=new ArrayList<String>();
isSepa=false;
}
public void addUsage(String st)
{
if (st!=null) {
usage.add(st);
}
}
public String toString()
{
StringBuffer ret=new StringBuffer();
String linesep=System.getProperty("line.separator");
ret.append(HBCIUtils.date2StringLocal(valuta)).append(" ").append(HBCIUtils.date2StringLocal(bdate)).append(" ");
ret.append(customerref).append(":").append(instref).append(" ");
ret.append(value.toString());
ret.append(isStorno?" (Storno)":"");
if (orig_value!=null)
ret.append(" (orig ").append(orig_value.toString()).append(")");
if (charge_value!=null)
ret.append(" (charge ").append(charge_value.toString()).append(")");
ret.append(linesep);
ret.append(" saldo: ").append(saldo.toString()).append(linesep);
ret.append(" code ").append(gvcode).append(linesep);
if (additional==null) {
ret.append(" text:").append(text).append(linesep);
ret.append(" primanota:").append(primanota).append(linesep);
for (Iterator<String> i=usage.iterator(); i.hasNext(); ) {
ret.append(" usage:").append(i.next()).append(linesep);
}
if (other!=null)
ret.append(" konto:").append(other.toString()).append(linesep);
ret.append(" addkey:").append(addkey);
}
else ret.append(" ").append(additional);
return ret.toString().trim();
}
}
/** Enth�lt alle Transaktionen eines einzelnen Buchungstages. Dazu geh�ren
das Datum des jeweiligen Tages, der Anfangs- und Endsaldo sowie die
Menge aller dazugeh�rigen Umsatzeilen */
public static class BTag
implements Serializable
{
/** <p>Konto, auf das sich die Umsatzdaten beziehen (Kundenkonto). Einige
Kreditinstitute geben fehlerhafte Kontoausz�ge zur�ck, was zur Folge
haben kann, dass dieses Feld nicht richtig belegt ist. Tritt ein solcher
Fall ein, so kann es vorkommen, dass von dem <code>Konto</code>-Objekt
nur das Feld <code>number</code> gef�llt ist, und zwar mit den
Informationen, die das Kreditinstitut zur Identifizierung dieses Kontos
zur�ckgibt.</p>
<p>Normalerweise bestehen diese Informationen aus BLZ und
Kontonummer, die dann auch korrekt in das <code>Konto</code>-Objekt
eingetragen werden. Liegen diese Informationen aber gar nicht oder in
einem falschen bzw. unbekannten Format vor, so werden diese Daten
komplett in das <code>number</code>-Feld geschrieben.</p> */
public Konto my;
/** Nummer des Kontoauszuges (optional) */
public String counter;
/** Saldo zu Beginn des Buchungstages */
public Saldo start;
/** Art des Saldos. <code>M</code> = Anfangssaldo; <code>F</code> = Zwischensaldo */
public char starttype;
/** Liste der einzelnen Buchungen dieses Tages (Instanzen von {@link GVRKUms.UmsLine}) */
public List<UmsLine> lines;
/** Saldo am Ende des Buchungstages */
public Saldo end;
/** Art des Endsaldos (siehe {@link #starttype}) */
public char endtype;
public BTag()
{
lines=new ArrayList<UmsLine>();
}
public void addLine(UmsLine line)
{
lines.add(line);
}
public String toString()
{
StringBuffer ret=new StringBuffer();
String linesep=System.getProperty("line.separator");
ret.append("Konto ").append(my.toString()).append(" - Auszugsnummer ").append(counter).append(linesep);
ret.append(" ").append((starttype=='F'?"Anfangs":"Zwischen")).append("saldo: ").append(start.toString()).append(linesep);
for (Iterator<UmsLine> i=lines.iterator(); i.hasNext(); ) {
ret.append(" ").append(i.next().toString()).append(linesep);
}
ret.append(" ").append((endtype=='F'?"Schluss":"Zwischen")).append("saldo: ").append(end.toString());
return ret.toString().trim();
}
}
private StringBuffer bufferMT940;
private StringBuffer bufferMT942;
private List<BTag> tageMT940;
private List<BTag> tageMT942;
private boolean parsed;
/** Dieses Feld enth�lt einen String, der den nicht-auswertbaren Teil der Kontoausz�ge
* enth�lt. Es dient nur zu Debugging-Zwecken und sollte eigentlich immer <code>null</code>
* bzw. einen leeren String enthalten. Wenn das nicht der Fall ist, dann konnten die
* empfangenen Kontoausz�ge nicht richtig geparst werden, und dieser String enth�lt den
* "Schwanz" der Kontoauszugsdaten, bei dem das Parsing-Problem aufgetreten ist. */
public StringBuffer restMT940;
/** Wie restMT940, allerdings f�r die Daten der *vorgemerkten* Ums�tze. */
public StringBuffer restMT942;
public GVRKUms()
{
bufferMT940=new StringBuffer();
bufferMT942=new StringBuffer();
tageMT940=new ArrayList<BTag>();
tageMT942=new ArrayList<BTag>();
restMT940=new StringBuffer();
restMT942=new StringBuffer();
parsed=false;
}
public void appendMT940Data(String data)
{
this.bufferMT940.append(data);
}
public void appendMT942Data(String data)
{
this.bufferMT942.append(data);
}
/** Gibt die Umsatzinformationen gruppiert nach Buchungstagen zur�ck.
@return Liste mit Informationen zu einzelnen Buchungstagen ({@link GVRKUms.BTag}) */
public List<BTag> getDataPerDay()
{
verifyMT94xParsing("getDataPerDay()");
return tageMT940;
}
/** Gibt alle Transaktionsdatens�tze in einer "flachen" Struktur zur�ck.
D.h. nicht in einzelne Buchungstage unterteilt, sondern in einer Liste
analog zu einem "normalen" Kontoauszug.
@return Liste mit Transaktionsdaten ({@link GVRKUms.UmsLine}) */
public List<UmsLine> getFlatData()
{
verifyMT94xParsing("getFlatData()");
List<UmsLine> result=new ArrayList<UmsLine>();
for (Iterator<BTag> i=tageMT940.iterator(); i.hasNext(); ) {
BTag tag= i.next();
result.addAll(tag.lines);
}
return result;
}
/** Gibt eine Liste aller vorgemerkten Ums�tze zur�ck
* @return Liste von {@link GVRKUms.UmsLine}-Objekten der vorgemerkten Ums�tze */
public List<UmsLine> getFlatDataUnbooked()
{
verifyMT94xParsing("getFlatDataUnbooked()");
List<UmsLine> result=new ArrayList<UmsLine>();
for (Iterator<BTag> i=tageMT942.iterator(); i.hasNext(); ) {
BTag tag= i.next();
result.addAll(tag.lines);
}
return result;
}
public String toString()
{
verifyMT94xParsing("toString()");
StringBuffer ret=new StringBuffer();
String linesep=System.getProperty("line.separator");
// mt940
for (Iterator<UmsLine> i=getFlatData().iterator(); i.hasNext(); ) {
ret.append(i.next().toString()).append(linesep);
}
ret.append("rest: ").append(restMT940).append(linesep).append(linesep);
// mt942
ret.append("not yet booked:").append(linesep);
for (Iterator<UmsLine> i=getFlatDataUnbooked().iterator(); i.hasNext(); ) {
ret.append(i.next().toString()).append(linesep);
}
ret.append("rest: ").append(restMT942);
return ret.toString().trim();
}
private void verifyMT94xParsing(String where)
{
if (!parsed) {
parseMT94x(bufferMT940, tageMT940, restMT940);
parseMT94x(bufferMT942, tageMT942, restMT942);
}
if (restMT940!=null && restMT940.length()!=0) {
HBCIUtils.log(
where+
": mt940 has not been parsed successfully " +
"- probably returned data will be incomplete. "+
"check variable 'restMT940' (or set logging level to 4 (=DEBUG)) "+
"to see the data that could not be parsed.",
HBCIUtils.LOG_WARN);
HBCIUtils.log("restMT940: "+restMT940, HBCIUtils.LOG_DEBUG);
}
if (restMT942!=null && restMT942.length()!=0) {
HBCIUtils.log(
where+
": mt942 has not been parsed successfully " +
"- probably returned data will be incomplete. "+
"check variable 'restMT942' (or set logging level to 4 (=DEBUG)) "+
"to see the data that could not be parsed.",
HBCIUtils.LOG_WARN);
HBCIUtils.log("restMT942: "+restMT942, HBCIUtils.LOG_DEBUG);
}
}
private void parseMT94x(StringBuffer buffer, List<BTag> tage, StringBuffer rest)
{
HBCIUtils.log("now parsing MT94x data", HBCIUtils.LOG_DEBUG);
parsed=true;
try {
SimpleDateFormat dateFormat=new SimpleDateFormat("yyMMdd");
HBCIPassport passport=getPassport();
// split into "buchungstage"
while (buffer.length()!=0) {
String st_tag=Swift.getOneBlock(buffer);
if (st_tag==null) {
break;
}
GVRKUms.BTag btag=new GVRKUms.BTag();
// extract konto data
String konto_info=Swift.getTagValue(st_tag,"25",0);
int pos=konto_info.indexOf("/");
String blz;
String number;
String iban;
String curr;
if (pos!=-1) {
blz=konto_info.substring(0,pos);
number=konto_info.substring(pos+1);
iban="";
curr="";
for (pos=number.length();pos>0;pos--) {
char ch=number.charAt(pos-1);
if (ch>='0' && ch<='9')
break;
}
if (pos<number.length()) {
curr=number.substring(pos);
number=number.substring(0,pos);
}
} else {
blz="";
number="";
iban=konto_info;
curr="";
}
btag.my=new Konto();
btag.my.blz=blz;
btag.my.number=number;
btag.my.iban=iban;
btag.my.curr=curr;
if (passport!=null) {
passport.fillAccountInfo(btag.my);
}
// extract "auszugsnummer"
btag.counter=Swift.getTagValue(st_tag,"28C",0);
// extract "anfangssaldo"
String st_start=Swift.getTagValue(st_tag,"60F",0);
char starttype='F';
if (st_start==null) {
st_start=Swift.getTagValue(st_tag,"60M",0);
starttype='M';
}
if (st_start!=null) {
// Tag 60 (Anfangssaldo) gibt es in MT942 nicht,
// darum wird btag.start nur in MT940 gef�llt
btag.start=new Saldo();
btag.starttype=starttype;
String cd=st_start.substring(0,1);
try {
btag.start.timestamp=dateFormat.parse(st_start.substring(1,7));
} catch (Exception e) {
btag.start.timestamp=null;
}
// hier aus dem CD-Indikator und dem absoluten Saldo-Betrag
// einen String f�r den Saldo-Betrag zusamennbauen
btag.start.value=new Value(
(cd.equals("D")?"-":"")+st_start.substring(10).replace(',','.'),
st_start.substring(7,10));
}
// looping to get all "umsaetze"
// TODO: beim MT942 (btag.start==null) m�sste als Initialwert
// fuer den Saldo hier eigentlich der Abschluss-Saldo aus den
// gebuchten Ums�tzen verwendet werden (den habe ich an dieser
// Stelle aber nicht so ohne weiteres)
long saldo = (btag.start!=null)?btag.start.value.getLongValue():0;
int ums_counter=0;
while (true) {
String st_ums=Swift.getTagValue(st_tag,"61",ums_counter);
if (st_ums==null)
break;
GVRKUms.UmsLine line=new GVRKUms.UmsLine();
// extract valuta
line.valuta=dateFormat.parse(st_ums.substring(0,6));
// extract bdate
int next=0;
if (st_ums.charAt(6)>'9') {
// [2012-01-27 - Patch von Frank/Pecunia]
// beim :61er Tag ist das Buchungsdatum optional. Wenn es nicht gesetzt ist, muss das Buchungsdatum des
// Umsatzes z.B. aus :60F kommen
if (btag.start != null && btag.start.timestamp != null) line.bdate = btag.start.timestamp;
else line.bdate=line.valuta;
next=6;
} else {
line.bdate=dateFormat.parse(st_ums.substring(0,2)+
st_ums.substring(6,10));
// wenn bdate und valuta um mehr als einen monat voneinander
// abweichen, dann ist das jahr des bdate falsch (1.1.2005 vs. 31.12.2004)
// korrektur des bdate-jahres in die richtige richtung notwendig
// FE: ein Monat reicht nicht, es sollte schon ein halbes Jahr sein - es gab verschiedene Probleme mit Umsaetzen im falschen Jahr!!
// http://www.onlinebanking-forum.de/phpBB2/viewtopic.php?p=75348
if (Math.abs(line.bdate.getTime()-line.valuta.getTime())>180L*24*3600*1000) {
int diff;
if (line.bdate.before(line.valuta)) {
diff=+1;
} else {
diff=-1;
}
Calendar cal=Calendar.getInstance();
cal.setTime(line.bdate);
cal.set(Calendar.YEAR,cal.get(Calendar.YEAR)+diff);
line.bdate=cal.getTime();
}
next=10;
}
// extract credit/debit
String cd;
if (st_ums.charAt(next)=='C' || st_ums.charAt(next)=='D') {
line.isStorno=false;
cd=st_ums.substring(next,next+1);
next++;
} else {
line.isStorno=true;
cd=st_ums.substring(next+1,next+2);
next+=2;
}
// skip part of currency
char currpart=st_ums.charAt(next);
if (currpart>'9')
next++;
line.value=new Value();
// TODO: bei einem MT942 wird die waehrung hier automatisch auf EUR
// gesetzt, weil die auto-erkennung (anhand des anfangssaldos) hier nicht
// funktioniert, weil es im MT942 keinen anfangssaldo gibt
line.value.setCurr((btag.start!=null)?btag.start.value.getCurr():"EUR");
// extract value and skip code
int npos=st_ums.indexOf("N",next);
// welcher Code (C/D) zeigt einen negativen Buchungsbetrag
// an? Bei einer "normalen" Buchung ist das D(ebit). Bei
// einer Storno-Buchung ist der Betrag allerdings negativ,
// wenn eine ehemalige Gutschrift (Credit) storniert wird,
// in dem Fall w�re als "C" der Indikator f�r den negativen
// Buchungsbetrag
String negValueIndikator=line.isStorno?"C":"D";
line.value.setValue(
HBCIUtilsInternal.string2Long(
(cd.equals(negValueIndikator)?"-":"") + st_ums.substring(next,npos).replace(',','.'),
100));
next=npos+4;
// update saldo
saldo+=line.value.getLongValue();
line.saldo=new Saldo();
line.saldo.timestamp=line.bdate;
// TODO: bei einem MT942 wird die waehrung hier automatisch auf EUR
// gesetzt, weil die auto-erkennung (anhand des anfangssaldos) hier nicht
// funktioniert, weil es im MT942 keinen anfangssaldo gibt
line.saldo.value=new Value(saldo, (btag.start!=null)?btag.start.value.getCurr():"EUR");
// extract customerref
npos=st_ums.indexOf("//",next);
if (npos==-1)
npos=st_ums.indexOf("\r\n",next);
if (npos==-1)
npos=st_ums.length();
line.customerref=st_ums.substring(next,npos);
next=npos;
// check for instref
if (next<st_ums.length() && st_ums.substring(next,next+2).equals("//")) {
// extract instref
next+=2;
npos=st_ums.indexOf("\r\n",next);
if (npos==-1)
npos=st_ums.length();
line.instref=st_ums.substring(next,npos);
next=npos+2;
}
if (line.instref==null)
line.instref="";
// check for additional information
if (next<st_ums.length() && st_ums.charAt(next)=='\r') {
next+=2;
// extract orig Value
pos=st_ums.indexOf("/OCMT/",next);
if (pos!=-1) {
int slashpos=st_ums.indexOf("/",pos+9);
if (slashpos==-1)
slashpos=st_ums.length();
try
{
line.orig_value=new Value(
st_ums.substring(pos+9,slashpos).replace(',','.'),
st_ums.substring(pos+6,pos+9));
}
catch (NumberFormatException nfe)
{
// Der Betrag darf fehlen. Tolerieren wir
}
}
// extract charge Value
pos=st_ums.indexOf("/CHGS/",next);
if (pos!=-1) {
int slashpos=st_ums.indexOf("/",pos+9);
if (slashpos==-1)
slashpos=st_ums.length();
try
{
line.charge_value=new Value(
st_ums.substring(pos+9,slashpos).replace(',','.'),
st_ums.substring(pos+6,pos+9));
}
catch (NumberFormatException nfe)
{
// Der Betrag darf fehlen. Tolerieren wir
}
}
}
String st_multi=Swift.getTagValue(st_tag,"86",ums_counter);
if (st_multi!=null) {
line.gvcode=st_multi.substring(0,3);
st_multi=Swift.packMulti(st_multi.substring(3));
if (!line.gvcode.equals("999")) {
line.isSepa = line.gvcode.startsWith("1");
line.text=Swift.getMultiTagValue(st_multi,"00");
line.primanota=Swift.getMultiTagValue(st_multi,"10");
for (int i=0;i<10;i++) {
line.addUsage(Swift.getMultiTagValue(st_multi,Integer.toString(20+i)));
}
Konto acc=new Konto();
acc.blz=Swift.getMultiTagValue(st_multi,"30");
acc.number=Swift.getMultiTagValue(st_multi,"31");
// fuer den Fall, dass in der BLZ sowas hier drin steht: "GENODEF1S06 SVWZ+ ja"
// Siehe http://www.onlinebanking-forum.de/phpBB2/viewtopic.php?t=16182
if (acc.blz != null)
{
int space = acc.blz.indexOf(" ");
if (space != -1)
{
HBCIUtils.log("blz/bic \"" + acc.blz + "\" contains invalid chars, trimming after first space", HBCIUtils.LOG_DEBUG);
acc.blz = acc.blz.substring(0,space);
}
}
if (line.isSepa)
{
acc.bic = acc.blz;
acc.iban = acc.number;
}
acc.name=Swift.getMultiTagValue(st_multi,"32");
acc.name2=Swift.getMultiTagValue(st_multi,"33");
if (acc.blz!=null ||
acc.number!=null ||
acc.name!=null ||
acc.name2!=null) {
if (acc.blz==null)
acc.blz="";
if (acc.number==null)
acc.number="";
if (acc.name==null)
acc.name="";
line.other=acc;
}
line.addkey=Swift.getMultiTagValue(st_multi,"34");
for (int i=0;i<4;i++) {
line.addUsage(Swift.getMultiTagValue(st_multi,Integer.toString(60+i)));
}
} else {
line.additional=st_multi;
}
}
btag.addLine(line);
ums_counter++;
}
// extract "schlusssaldo"
String st_end=Swift.getTagValue(st_tag,"62F",0);
char endtype='F';
btag.endtype='F';
if (st_end==null) {
st_end=Swift.getTagValue(st_tag,"62M",0);
endtype='M';
}
if (st_end!=null) {
// Tag 62 (Schlusssaldo) gibt es in MT942 nicht,
// darum wird btag.end nur in MT940 gef�llt
btag.end=new Saldo();
btag.endtype=endtype;
String cd=st_end.substring(0,1);
try {
btag.end.timestamp=dateFormat.parse(st_end.substring(1,7));
} catch (Exception e) {
btag.end.timestamp=null;
}
// set default values for optional non-given bdates
if (btag.start != null && btag.start.timestamp==null) {
btag.start.timestamp=btag.end.timestamp;
}
for (Iterator<UmsLine> j=btag.lines.iterator(); j.hasNext(); ) {
UmsLine line= j.next();
if (line.bdate==null) {
line.bdate=btag.end.timestamp;
}
}
btag.end.value=new Value(
(cd.equals("D")?"-":"")+st_end.substring(10).replace(',','.'),
st_end.substring(7,10));
}
// Now check if the end balance (Schlusssaldo) equals balance of last statement. If not, the bank sent a wrong start balance
// and we have to re-calculate the balances for each statement
int numLines = btag.lines.size();
if(numLines > 0 && btag.end != null) {
UmsLine lastLine = btag.lines.get(numLines-1);
saldo = btag.end.value.getLongValue();
if(lastLine.saldo.value.getLongValue() != saldo) {
for(int i=numLines-1; i>=0; i--) {
lastLine = btag.lines.get(i);
lastLine.saldo.value = new Value(saldo, btag.end.value.getCurr());
saldo -= lastLine.value.getLongValue();
}
}
}
tage.add(btag);
buffer.delete(0,st_tag.length());
}
// remove this debugging output
// HBCIUtils.log("Parsing of MT940 ok until now; unparsed data: "+buffer,HBCIUtils.LOG_DEBUG2);
} catch (Exception e) {
HBCIUtils.log("There is unparsed MT94x data - an exception occured while parsing",HBCIUtils.LOG_ERR);
HBCIUtils.log("current MT94x buffer: "+buffer,HBCIUtils.LOG_DEBUG2);
throw new HBCI_Exception(e);
} finally {
rest.setLength(0);
rest.append(buffer.toString());
}
}
}