1 /*
2  * Copyright 2000-2002  Michael Edwardes <mte@users.sourceforge.net>
3  * Copyright 2001       Felix Rodriguez <frodriguez@users.sourceforge.net>
4  * Copyright 2002-2003  Kevin Tambascio <ktambascio@users.sourceforge.net>
5  * Copyright 2006-2017  Thomas Baumgart <tbaumgart@kde.org>
6  * Copyright 2006       Ace Jones <acejones@users.sourceforge.net>
7  * Copyright 2017-2018  Łukasz Wojniłowicz <lukasz.wojnilowicz@gmail.com>
8  *
9  * This program is free software; you can redistribute it and/or
10  * modify it under the terms of the GNU General Public License as
11  * published by the Free Software Foundation; either version 2 of
12  * the License, or (at your option) any later version.
13  *
14  * This program is distributed in the hope that it will be useful,
15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17  * GNU General Public License for more details.
18  *
19  * You should have received a copy of the GNU General Public License
20  * along with this program.  If not, see <http://www.gnu.org/licenses/>.
21  */
22 
23 #include "mymoneyaccount.h"
24 #include "mymoneyaccount_p.h"
25 
26 // ----------------------------------------------------------------------------
27 // QT Includes
28 
29 #include <QRegExp>
30 #include <QPixmap>
31 #include <QPixmapCache>
32 #include <QPainter>
33 #include <QIcon>
34 #include <QDebug>
35 
36 // ----------------------------------------------------------------------------
37 // KDE Includes
38 
39 #include <KLocalizedString>
40 
41 // ----------------------------------------------------------------------------
42 // Project Includes
43 
44 #include "mymoneyutils.h"
45 #include "mymoneyexception.h"
46 #include "mymoneysplit.h"
47 #include "mymoneyfile.h"
48 #include "mymoneysecurity.h"
49 #include "mymoneyinstitution.h"
50 #include "mymoneypayee.h"
51 #include "payeeidentifier/payeeidentifiertyped.h"
52 #include "payeeidentifier/ibanbic/ibanbic.h"
53 #include "payeeidentifier/nationalaccount/nationalaccount.h"
54 #include "icons/icons.h"
55 
56 using namespace Icons;
57 
MyMoneyAccount()58 MyMoneyAccount::MyMoneyAccount() :
59   MyMoneyObject(*new MyMoneyAccountPrivate),
60   MyMoneyKeyValueContainer()
61 {
62 }
63 
MyMoneyAccount(const QString & id)64 MyMoneyAccount::MyMoneyAccount(const QString &id):
65   MyMoneyObject(*new MyMoneyAccountPrivate, id),
66   MyMoneyKeyValueContainer()
67 {
68 }
69 
MyMoneyAccount(const MyMoneyAccount & other)70 MyMoneyAccount::MyMoneyAccount(const MyMoneyAccount& other) :
71   MyMoneyObject(*new MyMoneyAccountPrivate(*other.d_func()), other.id()),
72   MyMoneyKeyValueContainer(other)
73 {
74 }
75 
MyMoneyAccount(const QString & id,const MyMoneyAccount & other)76 MyMoneyAccount::MyMoneyAccount(const QString& id, const MyMoneyAccount& other) :
77   MyMoneyObject(*new MyMoneyAccountPrivate(*other.d_func()), id),
78   MyMoneyKeyValueContainer(other)
79 {
80 }
81 
~MyMoneyAccount()82 MyMoneyAccount::~MyMoneyAccount()
83 {
84 }
85 
touch()86 void MyMoneyAccount::touch()
87 {
88   setLastModified(QDate::currentDate());
89 }
90 
accountType() const91 eMyMoney::Account::Type MyMoneyAccount::accountType() const
92 {
93   Q_D(const MyMoneyAccount);
94   return d->m_accountType;
95 }
96 
setAccountType(const Account::Type type)97 void MyMoneyAccount::setAccountType(const Account::Type type)
98 {
99   Q_D(MyMoneyAccount);
100   d->m_accountType = type;
101 }
102 
institutionId() const103 QString MyMoneyAccount::institutionId() const
104 {
105   Q_D(const MyMoneyAccount);
106   return d->m_institution;
107 }
108 
setInstitutionId(const QString & id)109 void MyMoneyAccount::setInstitutionId(const QString& id)
110 {
111   Q_D(MyMoneyAccount);
112   d->m_institution = id;
113 }
114 
name() const115 QString MyMoneyAccount::name() const
116 {
117   Q_D(const MyMoneyAccount);
118   return d->m_name;
119 }
120 
setName(const QString & name)121 void MyMoneyAccount::setName(const QString& name)
122 {
123   Q_D(MyMoneyAccount);
124   d->m_name = name;
125 }
126 
number() const127 QString MyMoneyAccount::number() const
128 {
129   Q_D(const MyMoneyAccount);
130   return d->m_number;
131 }
132 
setNumber(const QString & number)133 void MyMoneyAccount::setNumber(const QString& number)
134 {
135   Q_D(MyMoneyAccount);
136   d->m_number = number;
137 }
138 
description() const139 QString MyMoneyAccount::description() const
140 {
141   Q_D(const MyMoneyAccount);
142   return d->m_description;
143 }
144 
setDescription(const QString & desc)145 void MyMoneyAccount::setDescription(const QString& desc)
146 {
147   Q_D(MyMoneyAccount);
148   d->m_description = desc;
149 }
150 
openingDate() const151 QDate MyMoneyAccount::openingDate() const
152 {
153   Q_D(const MyMoneyAccount);
154   return d->m_openingDate;
155 }
156 
setOpeningDate(const QDate & date)157 void MyMoneyAccount::setOpeningDate(const QDate& date)
158 {
159   Q_D(MyMoneyAccount);
160   d->m_openingDate = date;
161 }
162 
lastReconciliationDate() const163 QDate MyMoneyAccount::lastReconciliationDate() const
164 {
165   Q_D(const MyMoneyAccount);
166   return d->m_lastReconciliationDate;
167 }
168 
setLastReconciliationDate(const QDate & date)169 void MyMoneyAccount::setLastReconciliationDate(const QDate& date)
170 {
171   Q_D(MyMoneyAccount);
172   d->m_lastReconciliationDate = date;
173 }
174 
lastModified() const175 QDate MyMoneyAccount::lastModified() const
176 {
177   Q_D(const MyMoneyAccount);
178   return d->m_lastModified;
179 }
180 
setLastModified(const QDate & date)181 void MyMoneyAccount::setLastModified(const QDate& date)
182 {
183   Q_D(MyMoneyAccount);
184   d->m_lastModified = date;
185 }
186 
parentAccountId() const187 QString MyMoneyAccount::parentAccountId() const
188 {
189   Q_D(const MyMoneyAccount);
190   return d->m_parentAccount;
191 }
192 
setParentAccountId(const QString & parent)193 void MyMoneyAccount::setParentAccountId(const QString& parent)
194 {
195   Q_D(MyMoneyAccount);
196   d->m_parentAccount = parent;
197 }
198 
accountList() const199 QStringList MyMoneyAccount::accountList() const
200 {
201   Q_D(const MyMoneyAccount);
202   return d->m_accountList;
203 }
204 
accountCount() const205 int MyMoneyAccount::accountCount() const
206 {
207   Q_D(const MyMoneyAccount);
208   return d->m_accountList.count();
209 }
210 
addAccountId(const QString & account)211 void MyMoneyAccount::addAccountId(const QString& account)
212 {
213   Q_D(MyMoneyAccount);
214   if (!d->m_accountList.contains(account))
215     d->m_accountList += account;
216 }
217 
removeAccountIds()218 void MyMoneyAccount::removeAccountIds()
219 {
220   Q_D(MyMoneyAccount);
221   d->m_accountList.clear();
222 }
223 
removeAccountId(const QString & account)224 void MyMoneyAccount::removeAccountId(const QString& account)
225 {
226   Q_D(MyMoneyAccount);
227   const auto pos = d->m_accountList.indexOf(account);
228   if (pos != -1)
229     d->m_accountList.removeAt(pos);
230 }
231 
operator ==(const MyMoneyAccount & right) const232 bool MyMoneyAccount::operator == (const MyMoneyAccount& right) const
233 {
234   Q_D(const MyMoneyAccount);
235   auto d2 = static_cast<const MyMoneyAccountPrivate *>(right.d_func());
236   return (MyMoneyKeyValueContainer::operator==(right) &&
237           MyMoneyObject::operator==(right) &&
238           (d->m_accountList == d2->m_accountList) &&
239           (d->m_accountType == d2->m_accountType) &&
240           (d->m_lastModified == d2->m_lastModified) &&
241           (d->m_lastReconciliationDate == d2->m_lastReconciliationDate) &&
242           ((d->m_name.length() == 0 && d2->m_name.length() == 0) || (d->m_name == d2->m_name)) &&
243           ((d->m_number.length() == 0 && d2->m_number.length() == 0) || (d->m_number == d2->m_number)) &&
244           ((d->m_description.length() == 0 && d2->m_description.length() == 0) || (d->m_description == d2->m_description)) &&
245           (d->m_openingDate == d2->m_openingDate) &&
246           (d->m_parentAccount == d2->m_parentAccount) &&
247           (d->m_currencyId == d2->m_currencyId) &&
248           (d->m_institution == d2->m_institution));
249 }
250 
accountGroup() const251 Account::Type MyMoneyAccount::accountGroup() const
252 {
253   Q_D(const MyMoneyAccount);
254   switch (d->m_accountType) {
255     case Account::Type::Checkings:
256     case Account::Type::Savings:
257     case Account::Type::Cash:
258     case Account::Type::Currency:
259     case Account::Type::Investment:
260     case Account::Type::MoneyMarket:
261     case Account::Type::CertificateDep:
262     case Account::Type::AssetLoan:
263     case Account::Type::Stock:
264       return Account::Type::Asset;
265 
266     case Account::Type::CreditCard:
267     case Account::Type::Loan:
268       return Account::Type::Liability;
269 
270     default:
271       return d->m_accountType;
272   }
273 }
274 
currencyId() const275 QString MyMoneyAccount::currencyId() const
276 {
277   Q_D(const MyMoneyAccount);
278   return d->m_currencyId;
279 }
280 
tradingCurrencyId() const281 QString MyMoneyAccount::tradingCurrencyId() const
282 {
283   const auto file = MyMoneyFile::instance();
284 
285   // First, get the trading currency (formerly deep currency)
286   auto deepcurrency = file->security(currencyId());
287   if (!deepcurrency.isCurrency())
288     deepcurrency = file->security(deepcurrency.tradingCurrency());
289 
290   // Return the trading currency's ID
291   return deepcurrency.id();
292 }
293 
isForeignCurrency() const294 bool MyMoneyAccount::isForeignCurrency() const
295 {
296   return (tradingCurrencyId() != MyMoneyFile::instance()->baseCurrency().id());
297 }
298 
setCurrencyId(const QString & id)299 void MyMoneyAccount::setCurrencyId(const QString& id)
300 {
301   Q_D(MyMoneyAccount);
302   d->m_currencyId = id;
303 }
304 
isAssetLiability() const305 bool MyMoneyAccount::isAssetLiability() const
306 {
307   return accountGroup() == Account::Type::Asset || accountGroup() == Account::Type::Liability;
308 }
309 
isIncomeExpense() const310 bool MyMoneyAccount::isIncomeExpense() const
311 {
312   return accountGroup() == Account::Type::Income || accountGroup() == Account::Type::Expense;
313 }
314 
isLoan() const315 bool MyMoneyAccount::isLoan() const
316 {
317   return accountType() == Account::Type::Loan || accountType() == Account::Type::AssetLoan;
318 }
319 
isInvest() const320 bool MyMoneyAccount::isInvest() const
321 {
322   return accountType() == Account::Type::Stock;
323 }
324 
isLiquidAsset() const325 bool MyMoneyAccount::isLiquidAsset() const
326 {
327   return accountType() == Account::Type::Checkings ||
328          accountType() == Account::Type::Savings ||
329          accountType() == Account::Type::Cash;
330 }
331 
isLiquidLiability() const332 bool MyMoneyAccount::isLiquidLiability() const
333 {
334   return accountType() == Account::Type::CreditCard;
335 }
336 
isCostCenterRequired() const337 bool MyMoneyAccount::isCostCenterRequired() const
338 {
339   return value("CostCenter").toLower() == QLatin1String("yes");
340 }
341 
setCostCenterRequired(bool required)342 void MyMoneyAccount::setCostCenterRequired(bool required)
343 {
344   if(required) {
345     setValue("CostCenter", "yes");
346   } else {
347     deletePair("CostCenter");
348   }
349 }
350 
hasReferenceTo(const QString & id) const351 bool MyMoneyAccount::hasReferenceTo(const QString& id) const
352 {
353   Q_D(const MyMoneyAccount);
354   return (id == d->m_institution) || (id == d->m_parentAccount) || (id == d->m_currencyId);
355 }
356 
setOnlineBankingSettings(const MyMoneyKeyValueContainer & values)357 void MyMoneyAccount::setOnlineBankingSettings(const MyMoneyKeyValueContainer& values)
358 {
359   Q_D(MyMoneyAccount);
360   d->m_onlineBankingSettings = values;
361 }
362 
onlineBankingSettings() const363 MyMoneyKeyValueContainer MyMoneyAccount::onlineBankingSettings() const
364 {
365   Q_D(const MyMoneyAccount);
366   return d->m_onlineBankingSettings;
367 }
368 
setClosed(bool closed)369 void MyMoneyAccount::setClosed(bool closed)
370 {
371   if (closed)
372     setValue("mm-closed", "yes");
373   else
374     deletePair("mm-closed");
375 }
376 
isClosed() const377 bool MyMoneyAccount::isClosed() const
378 {
379   return !(value("mm-closed").isEmpty());
380 }
381 
fraction(const MyMoneySecurity & sec) const382 int MyMoneyAccount::fraction(const MyMoneySecurity& sec) const
383 {
384   Q_D(const MyMoneyAccount);
385   int fraction;
386   if (d->m_accountType == Account::Type::Cash)
387     fraction = sec.smallestCashFraction();
388   else
389     fraction = sec.smallestAccountFraction();
390   return fraction;
391 }
392 
fraction(const MyMoneySecurity & sec)393 int MyMoneyAccount::fraction(const MyMoneySecurity& sec)
394 {
395   Q_D(MyMoneyAccount);
396   if (d->m_accountType == Account::Type::Cash)
397     d->m_fraction = sec.smallestCashFraction();
398   else
399     d->m_fraction = sec.smallestAccountFraction();
400   return d->m_fraction;
401 }
402 
fraction() const403 int MyMoneyAccount::fraction() const
404 {
405   Q_D(const MyMoneyAccount);
406   return d->m_fraction;
407 }
408 
isCategory() const409 bool MyMoneyAccount::isCategory() const
410 {
411   Q_D(const MyMoneyAccount);
412   return d->m_accountType == Account::Type::Income || d->m_accountType == Account::Type::Expense;
413 }
414 
brokerageName() const415 QString MyMoneyAccount::brokerageName() const
416 {
417   Q_D(const MyMoneyAccount);
418   if (d->m_accountType == Account::Type::Investment)
419     return QString("%1 (%2)").arg(d->m_name, i18nc("Brokerage (suffix for account names)", "Brokerage"));
420   return d->m_name;
421 }
422 
balance() const423 MyMoneyMoney MyMoneyAccount::balance() const
424 {
425   Q_D(const MyMoneyAccount);
426   return d->m_balance;
427 }
428 
adjustBalance(const MyMoneySplit & s,bool reverse)429 void MyMoneyAccount::adjustBalance(const MyMoneySplit& s, bool reverse)
430 {
431   Q_D(MyMoneyAccount);
432   if (s.action() == MyMoneySplit::actionName(eMyMoney::Split::Action::SplitShares)) {
433     if (reverse)
434       d->m_balance = d->m_balance / s.shares();
435     else
436       d->m_balance = d->m_balance * s.shares();
437   } else {
438     if (reverse)
439       d->m_balance -= s.shares();
440     else
441       d->m_balance += s.shares();
442   }
443 
444 }
445 
setBalance(const MyMoneyMoney & val)446 void MyMoneyAccount::setBalance(const MyMoneyMoney& val)
447 {
448   Q_D(MyMoneyAccount);
449   d->m_balance = val;
450 }
451 
accountPixmap(const bool reconcileFlag,const int size) const452 QPixmap MyMoneyAccount::accountPixmap(const bool reconcileFlag, const int size) const
453 {
454   static const QHash<Account::Type, Icon> accToIco {
455     {Account::Type::Asset, Icon::Asset},
456     {Account::Type::Investment, Icon::Stock},
457     {Account::Type::Stock, Icon::Stock},
458     {Account::Type::MoneyMarket, Icon::Stock},
459     {Account::Type::Checkings, Icon::Checking},
460     {Account::Type::Savings, Icon::Savings},
461     {Account::Type::AssetLoan, Icon::LoanAsset},
462     {Account::Type::Loan, Icon::Loan},
463     {Account::Type::CreditCard, Icon::CreditCard},
464     {Account::Type::Asset, Icon::Asset},
465     {Account::Type::Cash, Icon::Cash},
466     {Account::Type::Income, Icon::Income},
467     {Account::Type::Expense, Icon::Expense},
468     {Account::Type::Equity, Icon::Equity}
469   };
470 
471   Icon ixIcon = accToIco.value(accountType(), Icon::Liability);
472 
473   QString kyIcon = accountTypeToString(accountType()) + QString::number(size);
474   QPixmap pxIcon;
475 
476   if (!QPixmapCache::find(kyIcon, &pxIcon)) {
477     pxIcon = Icons::get(ixIcon).pixmap(size); // Qt::AA_UseHighDpiPixmaps (in Qt 5.7) doesn't return highdpi pixmap
478     QPixmapCache::insert(kyIcon, pxIcon);
479   }
480 
481   if (isClosed())
482     ixIcon = Icon::AccountClosed;
483   else if (reconcileFlag)
484     ixIcon = Icon::Reconciled;
485   else if (hasOnlineMapping())
486     ixIcon = Icon::Download;
487   else
488     return pxIcon;
489 
490   QPixmap pxOverlay = Icons::get(ixIcon).pixmap(size);
491   QPainter pxPainter(&pxIcon);
492   const QSize szIcon = pxIcon.size();
493   pxPainter.drawPixmap(szIcon.width() / 2, szIcon.height() / 2,
494                        szIcon.width() / 2, szIcon.height() / 2, pxOverlay);
495   return pxIcon;
496 }
497 
accountTypeToString(const Account::Type accountType)498 QString MyMoneyAccount::accountTypeToString(const Account::Type accountType)
499 {
500   switch (accountType) {
501     case Account::Type::Checkings:
502       return i18nc("Account type", "Checking");
503     case Account::Type::Savings:
504       return i18nc("Account type", "Savings");
505     case Account::Type::CreditCard:
506       return i18nc("Account type", "Credit Card");
507     case Account::Type::Cash:
508       return i18nc("Account type", "Cash");
509     case Account::Type::Loan:
510       return i18nc("Account type", "Loan");
511     case Account::Type::CertificateDep:
512       return i18nc("Account type", "Certificate of Deposit");
513     case Account::Type::Investment:
514       return i18nc("Account type", "Investment");
515     case Account::Type::MoneyMarket:
516       return i18nc("Account type", "Money Market");
517     case Account::Type::Asset:
518       return i18nc("Account type", "Asset");
519     case Account::Type::Liability:
520       return i18nc("Account type", "Liability");
521     case Account::Type::Currency:
522       return i18nc("Account type", "Currency");
523     case Account::Type::Income:
524       return i18nc("Account type", "Income");
525     case Account::Type::Expense:
526       return i18nc("Account type", "Expense");
527     case Account::Type::AssetLoan:
528       return i18nc("Account type", "Investment Loan");
529     case Account::Type::Stock:
530       return i18nc("Account type", "Stock");
531     case Account::Type::Equity:
532       return i18nc("Account type", "Equity");
533     default:
534       return i18nc("Account type", "Unknown");
535   }
536 }
537 
addReconciliation(const QDate & date,const MyMoneyMoney & amount)538 bool MyMoneyAccount::addReconciliation(const QDate& date, const MyMoneyMoney& amount)
539 {
540   Q_D(MyMoneyAccount);
541   d->m_reconciliationHistory[date] = amount;
542   QString history, sep;
543   QMap<QDate, MyMoneyMoney>::const_iterator it;
544   for (it = d->m_reconciliationHistory.constBegin();
545        it != d->m_reconciliationHistory.constEnd();
546        ++it) {
547 
548     history += QString("%1%2:%3").arg(sep,
549                                       it.key().toString(Qt::ISODate),
550                                       (*it).toString());
551     sep = QLatin1Char(';');
552   }
553   setValue("reconciliationHistory", history);
554   return true;
555 }
556 
reconciliationHistory()557 QMap<QDate, MyMoneyMoney> MyMoneyAccount::reconciliationHistory()
558 {
559   Q_D(MyMoneyAccount);
560   // check if the internal history member is already loaded
561   if (d->m_reconciliationHistory.count() == 0
562       && !value("reconciliationHistory").isEmpty()) {
563     QStringList entries = value("reconciliationHistory").split(';');
564     foreach (const QString& entry, entries) {
565       QStringList parts = entry.split(':');
566       QDate date = QDate::fromString(parts[0], Qt::ISODate);
567       MyMoneyMoney amount(parts[1]);
568       if (parts.count() == 2 && date.isValid()) {
569         d->m_reconciliationHistory[date] = amount;
570       }
571     }
572   }
573 
574   return d->m_reconciliationHistory;
575 }
576 
577 /**
578  * @todo Improve setting of country for nationalAccount
579  */
payeeIdentifiers() const580 QList< payeeIdentifier > MyMoneyAccount::payeeIdentifiers() const
581 {
582   QList< payeeIdentifier > list;
583 
584   MyMoneyFile* file = MyMoneyFile::instance();
585 
586   const auto strIBAN = QStringLiteral("iban");
587   const auto strBIC = QStringLiteral("bic");
588   // Iban & Bic
589   if (!value(strIBAN).isEmpty()) {
590     payeeIdentifierTyped<payeeIdentifiers::ibanBic> iban(new payeeIdentifiers::ibanBic);
591     iban->setIban(value(strIBAN));
592     iban->setBic(file->institution(institutionId()).value(strBIC));
593     iban->setOwnerName(file->user().name());
594     list.append(iban);
595   }
596 
597   // National Account number
598   if (!number().isEmpty()) {
599     payeeIdentifierTyped<payeeIdentifiers::nationalAccount> national(new payeeIdentifiers::nationalAccount);
600     national->setAccountNumber(number());
601     national->setBankCode(file->institution(institutionId()).sortcode());
602     if (file->user().state().length() == 2)
603       national->setCountry(file->user().state());
604     national->setOwnerName(file->user().name());
605     list.append(national);
606   }
607 
608   return list;
609 }
610 
hasOnlineMapping() const611 bool MyMoneyAccount::hasOnlineMapping() const
612 {
613   Q_D(const MyMoneyAccount);
614   return !d->m_onlineBankingSettings.value(QLatin1String("provider")).isEmpty();
615 }
616 
stdAccName(eMyMoney::Account::Standard stdAccID)617 QString MyMoneyAccount::stdAccName(eMyMoney::Account::Standard stdAccID)
618 {
619   static const QHash<eMyMoney::Account::Standard, QString> stdAccNames {
620     {eMyMoney::Account::Standard::Liability, QStringLiteral("AStd::Liability")},
621     {eMyMoney::Account::Standard::Asset,     QStringLiteral("AStd::Asset")},
622     {eMyMoney::Account::Standard::Expense,   QStringLiteral("AStd::Expense")},
623     {eMyMoney::Account::Standard::Income,    QStringLiteral("AStd::Income")},
624     {eMyMoney::Account::Standard::Equity,    QStringLiteral("AStd::Equity")},
625   };
626   return stdAccNames.value(stdAccID);
627 }
628