::const_iterator it_m;
it_m = m_protocolConversionMap.find((*it).c_str());
if (it_m != m_protocolConversionMap.end())
protocolList << (*it_m);
else
protocolList << (*it).c_str();
}
}
}
QWidget* KBanking::accountConfigTab(const MyMoneyAccount& acc, QString& name)
{
const MyMoneyKeyValueContainer& kvp = acc.onlineBankingSettings();
name = i18n("Online settings");
if (m_kbanking) {
m_accountSettings = new KBAccountSettings(acc, 0);
m_accountSettings->loadUi(kvp);
return m_accountSettings;
}
QLabel* label = new QLabel(i18n("KBanking module not correctly initialized"), 0);
label->setAlignment(Qt::AlignVCenter | Qt::AlignHCenter);
return label;
}
MyMoneyKeyValueContainer KBanking::onlineBankingSettings(const MyMoneyKeyValueContainer& current)
{
MyMoneyKeyValueContainer kvp(current);
kvp["provider"] = objectName().toLower();
if (m_accountSettings) {
m_accountSettings->loadKvp(kvp);
}
return kvp;
}
void KBanking::createActions()
{
QAction *settings_aqbanking = actionCollection()->addAction("settings_aqbanking");
settings_aqbanking->setText(i18n("Configure Aq&Banking..."));
connect(settings_aqbanking, &QAction::triggered, this, &KBanking::slotSettings);
QAction *file_import_aqbanking = actionCollection()->addAction("file_import_aqbanking");
file_import_aqbanking->setText(i18n("AqBanking importer..."));
connect(file_import_aqbanking, &QAction::triggered, this, &KBanking::slotImport);
Q_CHECK_PTR(viewInterface());
connect(viewInterface(), &KMyMoneyPlugin::ViewInterface::viewStateChanged, action("file_import_aqbanking"), &QAction::setEnabled);
#ifdef KMM_DEBUG
QAction *openChipTanDialog = actionCollection()->addAction("open_chiptan_dialog");
openChipTanDialog->setText("Open ChipTan Dialog");
connect(openChipTanDialog, &QAction::triggered, [&](){
auto dlg = new chipTanDialog();
dlg->setHhdCode("0F04871100030333555414312C32331D");
dlg->setInfoText("Test Graphic for debugging
The encoded data is
Account Number: 335554
Amount: 1,23
");
connect(dlg, &QDialog::accepted, dlg, &chipTanDialog::deleteLater);
connect(dlg, &QDialog::rejected, dlg, &chipTanDialog::deleteLater);
dlg->show();
});
#endif
}
void KBanking::slotSettings()
{
if (m_kbanking) {
GWEN_DIALOG* dlg = AB_SetupDialog_new(m_kbanking->getCInterface());
if (dlg == NULL) {
DBG_ERROR(0, "Could not create setup dialog.");
return;
}
if (GWEN_Gui_ExecDialog(dlg, 0) == 0) {
DBG_ERROR(0, "Aborted by user");
GWEN_Dialog_free(dlg);
return;
}
GWEN_Dialog_free(dlg);
}
}
bool KBanking::mapAccount(const MyMoneyAccount& acc, MyMoneyKeyValueContainer& settings)
{
bool rc = false;
if (m_kbanking && !acc.id().isEmpty()) {
m_kbanking->askMapAccount(acc);
// at this point, the account should be mapped
// so we search it and setup the account reference in the KMyMoney object
AB_ACCOUNT* ab_acc;
ab_acc = aqbAccount(acc);
if (ab_acc) {
MyMoneyAccount a(acc);
setupAccountReference(a, ab_acc);
settings = a.onlineBankingSettings();
rc = true;
}
}
return rc;
}
AB_ACCOUNT* KBanking::aqbAccount(const MyMoneyAccount& acc) const
{
if (m_kbanking == 0) {
return 0;
}
// certainly looking for an expense or income account does not make sense at this point
// so we better get out right away
if (acc.isIncomeExpense()) {
return 0;
}
AB_ACCOUNT *ab_acc = AB_Banking_GetAccountByAlias(m_kbanking->getCInterface(), m_kbanking->mappingId(acc).toUtf8().data());
// if the account is not found, we temporarily scan for the 'old' mapping (the one w/o the file id)
// in case we find it, we setup the new mapping in addition on the fly.
if (!ab_acc && acc.isAssetLiability()) {
ab_acc = AB_Banking_GetAccountByAlias(m_kbanking->getCInterface(), acc.id().toUtf8().data());
if (ab_acc) {
qDebug("Found old mapping for '%s' but not new. Setup new mapping", qPrintable(acc.name()));
m_kbanking->setAccountAlias(ab_acc, m_kbanking->mappingId(acc).toUtf8().constData());
// TODO at some point in time, we should remove the old mapping
}
}
return ab_acc;
}
AB_ACCOUNT* KBanking::aqbAccount(const QString& accountId) const
{
MyMoneyAccount account = MyMoneyFile::instance()->account(accountId);
return aqbAccount(account);
}
QString KBanking::stripLeadingZeroes(const QString& s) const
{
QString rc(s);
QRegExp exp("^(0*)([^0].*)");
if (exp.exactMatch(s)) {
rc = exp.cap(2);
}
return rc;
}
void KBanking::setupAccountReference(const MyMoneyAccount& acc, AB_ACCOUNT* ab_acc)
{
MyMoneyKeyValueContainer kvp;
if (ab_acc) {
QString accountNumber = stripLeadingZeroes(AB_Account_GetAccountNumber(ab_acc));
QString routingNumber = stripLeadingZeroes(AB_Account_GetBankCode(ab_acc));
QString val = QString("%1-%2").arg(routingNumber, accountNumber);
if (val != acc.onlineBankingSettings().value("kbanking-acc-ref")) {
kvp.clear();
// make sure to keep our own previous settings
const QMap& vals = acc.onlineBankingSettings().pairs();
QMap::const_iterator it_p;
for (it_p = vals.begin(); it_p != vals.end(); ++it_p) {
if (QString(it_p.key()).startsWith("kbanking-")) {
kvp.setValue(it_p.key(), *it_p);
}
}
kvp.setValue("kbanking-acc-ref", val);
kvp.setValue("provider", objectName().toLower());
setAccountOnlineParameters(acc, kvp);
}
} else {
// clear the connection
setAccountOnlineParameters(acc, kvp);
}
}
bool KBanking::accountIsMapped(const MyMoneyAccount& acc)
{
return aqbAccount(acc) != 0;
}
bool KBanking::updateAccount(const MyMoneyAccount& acc)
{
return updateAccount(acc, false);
}
bool KBanking::updateAccount(const MyMoneyAccount& acc, bool moreAccounts)
{
if (!m_kbanking)
return false;
bool rc = false;
if (!acc.id().isEmpty()) {
AB_JOB *job = 0;
int rv;
/* get AqBanking account */
AB_ACCOUNT *ba = aqbAccount(acc);
// Update the connection between the KMyMoney account and the AqBanking equivalent.
// If the account is not found anymore ba == 0 and the connection is removed.
setupAccountReference(acc, ba);
if (!ba) {
KMessageBox::error(0,
i18n(""
"The given application account %1 "
"has not been mapped to an online "
"account."
"",
acc.name()),
i18n("Account Not Mapped"));
} else {
bool enqueJob = true;
if (acc.onlineBankingSettings().value("kbanking-txn-download") != "no") {
/* create getTransactions job */
job = AB_JobGetTransactions_new(ba);
rv = AB_Job_CheckAvailability(job);
if (rv) {
DBG_ERROR(0, "Job \"GetTransactions\" is not available (%d)", rv);
KMessageBox::error(0,
i18n(""
"The update job is not supported by the "
"bank/account/backend.\n"
""),
i18n("Job not Available"));
AB_Job_free(job);
job = 0;
}
if (job) {
int days = AB_JobGetTransactions_GetMaxStoreDays(job);
QDate qd;
if (days > 0) {
GWEN_TIME *ti1;
GWEN_TIME *ti2;
ti1 = GWEN_CurrentTime();
ti2 = GWEN_Time_fromSeconds(GWEN_Time_Seconds(ti1) - (60 * 60 * 24 * days));
GWEN_Time_free(ti1);
ti1 = ti2;
int year, month, day;
if (GWEN_Time_GetBrokenDownDate(ti1, &day, &month, &year)) {
DBG_ERROR(0, "Bad date");
qd = QDate();
} else
qd = QDate(year, month + 1, day);
GWEN_Time_free(ti1);
}
// get last statement request date from application account object
// and start from a few days before if the date is valid
QDate lastUpdate = QDate::fromString(acc.value("lastImportedTransactionDate"), Qt::ISODate);
if (lastUpdate.isValid())
lastUpdate = lastUpdate.addDays(-3);
int dateOption = acc.onlineBankingSettings().value("kbanking-statementDate").toInt();
switch (dateOption) {
case 0: // Ask user
break;
case 1: // No date
qd = QDate();
break;
case 2: // Last download
qd = lastUpdate;
break;
case 3: // First possible
// qd is already setup
break;
}
// the pick start date option dialog is needed in
// case the dateOption is 0 or the date option is > 1
// and the qd is invalid
if (dateOption == 0 || (dateOption > 1 && !qd.isValid())) {
QPointer psd = new KBPickStartDate(m_kbanking, qd, lastUpdate, acc.name(),
lastUpdate.isValid() ? 2 : 3, 0, true);
if (psd->exec() == QDialog::Accepted) {
qd = psd->date();
} else {
enqueJob = false;
}
delete psd;
}
if (enqueJob) {
if (qd.isValid()) {
GWEN_TIME *ti1;
ti1 = GWEN_Time_new(qd.year(), qd.month() - 1, qd.day(), 0, 0, 0, 0);
AB_JobGetTransactions_SetFromTime(job, ti1);
GWEN_Time_free(ti1);
}
rv = m_kbanking->enqueueJob(job);
if (rv) {
DBG_ERROR(0, "Error %d", rv);
KMessageBox::error(0,
i18n(""
"Could not enqueue the job.\n"
""),
i18n("Error"));
}
}
AB_Job_free(job);
}
}
if (enqueJob) {
/* create getBalance job */
job = AB_JobGetBalance_new(ba);
rv = AB_Job_CheckAvailability(job);
if (!rv)
rv = m_kbanking->enqueueJob(job);
else
rv = 0;
AB_Job_free(job);
if (rv) {
DBG_ERROR(0, "Error %d", rv);
KMessageBox::error(0,
i18n(""
"Could not enqueue the job.\n"
""),
i18n("Error"));
} else {
rc = true;
emit queueChanged();
}
}
}
}
// make sure we have at least one job in the queue before sending it
if (!moreAccounts && m_kbanking->getEnqueuedJobs().size() > 0)
executeQueue();
return rc;
}
void KBanking::executeQueue()
{
if (m_kbanking && m_kbanking->getEnqueuedJobs().size() > 0) {
AB_IMEXPORTER_CONTEXT *ctx;
ctx = AB_ImExporterContext_new();
int rv = m_kbanking->executeQueue(ctx);
if (!rv) {
m_kbanking->importContext(ctx, 0);
} else {
DBG_ERROR(0, "Error: %d", rv);
}
AB_ImExporterContext_free(ctx);
}
}
/** @todo improve error handling, e.g. by adding a .isValid to nationalTransfer
* @todo use new onlineJob system
*/
void KBanking::sendOnlineJob(QList& jobs)
{
Q_CHECK_PTR(m_kbanking);
m_onlineJobQueue.clear();
QList unhandledJobs;
if (!jobs.isEmpty()) {
foreach (onlineJob job, jobs) {
if (sepaOnlineTransfer::name() == job.task()->taskName()) {
onlineJobTyped typedJob(job);
enqueTransaction(typedJob);
job = typedJob;
} else {
job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Error, "KBanking", "Cannot handle this request"));
unhandledJobs.append(job);
}
m_onlineJobQueue.insert(m_kbanking->mappingId(job), job);
}
executeQueue();
}
jobs = m_onlineJobQueue.values() + unhandledJobs;
m_onlineJobQueue.clear();
}
QStringList KBanking::availableJobs(QString accountId)
{
try {
MyMoneyAccount acc = MyMoneyFile::instance()->account(accountId);
QString id = MyMoneyFile::instance()->value("kmm-id");
if(id != d->fileId) {
d->jobList.clear();
d->fileId = id;
}
} catch (const MyMoneyException &) {
// Exception usually means account was not found
return QStringList();
}
if(d->jobList.contains(accountId)) {
return d->jobList[accountId];
}
QStringList list;
AB_ACCOUNT* abAccount = aqbAccount(accountId);
if (!abAccount) {
return list;
}
// Check availableJobs
// sepa transfer
AB_JOB* abJob = AB_JobSepaTransfer_new(abAccount);
if (AB_Job_CheckAvailability(abJob) == 0)
list.append(sepaOnlineTransfer::name());
AB_Job_free(abJob);
d->jobList[accountId] = list;
return list;
}
/** @brief experimenting with QScopedPointer and aqBanking pointers */
class QScopedPointerAbJobDeleter
{
public:
static void cleanup(AB_JOB* job) {
AB_Job_free(job);
}
};
/** @brief experimenting with QScopedPointer and aqBanking pointers */
class QScopedPointerAbAccountDeleter
{
public:
static void cleanup(AB_ACCOUNT* account) {
AB_Account_free(account);
}
};
IonlineTaskSettings::ptr KBanking::settings(QString accountId, QString taskName)
{
AB_ACCOUNT* abAcc = aqbAccount(accountId);
if (abAcc == 0)
return IonlineTaskSettings::ptr();
if (sepaOnlineTransfer::name() == taskName) {
// Get limits for sepaonlinetransfer
QScopedPointer abJob(AB_JobSepaTransfer_new(abAcc));
if (AB_Job_CheckAvailability(abJob.data()) != 0)
return IonlineTaskSettings::ptr();
const AB_TRANSACTION_LIMITS* limits = AB_Job_GetFieldLimits(abJob.data());
return AB_TransactionLimits_toSepaOnlineTaskSettings(limits).dynamicCast();
}
return IonlineTaskSettings::ptr();
}
bool KBanking::enqueTransaction(onlineJobTyped& job)
{
/* get AqBanking account */
const QString accId = job.constTask()->responsibleAccount();
AB_ACCOUNT *abAccount = aqbAccount(accId);
if (!abAccount) {
job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Warning, "KBanking", i18n(""
"The given application account %1 "
"has not been mapped to an online "
"account."
"",
MyMoneyFile::instance()->account(accId).name())));
return false;
}
//setupAccountReference(acc, ba); // needed?
AB_JOB *abJob = AB_JobSepaTransfer_new(abAccount);
int rv = AB_Job_CheckAvailability(abJob);
if (rv) {
qDebug("AB_ERROR_OFFSET is %i", AB_ERROR_OFFSET);
job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Error, "AqBanking",
QString("Sepa credit transfers for account \"%1\" are not available, error code %2.").arg(MyMoneyFile::instance()->account(accId).name(), rv)
)
);
return false;
}
AB_TRANSACTION *AbTransaction = AB_Transaction_new();
// Recipient
payeeIdentifiers::ibanBic beneficiaryAcc = job.constTask()->beneficiaryTyped();
AB_Transaction_SetRemoteName(AbTransaction, GWEN_StringList_fromQString(beneficiaryAcc.ownerName()));
AB_Transaction_SetRemoteIban(AbTransaction, beneficiaryAcc.electronicIban().toUtf8().constData());
AB_Transaction_SetRemoteBic(AbTransaction, beneficiaryAcc.fullStoredBic().toUtf8().constData());
// Origin Account
AB_Transaction_SetLocalAccount(AbTransaction, abAccount);
// Purpose
QStringList qPurpose = job.constTask()->purpose().split('\n');
GWEN_STRINGLIST *purpose = GWEN_StringList_fromQStringList(qPurpose);
AB_Transaction_SetPurpose(AbTransaction, purpose);
GWEN_StringList_free(purpose);
// Reference
// AqBanking duplicates the string. This should be safe.
AB_Transaction_SetEndToEndReference(AbTransaction, job.constTask()->endToEndReference().toUtf8().constData());
// Other Fields
AB_Transaction_SetTextKey(AbTransaction, job.constTask()->textKey());
AB_Transaction_SetValue(AbTransaction, AB_Value_fromMyMoneyMoney(job.constTask()->value()));
/** @todo LOW remove Debug info */
qDebug() << "SetTransaction: " << AB_Job_SetTransaction(abJob, AbTransaction);
GWEN_DB_NODE *gwenNode = AB_Job_GetAppData(abJob);
GWEN_DB_SetCharValue(gwenNode, GWEN_DB_FLAGS_DEFAULT, "kmmOnlineJobId", m_kbanking->mappingId(job).toLatin1().constData());
qDebug() << "Enqueue: " << m_kbanking->enqueueJob(abJob);
//delete localAcc;
return true;
}
void KBanking::startPasswordTimer()
{
if (d->passwordCacheTimer->isActive())
d->passwordCacheTimer->stop();
d->passwordCacheTimer->start();
}
void KBanking::slotClearPasswordCache()
{
m_kbanking->clearPasswordCache();
}
void KBanking::slotImport()
{
m_statementCount = 0;
statementInterface()->resetMessages();
if (!m_kbanking->interactiveImport())
qWarning("Error on import dialog");
else
statementInterface()->showMessages(m_statementCount);
}
bool KBanking::importStatement(const MyMoneyStatement& s)
{
m_statementCount++;
return !statementInterface()->import(s).isEmpty();
}
MyMoneyAccount KBanking::account(const QString& key, const QString& value) const
{
return statementInterface()->account(key, value);
}
void KBanking::setAccountOnlineParameters(const MyMoneyAccount& acc, const MyMoneyKeyValueContainer& kvps) const
{
return statementInterface()->setAccountOnlineParameters(acc, kvps);
}
KBankingExt::KBankingExt(KBanking* parent, const char* appname, const char* fname)
: AB_Banking(appname, fname)
, m_parent(parent)
, _jobQueue(0)
{
m_sepaKeywords = {QString::fromUtf8("SEPA-BASISLASTSCHRIFT"), QString::fromUtf8("SEPA-ÜBERWEISUNG")};
}
int KBankingExt::init()
{
int rv = AB_Banking::init();
if (rv < 0)
return rv;
rv = onlineInit();
if (rv) {
fprintf(stderr, "Error on online init (%d).\n", rv);
AB_Banking::fini();
return rv;
}
_jobQueue = AB_Job_List2_new();
return 0;
}
int KBankingExt::fini()
{
if (_jobQueue) {
AB_Job_List2_FreeAll(_jobQueue);
_jobQueue = 0;
}
const int rv = onlineFini();
if (rv) {
AB_Banking::fini();
return rv;
}
return AB_Banking::fini();
}
int KBankingExt::executeQueue(AB_IMEXPORTER_CONTEXT *ctx)
{
m_parent->startPasswordTimer();
int rv = AB_Banking::executeJobs(_jobQueue, ctx);
if (rv != 0) {
qDebug() << "Sending queue by aqbanking got error no " << rv;
}
/** check result of each job */
AB_JOB_LIST2_ITERATOR* jobIter = AB_Job_List2_First(_jobQueue);
if (jobIter) {
AB_JOB* abJob = AB_Job_List2Iterator_Data(jobIter);
while (abJob) {
GWEN_DB_NODE* gwenNode = AB_Job_GetAppData(abJob);
if (gwenNode == 0) {
qWarning("Executed AB_Job without KMyMoney id");
abJob = AB_Job_List2Iterator_Next(jobIter);
break;
}
QString jobIdent = QString::fromUtf8(GWEN_DB_GetCharValue(gwenNode, "kmmOnlineJobId", 0, ""));
onlineJob job = m_parent->m_onlineJobQueue.value(jobIdent);
if (job.isNull()) {
// It should not be possiblie that this will happen (only if AqBanking fails heavily).
//! @todo correct exception text
qWarning("Executed a job which was not in queue. Please inform the KMyMoney developers.");
abJob = AB_Job_List2Iterator_Next(jobIter);
continue;
}
AB_JOB_STATUS abStatus = AB_Job_GetStatus(abJob);
if (abStatus == AB_Job_StatusSent
|| abStatus == AB_Job_StatusPending
|| abStatus == AB_Job_StatusFinished
|| abStatus == AB_Job_StatusError
|| abStatus == AB_Job_StatusUnknown)
job.setJobSend();
if (abStatus == AB_Job_StatusFinished)
job.setBankAnswer(eMyMoney::OnlineJob::sendingState::acceptedByBank);
else if (abStatus == AB_Job_StatusError || abStatus == AB_Job_StatusUnknown)
job.setBankAnswer(eMyMoney::OnlineJob::sendingState::sendingError);
job.addJobMessage(onlineJobMessage(eMyMoney::OnlineJob::MessageType::Debug, "KBanking", "Job was processed"));
m_parent->m_onlineJobQueue.insert(jobIdent, job);
abJob = AB_Job_List2Iterator_Next(jobIter);
}
AB_Job_List2Iterator_free(jobIter);
}
AB_JOB_LIST2 *oldQ = _jobQueue;
_jobQueue = AB_Job_List2_new();
AB_Job_List2_FreeAll(oldQ);
emit m_parent->queueChanged();
m_parent->startPasswordTimer();
return rv;
}
void KBankingExt::clearPasswordCache()
{
/* clear password DB */
GWEN_Gui_SetPasswordStatus(NULL, NULL, GWEN_Gui_PasswordStatus_Remove, 0);
}
std::list KBankingExt::getEnqueuedJobs()
{
AB_JOB_LIST2 *ll;
std::list rl;
ll = _jobQueue;
if (ll && AB_Job_List2_GetSize(ll)) {
AB_JOB *j;
AB_JOB_LIST2_ITERATOR *it;
it = AB_Job_List2_First(ll);
assert(it);
j = AB_Job_List2Iterator_Data(it);
assert(j);
while (j) {
rl.push_back(j);
j = AB_Job_List2Iterator_Next(it);
}
AB_Job_List2Iterator_free(it);
}
return rl;
}
int KBankingExt::enqueueJob(AB_JOB *j)
{
assert(_jobQueue);
assert(j);
AB_Job_Attach(j);
AB_Job_List2_PushBack(_jobQueue, j);
return 0;
}
int KBankingExt::dequeueJob(AB_JOB *j)
{
assert(_jobQueue);
AB_Job_List2_Remove(_jobQueue, j);
AB_Job_free(j);
emit m_parent->queueChanged();
return 0;
}
void KBankingExt::transfer()
{
//m_parent->transfer();
}
bool KBankingExt::askMapAccount(const MyMoneyAccount& acc)
{
MyMoneyFile* file = MyMoneyFile::instance();
QString bankId;
QString accountId;
// extract some information about the bank. if we have a sortcode
// (BLZ) we display it, otherwise the name is enough.
try {
const MyMoneyInstitution &bank = file->institution(acc.institutionId());
bankId = bank.name();
if (!bank.sortcode().isEmpty())
bankId = bank.sortcode();
} catch (const MyMoneyException &e) {
// no bank assigned, we just leave the field emtpy
}
// extract account information. if we have an account number
// we show it, otherwise the name will be displayed
accountId = acc.number();
if (accountId.isEmpty())
accountId = acc.name();
// do the mapping. the return value of this method is either
// true, when the user mapped the account or false, if he
// decided to quit the dialog. So not really a great thing
// to present some more information.
KBMapAccount *w;
w = new KBMapAccount(this,
bankId.toUtf8().constData(),
accountId.toUtf8().constData());
if (w->exec() == QDialog::Accepted) {
AB_ACCOUNT *a;
a = w->getAccount();
assert(a);
DBG_NOTICE(0,
"Mapping application account \"%s\" to "
"online account \"%s/%s\"",
qPrintable(acc.name()),
AB_Account_GetBankCode(a),
AB_Account_GetAccountNumber(a));
// TODO remove the following line once we don't need backward compatibility
setAccountAlias(a, acc.id().toUtf8().constData());
qDebug("Setup mapping to '%s'", acc.id().toUtf8().constData());
setAccountAlias(a, mappingId(acc).toUtf8().constData());
qDebug("Setup mapping to '%s'", mappingId(acc).toUtf8().constData());
delete w;
return true;
}
delete w;
return false;
}
QString KBankingExt::mappingId(const MyMoneyObject& object) const
{
QString id = MyMoneyFile::instance()->storageId() + QLatin1Char('-') + object.id();
// AqBanking does not handle the enclosing parens, so we remove it
id.remove('{');
id.remove('}');
return id;
}
bool KBankingExt::interactiveImport()
{
AB_IMEXPORTER_CONTEXT *ctx;
GWEN_DIALOG *dlg;
int rv;
ctx = AB_ImExporterContext_new();
dlg = AB_ImporterDialog_new(getCInterface(), ctx, NULL);
if (dlg == NULL) {
DBG_ERROR(0, "Could not create importer dialog.");
AB_ImExporterContext_free(ctx);
return false;
}
rv = GWEN_Gui_ExecDialog(dlg, 0);
if (rv == 0) {
DBG_ERROR(0, "Aborted by user");
GWEN_Dialog_free(dlg);
AB_ImExporterContext_free(ctx);
return false;
}
if (!importContext(ctx, 0)) {
DBG_ERROR(0, "Error on importContext");
GWEN_Dialog_free(dlg);
AB_ImExporterContext_free(ctx);
return false;
}
GWEN_Dialog_free(dlg);
AB_ImExporterContext_free(ctx);
return true;
}
const AB_ACCOUNT_STATUS* KBankingExt::_getAccountStatus(AB_IMEXPORTER_ACCOUNTINFO *ai)
{
const AB_ACCOUNT_STATUS *ast;
const AB_ACCOUNT_STATUS *best;
best = 0;
ast = AB_ImExporterAccountInfo_GetFirstAccountStatus(ai);
while (ast) {
if (!best)
best = ast;
else {
const GWEN_TIME *tiBest;
const GWEN_TIME *ti;
tiBest = AB_AccountStatus_GetTime(best);
ti = AB_AccountStatus_GetTime(ast);
if (!tiBest) {
best = ast;
} else {
if (ti) {
double d;
/* we have two times, compare them */
d = GWEN_Time_Diff(ti, tiBest);
if (d > 0)
/* newer */
best = ast;
}
}
}
ast = AB_ImExporterAccountInfo_GetNextAccountStatus(ai);
} /* while */
return best;
}
void KBankingExt::_xaToStatement(MyMoneyStatement &ks,
const MyMoneyAccount& acc,
const AB_TRANSACTION *t)
{
const GWEN_STRINGLIST *sl;
QString s;
QString memo;
const char *p;
const AB_VALUE *val;
const GWEN_TIME *ti;
const GWEN_TIME *startTime = 0;
MyMoneyStatement::Transaction kt;
unsigned long h;
kt.m_fees = MyMoneyMoney();
// bank's transaction id
p = AB_Transaction_GetFiId(t);
if (p)
kt.m_strBankID = QString("ID ") + QString::fromUtf8(p);
// payee
s.truncate(0);
sl = AB_Transaction_GetRemoteName(t);
if (sl) {
GWEN_STRINGLISTENTRY *se;
se = GWEN_StringList_FirstEntry(sl);
while (se) {
p = GWEN_StringListEntry_Data(se);
assert(p);
s += QString::fromUtf8(p);
se = GWEN_StringListEntry_Next(se);
} // while
}
kt.m_strPayee = s;
// memo
// The variable 's' contains the old method of extracting
// the memo which added a linefeed after each part received
// from AqBanking. The new variable 'memo' does not have
// this inserted linefeed. We keep the variable 's' to
// construct the hash-value to retrieve the reference
s.truncate(0);
sl = AB_Transaction_GetPurpose(t);
if (sl) {
GWEN_STRINGLISTENTRY *se;
bool insertLineSep = false;
se = GWEN_StringList_FirstEntry(sl);
while (se) {
p = GWEN_StringListEntry_Data(se);
assert(p);
if (insertLineSep)
s += '\n';
insertLineSep = true;
s += QString::fromUtf8(p).trimmed();
memo += QString::fromUtf8(p).trimmed();
se = GWEN_StringListEntry_Next(se);
} // while
// Sparda / Netbank hack: the software these banks use stores
// parts of the payee name in the beginning of the purpose field
// in case the payee name exceeds the 27 character limit. This is
// the case, when one of the strings listed in m_sepaKeywords is part
// of the purpose fields but does not start at the beginning. In this
// case, the part leading up to the keyword is to be treated as the
// tail of the payee. Also, a blank is inserted after the keyword.
QSet::const_iterator itk;
for (itk = m_sepaKeywords.constBegin(); itk != m_sepaKeywords.constEnd(); ++itk) {
int idx = s.indexOf(*itk);
if (idx >= 0) {
if (idx > 0) {
// re-add a possibly removed blank to name
if (kt.m_strPayee.length() < 27)
kt.m_strPayee += ' ';
kt.m_strPayee += s.left(idx);
s = s.mid(idx);
}
s = QString("%1 %2").arg(*itk).arg(s.mid((*itk).length()));
// now do the same for 'memo' except for updating the payee
idx = memo.indexOf(*itk);
if (idx >= 0) {
if (idx > 0) {
memo = memo.mid(idx);
}
}
memo = QString("%1 %2").arg(*itk).arg(memo.mid((*itk).length()));
break;
}
}
// in case we have some SEPA fields filled with information
// we add them to the memo field
p = AB_Transaction_GetEndToEndReference(t);
if (p) {
s += QString(", EREF: %1").arg(p);
if(memo.length())
memo.append('\n');
memo.append(QString("EREF: %1").arg(p));
}
p = AB_Transaction_GetCustomerReference(t);
if (p) {
s += QString(", CREF: %1").arg(p);
if(memo.length())
memo.append('\n');
memo.append(QString("CREF: %1").arg(p));
}
p = AB_Transaction_GetMandateId(t);
if (p) {
s += QString(", MREF: %1").arg(p);
if(memo.length())
memo.append('\n');
memo.append(QString("MREF: %1").arg(p));
}
p = AB_Transaction_GetCreditorSchemeId(t);
if (p) {
s += QString(", CRED: %1").arg(p);
if(memo.length())
memo.append('\n');
memo.append(QString("CRED: %1").arg(p));
}
p = AB_Transaction_GetOriginatorIdentifier(t);
if (p) {
s += QString(", DEBT: %1").arg(p);
if(memo.length())
memo.append('\n');
memo.append(QString("DEBT: %1").arg(p));
}
}
const MyMoneyKeyValueContainer& kvp = acc.onlineBankingSettings();
// check if we need the version with or without linebreaks
if (kvp.value("kbanking-memo-removelinebreaks").compare(QLatin1String("no"))) {
kt.m_strMemo = memo;
} else {
kt.m_strMemo = s;
}
// calculate the hash code and start with the payee info
// and append the memo field
h = MyMoneyTransaction::hash(kt.m_strPayee.trimmed());
h = MyMoneyTransaction::hash(s, h);
// see, if we need to extract the payee from the memo field
QString rePayee = kvp.value("kbanking-payee-regexp");
if (!rePayee.isEmpty() && kt.m_strPayee.isEmpty()) {
QString reMemo = kvp.value("kbanking-memo-regexp");
QStringList exceptions = kvp.value("kbanking-payee-exceptions").split(';', QString::SkipEmptyParts);
bool needExtract = true;
QStringList::const_iterator it_s;
for (it_s = exceptions.constBegin(); needExtract && it_s != exceptions.constEnd(); ++it_s) {
QRegExp exp(*it_s, Qt::CaseInsensitive);
if (exp.indexIn(kt.m_strMemo) != -1) {
needExtract = false;
}
}
if (needExtract) {
QRegExp expPayee(rePayee, Qt::CaseInsensitive);
QRegExp expMemo(reMemo, Qt::CaseInsensitive);
if (expPayee.indexIn(kt.m_strMemo) != -1) {
kt.m_strPayee = expPayee.cap(1);
if (expMemo.indexIn(kt.m_strMemo) != -1) {
kt.m_strMemo = expMemo.cap(1);
}
}
}
}
kt.m_strPayee = kt.m_strPayee.trimmed();
// date
ti = AB_Transaction_GetDate(t);
if (!ti)
ti = AB_Transaction_GetValutaDate(t);
if (ti) {
int year, month, day;
if (!startTime)
startTime = ti;
/*else { dead code
if (GWEN_Time_Diff(ti, startTime) < 0)
startTime = ti;
}*/
if (!GWEN_Time_GetBrokenDownDate(ti, &day, &month, &year)) {
kt.m_datePosted = QDate(year, month + 1, day);
}
} else {
DBG_WARN(0, "No date for transaction");
}
// value
val = AB_Transaction_GetValue(t);
if (val) {
if (ks.m_strCurrency.isEmpty()) {
p = AB_Value_GetCurrency(val);
if (p)
ks.m_strCurrency = p;
} else {
p = AB_Value_GetCurrency(val);
if (p)
s = p;
if (ks.m_strCurrency.toLower() != s.toLower()) {
// TODO: handle currency difference
DBG_ERROR(0, "Mixed currencies currently not allowed");
}
}
kt.m_amount = MyMoneyMoney(AB_Value_GetValueAsDouble(val));
// The initial implementation of this feature was based on
// a denominator of 100. Since the denominator might be
// different nowadays, we make sure to use 100 for the
// duplicate detection
QString tmpVal = kt.m_amount.formatMoney(100, false);
tmpVal.remove(QRegExp("[,\\.]"));
tmpVal += QLatin1String("/100");
h = MyMoneyTransaction::hash(tmpVal, h);
} else {
DBG_WARN(0, "No value for transaction");
}
if (startTime) {
int year, month, day;
if (!GWEN_Time_GetBrokenDownDate(startTime, &day, &month, &year)) {
QDate d(year, month + 1, day);
if (!ks.m_dateBegin.isValid())
ks.m_dateBegin = d;
else if (d < ks.m_dateBegin)
ks.m_dateBegin = d;
if (!ks.m_dateEnd.isValid())
ks.m_dateEnd = d;
else if (d > ks.m_dateEnd)
ks.m_dateEnd = d;
}
} else {
DBG_WARN(0, "No date in current transaction");
}
// add information about remote account to memo in case we have something
const char *remoteAcc = AB_Transaction_GetRemoteAccountNumber(t);
const char *remoteBankCode = AB_Transaction_GetRemoteBankCode(t);
if (remoteAcc && remoteBankCode) {
kt.m_strMemo += QString("\n%1/%2").arg(remoteBankCode, remoteAcc);
}
// make hash value unique in case we don't have one already
if (kt.m_strBankID.isEmpty()) {
QString hashBase;
hashBase.sprintf("%s-%07lx", qPrintable(kt.m_datePosted.toString(Qt::ISODate)), h);
int idx = 1;
QString hash;
for (;;) {
hash = QString("%1-%2").arg(hashBase).arg(idx);
QMap::const_iterator it;
it = m_hashMap.constFind(hash);
if (it == m_hashMap.constEnd()) {
m_hashMap[hash] = true;
break;
}
++idx;
}
kt.m_strBankID = QString("%1-%2").arg(acc.id()).arg(hash);
}
// store transaction
ks.m_listTransactions += kt;
}
bool KBankingExt::importAccountInfo(AB_IMEXPORTER_ACCOUNTINFO *ai,
uint32_t /*flags*/)
{
const char *p;
DBG_INFO(0, "Importing account...");
// account number
MyMoneyStatement ks;
p = AB_ImExporterAccountInfo_GetAccountNumber(ai);
if (p) {
ks.m_strAccountNumber = m_parent->stripLeadingZeroes(p);
}
p = AB_ImExporterAccountInfo_GetBankCode(ai);
if (p) {
ks.m_strRoutingNumber = m_parent->stripLeadingZeroes(p);
}
- MyMoneyAccount kacc = m_parent->account("kbanking-acc-ref", QString("%1-%2").arg(ks.m_strRoutingNumber, ks.m_strAccountNumber));
- ks.m_accountId = kacc.id();
+ MyMoneyAccount kacc;
+ if (!ks.m_strAccountNumber.isEmpty() || !ks.m_strRoutingNumber.isEmpty()) {
+ kacc = m_parent->account("kbanking-acc-ref", QString("%1-%2").arg(ks.m_strRoutingNumber, ks.m_strAccountNumber));
+ ks.m_accountId = kacc.id();
+ }
// account name
p = AB_ImExporterAccountInfo_GetAccountName(ai);
if (p)
ks.m_strAccountName = p;
// account type
switch (AB_ImExporterAccountInfo_GetType(ai)) {
case AB_AccountType_Bank:
ks.m_eType = eMyMoney::Statement::Type::Savings;
break;
case AB_AccountType_CreditCard:
ks.m_eType = eMyMoney::Statement::Type::CreditCard;
break;
case AB_AccountType_Checking:
ks.m_eType = eMyMoney::Statement::Type::Checkings;
break;
case AB_AccountType_Savings:
ks.m_eType = eMyMoney::Statement::Type::Savings;
break;
case AB_AccountType_Investment:
ks.m_eType = eMyMoney::Statement::Type::Investment;
break;
case AB_AccountType_Cash:
default:
ks.m_eType = eMyMoney::Statement::Type::None;
}
// account status
const AB_ACCOUNT_STATUS* ast = _getAccountStatus(ai);
if (ast) {
const AB_BALANCE *bal;
bal = AB_AccountStatus_GetBookedBalance(ast);
if (!bal)
bal = AB_AccountStatus_GetNotedBalance(ast);
if (bal) {
const AB_VALUE* val = AB_Balance_GetValue(bal);
if (val) {
DBG_INFO(0, "Importing balance");
ks.m_closingBalance = AB_Value_toMyMoneyMoney(val);
p = AB_Value_GetCurrency(val);
if (p)
ks.m_strCurrency = p;
}
const GWEN_TIME* ti = AB_Balance_GetTime(bal);
if (ti) {
int year, month, day;
if (!GWEN_Time_GetBrokenDownDate(ti, &day, &month, &year))
ks.m_dateEnd = QDate(year, month + 1, day);
} else {
DBG_WARN(0, "No time for balance");
}
} else {
DBG_WARN(0, "No account balance");
}
} else {
DBG_WARN(0, "No account status");
}
// clear hash map
m_hashMap.clear();
// get all transactions
const AB_TRANSACTION* t = AB_ImExporterAccountInfo_GetFirstTransaction(ai);
while (t) {
_xaToStatement(ks, kacc, t);
t = AB_ImExporterAccountInfo_GetNextTransaction(ai);
}
// import them
if (!m_parent->importStatement(ks)) {
if (KMessageBox::warningYesNo(0,
i18n("Error importing statement. Do you want to continue?"),
i18n("Critical Error")) == KMessageBox::No) {
DBG_ERROR(0, "User aborted");
return false;
}
}
return true;
}
K_PLUGIN_FACTORY_WITH_JSON(KBankingFactory, "kbanking.json", registerPlugin();)
#include "kbanking.moc"