void MyMoneyQifWriter::writeTransactionEntry(QTextStream &s, const MyMoneyTransaction& t, const QString& accountId)
{
  MyMoneyFile* file = MyMoneyFile::instance();
  MyMoneySplit split = t.splitByAccount(accountId);

  s << "D" << m_qifProfile.date(t.postDate()) << endl;

  switch(split.reconcileFlag()) {
    case MyMoneySplit::Cleared:
      s << "C*" << endl;
      break;

    case MyMoneySplit::Reconciled:
    case MyMoneySplit::Frozen:
      s << "CX" << endl;
      break;

    default:
      break;
  }

  if(split.memo().length() > 0) {
    QString m = split.memo();
    m.replace('\n', "\\n");
    s << "M" << m << endl;
  }

  s << "T" << m_qifProfile.value('T', split.value()) << endl;

  if(split.number().length() > 0)
    s << "N" << split.number() << endl;

  if(!split.payeeId().isEmpty()) {
    MyMoneyPayee payee = file->payee(split.payeeId());
    s << "P" << payee.name() << endl;
  }

  QValueList<MyMoneySplit> list = t.splits();
  if(list.count() > 1) {
    MyMoneySplit sp = t.splitByAccount(accountId, false);
    MyMoneyAccount acc = file->account(sp.accountId());
    if(acc.accountGroup() != MyMoneyAccount::Income
    && acc.accountGroup() != MyMoneyAccount::Expense) {
      s << "L" << m_qifProfile.accountDelimiter()[0]
              << MyMoneyFile::instance()->accountToCategory(sp.accountId())
              << m_qifProfile.accountDelimiter()[1] << endl;
    } else {
      s << "L" << file->accountToCategory(sp.accountId()) << endl;
    }
    if(list.count() > 2) {
      QValueList<MyMoneySplit>::ConstIterator it;
      for(it = list.begin(); it != list.end(); ++it) {
        if(!((*it) == split)) {
          writeSplitEntry(s, *it);
        }
      }
    }
  }
  s << "^" << endl;
}
void KSelectTransactionsDlg::addTransaction(const MyMoneyTransaction& t)
{
  QList<MyMoneySplit>::const_iterator it_s;
  for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
    if ((*it_s).accountId() == m_account.id()) {
      KMyMoneyRegister::Transaction* tr = KMyMoneyRegister::Register::transactionFactory(m_register, t, (*it_s), 0);
      // force full detail display
      tr->setNumRowsRegister(tr->numRowsRegister(true));
      break;
    }
  }
}
void MyMoneyStorageDump::dumpTransaction(QTextStream& s, IMyMoneyStorage* storage, const MyMoneyTransaction& it_t)
{
  s << "  ID = " << it_t.id() << "\n";
  s << "  Postdate  = " << it_t.postDate().toString(Qt::ISODate) << "\n";
  s << "  EntryDate = " << it_t.entryDate().toString(Qt::ISODate) << "\n";
  s << "  Commodity = [" << it_t.commodity() << "]\n";
  s << "  Memo = " << it_t.memo() << "\n";
  s << "  BankID = " << it_t.bankID() << "\n";
  dumpKVP("KVP:", s, it_t, 2);

  s << "  Splits\n";
  s << "  ------\n";
  QList<MyMoneySplit>::ConstIterator it_s;
  for (it_s = it_t.splits().constBegin(); it_s != it_t.splits().constEnd(); ++it_s) {
    s << "   ID = " << (*it_s).id() << "\n";
    s << "    Transaction = " << (*it_s).transactionId() << "\n";
    s << "    Payee = " << (*it_s).payeeId();
    if (!(*it_s).payeeId().isEmpty()) {
      MyMoneyPayee p = storage->payee((*it_s).payeeId());
      s << " (" << p.name() << ")" << "\n";
    } else
      s << " ()\n";
    for (int i = 0; i < (*it_s).tagIdList().size(); i++) {
      s << "    Tag = " << (*it_s).tagIdList()[i];
      if (!(*it_s).tagIdList()[i].isEmpty()) {
        MyMoneyTag ta = storage->tag((*it_s).tagIdList()[i]);
        s << " (" << ta.name() << ")" << "\n";
      } else
        s << " ()\n";
    }
    s << "    Account = " << (*it_s).accountId();
    MyMoneyAccount acc;
    try {
      acc = storage->account((*it_s).accountId());
      s << " (" << acc.name() << ") [" << acc.currencyId() << "]\n";
    } catch (const MyMoneyException &) {
      s << " (---) [---]\n";
    }
    s << "    Memo = " << (*it_s).memo() << "\n";
    if ((*it_s).value() == MyMoneyMoney::autoCalc)
      s << "    Value = will be calculated" << "\n";
    else
      s << "    Value = " << (*it_s).value().formatMoney("", 2)
      << " (" << (*it_s).value().toString() << ")\n";
    s << "    Shares = " << (*it_s).shares().formatMoney("", 2)
    << " (" << (*it_s).shares().toString() << ")\n";
    s << "    Action = '" << (*it_s).action() << "'\n";
    s << "    Nr = '" << (*it_s).number() << "'\n";
    s << "    ReconcileFlag = '" << reconcileToString((*it_s).reconcileFlag()) << "'\n";
    if ((*it_s).reconcileFlag() != MyMoneySplit::NotReconciled) {
      s << "    ReconcileDate = " << (*it_s).reconcileDate().toString(Qt::ISODate) << "\n";
    }
    s << "    BankID = " << (*it_s).bankID() << "\n";
    dumpKVP("KVP:", s, (*it_s), 4);
    s << "\n";
  }
  s << "\n";
}
Exemple #4
0
MyMoneyTransaction KMyMoneyUtils::scheduledTransaction(const MyMoneySchedule& schedule)
{
  MyMoneyTransaction t = schedule.transaction();

  try {
    if (schedule.type() == MyMoneySchedule::TYPE_LOANPAYMENT) {
      calculateAutoLoan(schedule, t, QMap<QString, MyMoneyMoney>());
    }
  } catch (const MyMoneyException &e) {
    qDebug("Unable to load schedule details for '%s' during transaction match: %s", qPrintable(schedule.name()), qPrintable(e.what()));
  }

  t.clearId();
  t.setEntryDate(QDate());
  return t;
}
Exemple #5
0
MyMoneySchedule KNewLoanWizard::schedule() const
{
  MyMoneySchedule sched(field("nameEdit").toString(),
                        MyMoneySchedule::TYPE_LOANPAYMENT,
                        MyMoneySchedule::occurrenceE(field("paymentFrequencyUnitEdit").toInt()), 1,
                        MyMoneySchedule::STYPE_OTHER,
                        QDate(),
                        QDate(),
                        false,
                        false);

  MyMoneyTransaction t = transaction();
  t.setPostDate(field("nextDueDateEdit").toDate());
  sched.setTransaction(t);

  return sched;
}
void MyMoneyQifWriter::writeAccountEntry(QTextStream &s, const QString& accountId, const QDate& startDate, const QDate& endDate)
{
  MyMoneyFile* file = MyMoneyFile::instance();
  MyMoneyAccount account;

  account = file->account(accountId);
  MyMoneyTransactionFilter filter(accountId);
  filter.setDateFilter(startDate, endDate);
  QValueList<MyMoneyTransaction> list = file->transactionList(filter);
  QString openingBalanceTransactionId;

  s << "!Type:" << m_qifProfile.profileType() << endl;
  if(!startDate.isValid() || startDate <= account.openingDate()) {
    s << "D" << m_qifProfile.date(account.openingDate()) << endl;
    openingBalanceTransactionId = file->openingBalanceTransaction(account);
    MyMoneySplit split;
    if(!openingBalanceTransactionId.isEmpty()) {
      MyMoneyTransaction openingBalanceTransaction = file->transaction(openingBalanceTransactionId);
      split = openingBalanceTransaction.splitByAccount(account.id(), true /* match */);
    }
    s << "T" << m_qifProfile.value('T', split.value()) << endl;
  } else {
    s << "D" << m_qifProfile.date(startDate) << endl;
    s << "T" << m_qifProfile.value('T', file->balance(accountId, startDate.addDays(-1))) << endl;
  }
  s << "CX" << endl;
  s << "P" << m_qifProfile.openingBalanceText() << endl;
  s << "L";
  if(m_qifProfile.accountDelimiter().length())
    s << m_qifProfile.accountDelimiter()[0];
  s << account.name();
  if(m_qifProfile.accountDelimiter().length() > 1)
    s << m_qifProfile.accountDelimiter()[1];
  s << endl;
  s << "^" << endl;

  QValueList<MyMoneyTransaction>::ConstIterator it;
  signalProgress(0, list.count());
  int count = 0;
  for(it = list.begin(); it != list.end(); ++it) {
    // don't include the openingBalanceTransaction again
    if((*it).id() != openingBalanceTransactionId)
      writeTransactionEntry(s, *it, accountId);
    signalProgress(++count, 0);
  }
}
Exemple #7
0
void CsvUtil::dissectTransaction(const MyMoneyTransaction& transaction, const MyMoneySplit& split, MyMoneySplit& assetAccountSplit, QList<MyMoneySplit>& feeSplits, QList<MyMoneySplit>& interestSplits, MyMoneySecurity& security, MyMoneySecurity& currency, MyMoneySplit::investTransactionTypeE& transactionType)
{
  // collect the splits. split references the stock account and should already
  // be set up. assetAccountSplit references the corresponding asset account (maybe
  // empty), feeSplits is the list of all expenses and interestSplits
  // the list of all incomes
  MyMoneyFile* file = MyMoneyFile::instance();
  QList<MyMoneySplit>::ConstIterator it_s;
  for (it_s = transaction.splits().constBegin(); it_s != transaction.splits().constEnd(); ++it_s) {
    MyMoneyAccount acc = file->account((*it_s).accountId());
    if ((*it_s).id() == split.id()) {
      security = file->security(acc.currencyId());
    } else if (acc.accountGroup() == MyMoneyAccount::Expense) {
      feeSplits.append(*it_s);
    } else if (acc.accountGroup() == MyMoneyAccount::Income) {
      interestSplits.append(*it_s);
    } else {
      assetAccountSplit = *it_s;
    }
  }

  // determine transaction type
  if (split.action() == MyMoneySplit::ActionAddShares) {
    transactionType = (!split.shares().isNegative()) ? MyMoneySplit::AddShares : MyMoneySplit::RemoveShares;
  } else if (split.action() == MyMoneySplit::ActionBuyShares) {
    transactionType = (!split.value().isNegative()) ? MyMoneySplit::BuyShares : MyMoneySplit::SellShares;
  } else if (split.action() == MyMoneySplit::ActionDividend) {
    transactionType = MyMoneySplit::Dividend;
  } else if (split.action() == MyMoneySplit::ActionReinvestDividend) {
    transactionType = MyMoneySplit::ReinvestDividend;
  } else if (split.action() == MyMoneySplit::ActionYield) {
    transactionType = MyMoneySplit::Yield;
  } else if (split.action() == MyMoneySplit::ActionSplitShares) {
    transactionType = MyMoneySplit::SplitShares;
  } else if (split.action() == MyMoneySplit::ActionInterestIncome) {
    transactionType = MyMoneySplit::InterestIncome;
  } else
    transactionType = MyMoneySplit::BuyShares;

  currency.setTradingSymbol("???");
  try {
    currency = file->security(transaction.commodity());
  } catch (const MyMoneyException &) {
  }
}
void TransactionMatcher::checkTransaction(const MyMoneyTransaction& tm, const MyMoneyTransaction& ti, const MyMoneySplit& si, QPair<MyMoneyTransaction, MyMoneySplit>& lastMatch, TransactionMatcher::autoMatchResultE& result, int variation) const
{
  Q_UNUSED(ti);


  const QValueList<MyMoneySplit>& splits = tm.splits();
  QValueList<MyMoneySplit>::const_iterator it_s;
  for(it_s = splits.begin(); it_s != splits.end(); ++it_s) {
    MyMoneyMoney upper((*it_s).shares());
    MyMoneyMoney lower(upper);
    if((variation > 0) && (variation < 100)) {
      lower = lower - (lower.abs() * MyMoneyMoney(variation, 100));
      upper = upper + (upper.abs() * MyMoneyMoney(variation, 100));
    }
    // we only check for duplicates / matches if the sign
    // of the amount for this split is identical
    if((si.shares() >= lower) && (si.shares() <= upper)) {
      // check for duplicate (we can only do that, if we have a bankID)
      if(!si.bankID().isEmpty()) {
        if((*it_s).bankID() == si.bankID()) {
          lastMatch = QPair<MyMoneyTransaction, MyMoneySplit>(tm, *it_s);
          result = matchedDuplicate;
          break;
        }
        // in case the stored split already has a bankid
        // assigned, it must be a different one and therefore
        // will certainly not match
        if(!(*it_s).bankID().isEmpty())
          continue;
      }
      // check if this is the one that matches
      if((*it_s).accountId() == si.accountId()
      && (si.shares() >= lower) && (si.shares() <= upper)
      && !(*it_s).isMatched()) {
        if(tm.postDate() == ti.postDate()) {
          lastMatch = QPair<MyMoneyTransaction, MyMoneySplit>(tm, *it_s);
          result = matchedExact;
        } else if(result != matchedExact) {
          lastMatch = QPair<MyMoneyTransaction, MyMoneySplit>(tm, *it_s);
          result = matched;
        }
      }
    }
  }
}
Exemple #9
0
bool KEndingBalanceDlg::createTransaction(MyMoneyTransaction &t, const int sign, const MyMoneyMoney& amount, const QString& category, const QDate& date)
{
  t = MyMoneyTransaction();

  if (category.isEmpty() || !date.isValid())
    return true;

  MyMoneySplit s1, s2;
  MyMoneyMoney val = amount * MyMoneyMoney(sign, 1);
  try {
    t.setPostDate(date);
    t.setCommodity(d->m_account.currencyId());

    s1.setPayeeId(field("payeeEdit").toString());
    s1.setReconcileFlag(MyMoneySplit::Cleared);
    s1.setAccountId(d->m_account.id());
    s1.setValue(-val);
    s1.setShares(-val);

    s2 = s1;
    s2.setAccountId(category);
    s2.setValue(val);

    t.addSplit(s1);
    t.addSplit(s2);

    QMap<QString, MyMoneyMoney> priceInfo; // just empty
    MyMoneyMoney shares;
    if (!KCurrencyCalculator::setupSplitPrice(shares, t, s2, priceInfo, this)) {
      t = MyMoneyTransaction();
      return false;
    }

    s2.setShares(shares);
    t.modifySplit(s2);

  } catch (const MyMoneyException &e) {
    qDebug("%s", qPrintable(e.what()));
    t = MyMoneyTransaction();
    return false;
  }

  return true;
}
Exemple #10
0
KMyMoneyUtils::transactionTypeE KMyMoneyUtils::transactionType(const MyMoneyTransaction& t)
{
  if (!stockSplit(t).id().isEmpty())
    return InvestmentTransaction;

  if (t.splitCount() < 2) {
    return Unknown;
  } else if (t.splitCount() > 2) {
    // FIXME check for loan transaction here
    return SplitTransaction;
  }
  QString ida, idb;
  if (t.splits().size() > 0)
    ida = t.splits()[0].accountId();
  if (t.splits().size() > 1)
    idb = t.splits()[1].accountId();
  if (ida.isEmpty() || idb.isEmpty())
    return Unknown;

  MyMoneyAccount a, b;
  a = MyMoneyFile::instance()->account(ida);
  b = MyMoneyFile::instance()->account(idb);
  if ((a.accountGroup() == MyMoneyAccount::Asset
       || a.accountGroup() == MyMoneyAccount::Liability)
      && (b.accountGroup() == MyMoneyAccount::Asset
          || b.accountGroup() == MyMoneyAccount::Liability))
    return Transfer;
  return Normal;
}
Exemple #11
0
const MyMoneySplit KMyMoneyUtils::stockSplit(const MyMoneyTransaction& t)
{
  QList<MyMoneySplit>::ConstIterator it_s;
  MyMoneySplit investmentAccountSplit;
  for (it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s) {
    if (!(*it_s).accountId().isEmpty()) {
      MyMoneyAccount acc = MyMoneyFile::instance()->account((*it_s).accountId());
      if (acc.isInvest()) {
        return *it_s;
      }
      // if we have a reference to an investment account, we remember it here
      if (acc.accountType() == MyMoneyAccount::Investment)
        investmentAccountSplit = *it_s;
    }
  }
  // if we haven't found a stock split, we see if we've seen
  // an investment account on the way. If so, we return it.
  if (!investmentAccountSplit.id().isEmpty())
    return investmentAccountSplit;

  // if none was found, we return an empty split.
  return MyMoneySplit();
}
Exemple #12
0
bool MyMoneySplit::replaceId(const QString& newId, const QString& oldId)
{
  bool changed = false;

  if (m_payee == oldId) {
    m_payee = newId;
    changed = true;
  } else if (m_account == oldId) {
    m_account = newId;
    changed = true;
  }

  if (isMatched()) {
    MyMoneyTransaction t = matchedTransaction();
    if (t.replaceId(newId, oldId)) {
      removeMatch();
      addMatch(t);
      changed = true;
    }
  }

  return changed;
}
Exemple #13
0
KSplitTransactionDlg::KSplitTransactionDlg(const MyMoneyTransaction& t,
        const MyMoneySplit& s,
        const MyMoneyAccount& acc,
        const bool amountValid,
        const bool deposit,
        const MyMoneyMoney& calculatedValue,
        const QMap<QString, MyMoneyMoney>& priceInfo,
        QWidget* parent) :
    KSplitTransactionDlgDecl(parent),
    m_account(acc),
    m_split(s),
    m_precision(2),
    m_amountValid(amountValid),
    m_isDeposit(deposit),
    m_calculatedValue(calculatedValue)
{
    setModal(true);

    QHBoxLayout *mainLayout = new QHBoxLayout;
    setLayout(mainLayout);
    mainLayout->addWidget(horizontalLayoutWidget);

    m_buttonBox = new QDialogButtonBox(QDialogButtonBox::Ok|QDialogButtonBox::Cancel);
    QPushButton *okButton = m_buttonBox->button(QDialogButtonBox::Ok);
    okButton->setDefault(true);
    okButton->setShortcut(Qt::CTRL | Qt::Key_Return);
    QPushButton *user1Button = new QPushButton;
    m_buttonBox->addButton(user1Button, QDialogButtonBox::ActionRole);
    QPushButton *user2Button = new QPushButton;
    m_buttonBox->addButton(user2Button, QDialogButtonBox::ActionRole);
    QPushButton *user3Button = new QPushButton;
    m_buttonBox->addButton(user3Button, QDialogButtonBox::ActionRole);
    connect(m_buttonBox, SIGNAL(accepted()), this, SLOT(accept()));
    connect(m_buttonBox, SIGNAL(rejected()), this, SLOT(reject()));
    m_buttonBox->setOrientation(Qt::Vertical);
    mainLayout->addWidget(m_buttonBox);

    //set custom buttons
    //clearAll button
    user1Button->setText(i18n("Clear &All"));
    user1Button->setToolTip(i18n("Clear all splits"));
    user1Button->setWhatsThis(i18n("Use this to clear all splits of this transaction"));
    user1Button->setIcon(QIcon::fromTheme("edit-clear"));

    //clearZero button
    user2Button->setText(i18n("Clear &Zero"));
    user2Button->setToolTip(i18n("Removes all splits that have a value of zero"));
    user2Button->setIcon(QIcon::fromTheme("edit-clear"));

    //merge button
    user3Button->setText(i18n("&Merge"));
    user3Button->setToolTip(i18n("Merges splits with the same category to one split"));
    user3Button->setWhatsThis(i18n("In case you have multiple split entries to the same category and you like to keep them as a single split"));

    // make finish the default
    m_buttonBox->button(QDialogButtonBox::Cancel)->setDefault(true);

    // setup the focus
    m_buttonBox->button(QDialogButtonBox::Cancel)->setFocusPolicy(Qt::NoFocus);
    okButton->setFocusPolicy(Qt::NoFocus);
    user1Button->setFocusPolicy(Qt::NoFocus);

    // connect signals with slots
    connect(transactionsTable, SIGNAL(transactionChanged(MyMoneyTransaction)),
            this, SLOT(slotSetTransaction(MyMoneyTransaction)));
    connect(transactionsTable, SIGNAL(createCategory(QString,QString&)), this, SLOT(slotCreateCategory(QString,QString&)));
    connect(transactionsTable, SIGNAL(objectCreation(bool)), this, SIGNAL(objectCreation(bool)));

    connect(transactionsTable, SIGNAL(returnPressed()), this, SLOT(accept()));
    connect(transactionsTable, SIGNAL(escapePressed()), this, SLOT(reject()));
    connect(transactionsTable, SIGNAL(editStarted()), this, SLOT(slotEditStarted()));
    connect(transactionsTable, SIGNAL(editFinished()), this, SLOT(slotUpdateButtons()));

    connect(m_buttonBox->button(QDialogButtonBox::Cancel), SIGNAL(clicked()), this, SLOT(reject()));
    connect(m_buttonBox->button(QDialogButtonBox::Ok), SIGNAL(clicked()), this, SLOT(accept()));
    connect(user1Button, SIGNAL(clicked()), this, SLOT(slotClearAllSplits()));
    connect(user3Button, SIGNAL(clicked()), this, SLOT(slotMergeSplits()));
    connect(user2Button, SIGNAL(clicked()), this, SLOT(slotClearUnusedSplits()));

    // setup the precision
    try {
        MyMoneySecurity currency = MyMoneyFile::instance()->currency(t.commodity());
        m_precision = MyMoneyMoney::denomToPrec(m_account.fraction(currency));
    } catch (const MyMoneyException &) {
    }

    slotSetTransaction(t);

    // pass on those vars
    transactionsTable->setup(priceInfo, m_precision);

    QSize size(width(), height());
    KConfigGroup grp = KSharedConfig::openConfig()->group("SplitTransactionEditor");
    size = grp.readEntry("Geometry", size);
    size.setHeight(size.height() - 1);
    QDialog::resize(size.expandedTo(minimumSizeHint()));

    // Trick: it seems, that the initial sizing of the dialog does
    // not work correctly. At least, the columns do not get displayed
    // correct. Reason: the return value of transactionsTable->visibleWidth()
    // is incorrect. If the widget is visible, resizing works correctly.
    // So, we let the dialog show up and resize it then. It's not really
    // clean, but the only way I got the damned thing working.
    QTimer::singleShot(10, this, SLOT(initSize()));
}
void MyMoneyForecast::calculateAutoLoan(const MyMoneySchedule& schedule, MyMoneyTransaction& transaction, const QMap<QString, MyMoneyMoney>& balances)
{
  if (schedule.type() == MyMoneySchedule::TYPE_LOANPAYMENT) {

    //get amortization and interest autoCalc splits
    MyMoneySplit amortizationSplit = transaction.amortizationSplit();
    MyMoneySplit interestSplit = transaction.interestSplit();

    if(!amortizationSplit.id().isEmpty() && !interestSplit.id().isEmpty()) {
      MyMoneyAccountLoan acc(MyMoneyFile::instance()->account(amortizationSplit.accountId()));
      MyMoneyFinancialCalculator calc;
      QDate dueDate;

      // FIXME: setup dueDate according to when the interest should be calculated
      // current implementation: take the date of the next payment according to
      // the schedule. If the calculation is based on the payment reception, and
      // the payment is overdue then take the current date
      dueDate = schedule.nextDueDate();
      if(acc.interestCalculation() == MyMoneyAccountLoan::paymentReceived) {
        if(dueDate < QDate::currentDate())
          dueDate = QDate::currentDate();
      }

      // we need to calculate the balance at the time the payment is due

      MyMoneyMoney balance;
      if(balances.count() == 0)
        balance = MyMoneyFile::instance()->balance(acc.id(), dueDate.addDays(-1));
      else
        balance = balances[acc.id()];

      /*
         QValueList<MyMoneyTransaction> list;
         QValueList<MyMoneyTransaction>::ConstIterator it;
         MyMoneySplit split;
         MyMoneyTransactionFilter filter(acc.id());

         filter.setDateFilter(QDate(), dueDate.addDays(-1));
         list = MyMoneyFile::instance()->transactionList(filter);

         for(it = list.begin(); it != list.end(); ++it) {
         try {
         split = (*it).splitByAccount(acc.id());
         balance += split.value();

         } catch(MyMoneyException *e) {
      // account is not referenced within this transaction
      delete e;
      }
      }
      */

      // FIXME: for now, we only support interest calculation at the end of the period
      calc.setBep();
      // FIXME: for now, we only support periodic compounding
      calc.setDisc();

      calc.setPF(MyMoneySchedule::eventsPerYear(schedule.occurence()));
      MyMoneySchedule::occurenceE compoundingOccurence = static_cast<MyMoneySchedule::occurenceE>(acc.interestCompounding());
      if(compoundingOccurence == MyMoneySchedule::OCCUR_ANY)
        compoundingOccurence = schedule.occurence();

      calc.setCF(MyMoneySchedule::eventsPerYear(compoundingOccurence));

      calc.setPv(balance.toDouble());
      calc.setIr(static_cast<FCALC_DOUBLE> (acc.interestRate(dueDate).abs().toDouble()));
      calc.setPmt(acc.periodicPayment().toDouble());

      MyMoneyMoney interest(calc.interestDue()), amortization;
      interest = interest.abs();    // make sure it's positive for now
      amortization = acc.periodicPayment() - interest;

      if(acc.accountType() == MyMoneyAccount::AssetLoan) {
        interest = -interest;
        amortization = -amortization;
      }

      amortizationSplit.setShares(amortization);
      interestSplit.setShares(interest);

      // FIXME: for now we only assume loans to be in the currency of the transaction
      amortizationSplit.setValue(amortization);
      interestSplit.setValue(interest);

      transaction.modifySplit(amortizationSplit);
      transaction.modifySplit(interestSplit);
    }
  }
}
Exemple #15
0
void MyMoneyStorageXML::writeTransaction(QDomElement& transaction, const MyMoneyTransaction& tx)
{
  tx.writeXML(*m_doc, transaction);
}
void TransactionMatcher::match(MyMoneyTransaction tm, MyMoneySplit sm, MyMoneyTransaction ti, MyMoneySplit si, bool allowImportedTransactions)
{
  const MyMoneySecurity& sec = MyMoneyFile::instance()->security(m_account.currencyId());

  // Now match the transactions.
  //
  // 'Matching' the transactions entails DELETING the end transaction,
  // and MODIFYING the start transaction as needed.
  //
  // There are a variety of ways that a transaction can conflict.
  // Post date, splits, amount are the ones that seem to matter.
  // TODO: Handle these conflicts intelligently, at least warning
  // the user, or better yet letting the user choose which to use.
  //
  // For now, we will just use the transaction details from the start
  // transaction.  The only thing we'll take from the end transaction
  // are the bank ID's.
  //
  // What we have to do here is iterate over the splits in the end
  // transaction, and find the corresponding split in the start
  // transaction.  If there is a bankID in the end split but not the
  // start split, add it to the start split.  If there is a bankID
  // in BOTH, then this transaction cannot be merged (both transactions
  // were imported!!)  If the corresponding start split cannot  be
  // found and the end split has a bankID, we should probably just fail.
  // Although we could ADD it to the transaction.

  // ipwizard: Don't know if iterating over the transactions is a good idea.
  // In case of a split transaction recorded with KMyMoney and the transaction
  // data being imported consisting only of a single category assignment, this
  // does not make much sense. The same applies for investment transactions
  // stored in KMyMoney against imported transactions. I think a better solution
  // is to just base the match on the splits referencing the same (currently
  // selected) account.

  // verify, that tm is a manual (non-matched) transaction
  // allow matching two manual transactions

  if ((!allowImportedTransactions && tm.isImported()) || sm.isMatched())
    throw MYMONEYEXCEPTION(i18n("First transaction does not match requirement for matching"));

  // verify that the amounts are the same, otherwise we should not be matching!
  if (sm.shares() != si.shares()) {
    throw MYMONEYEXCEPTION(i18n("Splits for %1 have conflicting values (%2,%3)", m_account.name(), MyMoneyUtils::formatMoney(sm.shares(), m_account, sec), MyMoneyUtils::formatMoney(si.shares(), m_account, sec)));
  }

  // check that dates are within user's setting
  const int gap = abs(tm.postDate().toJulianDay() - ti.postDate().toJulianDay());
  if (gap > KMyMoneyGlobalSettings::matchInterval()) {
    int rc = KMessageBox::questionYesNo(0, i18np("The transaction dates are one day apart. Do you want to match them anyway?",
                                        "The transaction dates are %1 days apart. Do you want to match them anyway?", gap));
    if (rc == KMessageBox::No) {
      return;
    }
  }

  // ipwizard: I took over the code to keep the bank id found in the endMatchTransaction
  // This might not work for QIF imports as they don't setup this information. It sure
  // makes sense for OFX and HBCI.
  const QString& bankID = si.bankID();
  if (!bankID.isEmpty()) {
    try {
      if (sm.bankID().isEmpty()) {
        sm.setBankID(bankID);
        tm.modifySplit(sm);
      }
    } catch (const MyMoneyException &e) {
      QString estr = e.what();
      throw MYMONEYEXCEPTION(i18n("Unable to match all splits (%1)", estr));
    }
  }
  //
  //  we now allow matching of two non-imported transactions
  //

  // mark the split as cleared if it does not have a reconciliation information yet
  if (sm.reconcileFlag() == MyMoneySplit::NotReconciled) {
    sm.setReconcileFlag(MyMoneySplit::Cleared);
  }

  // if we don't have a payee assigned to the manually entered transaction
  // we use the one we found in the imported transaction
  if (sm.payeeId().isEmpty() && !si.payeeId().isEmpty()) {
    sm.setValue("kmm-orig-payee", sm.payeeId());
    sm.setPayeeId(si.payeeId());
  }

  // We use the imported postdate and keep the previous one for unmatch
  if (tm.postDate() != ti.postDate()) {
    sm.setValue("kmm-orig-postdate", tm.postDate().toString(Qt::ISODate));
    tm.setPostDate(ti.postDate());
  }

  // combine the two memos into one
  QString memo = sm.memo();
  if (!si.memo().isEmpty() && si.memo() != memo) {
    sm.setValue("kmm-orig-memo", memo);
    if (!memo.isEmpty())
      memo += '\n';
    memo += si.memo();
  }
  sm.setMemo(memo);

  // remember the split we matched
  sm.setValue("kmm-match-split", si.id());

  sm.addMatch(ti);
  tm.modifySplit(sm);

  ti.modifySplit(si);///
  MyMoneyFile::instance()->modifyTransaction(tm);
  // Delete the end transaction if it was stored in the engine
  if (!ti.id().isEmpty())
    MyMoneyFile::instance()->removeTransaction(ti);
}
void TransactionMatcher::match(MyMoneyTransaction tm, MyMoneySplit sm, MyMoneyTransaction ti, MyMoneySplit si, bool allowImportedTransactions)
{
  const MyMoneySecurity& sec = MyMoneyFile::instance()->security(m_account.currencyId());

  // Now match the transactions.
  //
  // 'Matching' the transactions entails DELETING the end transaction,
  // and MODIFYING the start transaction as needed.
  //
  // There are a variety of ways that a transaction can conflict.
  // Post date, splits, amount are the ones that seem to matter.
  // TODO: Handle these conflicts intelligently, at least warning
  // the user, or better yet letting the user choose which to use.
  //
  // For now, we will just use the transaction details from the start
  // transaction.  The only thing we'll take from the end transaction
  // are the bank ID's.
  //
  // What we have to do here is iterate over the splits in the end
  // transaction, and find the corresponding split in the start
  // transaction.  If there is a bankID in the end split but not the
  // start split, add it to the start split.  If there is a bankID
  // in BOTH, then this transaction cannot be merged (both transactions
  // were imported!!)  If the corresponding start split cannot  be
  // found and the end split has a bankID, we should probably just fail.
  // Although we could ADD it to the transaction.

  // ipwizard: Don't know if iterating over the transactions is a good idea.
  // In case of a split transaction recorded with KMyMoney and the transaction
  // data being imported consisting only of a single category assignment, this
  // does not make much sense. The same applies for investment transactions
  // stored in KMyMoney against imported transactions. I think a better solution
  // is to just base the match on the splits referencing the same (currently
  // selected) account.

  // verify, that tm is a manually (non-matched) transaction and ti an imported one
  if(sm.isMatched() || (!allowImportedTransactions && tm.isImported()))
    throw new MYMONEYEXCEPTION(i18n("First transaction does not match requirement for matching"));
  if(!ti.isImported())
    throw new MYMONEYEXCEPTION(i18n("Second transaction does not match requirement for matching"));

  // verify that the amounts are the same, otherwise we should not be matching!
  if(sm.shares() != si.shares()) {
    throw new MYMONEYEXCEPTION(i18n("Splits for %1 have conflicting values (%2,%3)").arg(m_account.name()).arg(sm.shares().formatMoney(m_account, sec), si.shares().formatMoney(m_account, sec)));
  }

  // ipwizard: I took over the code to keep the bank id found in the endMatchTransaction
  // This might not work for QIF imports as they don't setup this information. It sure
  // makes sense for OFX and HBCI.
  const QString& bankID = si.bankID();
  if (!bankID.isEmpty()) {
    try {
      if (sm.bankID().isEmpty() ) {
        sm.setBankID( bankID );
        tm.modifySplit(sm);
      } else if(sm.bankID() != bankID) {
        throw new MYMONEYEXCEPTION(i18n("Both of these transactions have been imported into %1.  Therefore they cannot be matched.  Matching works with one imported transaction and one non-imported transaction.").arg(m_account.name()));
      }
    } catch(MyMoneyException *e) {
      QString estr = e->what();
      delete e;
      throw new MYMONEYEXCEPTION(i18n("Unable to match all splits (%1)").arg(estr));
    }
  }

#if 0 // Ace's original code
  // TODO (Ace) Add in another error to catch the case where a user
  // tries to match two hand-entered transactions.
  QValueList<MyMoneySplit> endSplits = endMatchTransaction.splits();
  QValueList<MyMoneySplit>::const_iterator it_split = endSplits.begin();
  while (it_split != endSplits.end())
  {
    // find the corresponding split in the start transaction
    MyMoneySplit startSplit;
    QString accountid = (*it_split).accountId();
    try
    {
      startSplit = startMatchTransaction.splitByAccount( accountid );
    }
      // only exception is thrown if we cannot find a split like this
    catch(MyMoneyException *e)
    {
      delete e;
      startSplit = (*it_split);
      startSplit.clearId();
      startMatchTransaction.addSplit(startSplit);
    }

    // verify that the amounts are the same, otherwise we should not be
    // matching!
    if ( (*it_split).value() != startSplit.value() )
    {
      QString accountname = MyMoneyFile::instance()->account(accountid).name();
      throw new MYMONEYEXCEPTION(i18n("Splits for %1 have conflicting values (%2,%3)").arg(accountname).arg((*it_split).value().formatMoney(),startSplit.value().formatMoney()));
    }

    QString bankID = (*it_split).bankID();
    if ( ! bankID.isEmpty() )
    {
      try
      {
        if ( startSplit.bankID().isEmpty() )
        {
          startSplit.setBankID( bankID );
          startMatchTransaction.modifySplit(startSplit);
        }
        else
        {
          QString accountname = MyMoneyFile::instance()->account(accountid).name();
          throw new MYMONEYEXCEPTION(i18n("Both of these transactions have been imported into %1.  Therefore they cannot be matched.  Matching works with one imported transaction and one non-imported transaction.").arg(accountname));
        }
      }
      catch(MyMoneyException *e)
      {
        QString estr = e->what();
        delete e;
        throw new MYMONEYEXCEPTION(i18n("Unable to match all splits (%1)").arg(estr));
      }
    }
    ++it_split;
  }
#endif

  // mark the split as cleared if it does not have a reconciliation information yet
  if(sm.reconcileFlag() == MyMoneySplit::NotReconciled) {
    sm.setReconcileFlag(MyMoneySplit::Cleared);
  }

  // if we don't have a payee assigned to the manually entered transaction
  // we use the one we found in the imported transaction
  if(sm.payeeId().isEmpty() && !si.payeeId().isEmpty()) {
    sm.setValue("kmm-orig-payee", sm.payeeId());
    sm.setPayeeId(si.payeeId());
  }

  // We use the imported postdate and keep the previous one for unmatch
  if(tm.postDate() != ti.postDate()) {
    sm.setValue("kmm-orig-postdate", tm.postDate().toString(Qt::ISODate));
    tm.setPostDate(ti.postDate());
  }

  // combine the two memos into one
  QString memo = sm.memo();
  if(!si.memo().isEmpty() && si.memo() != memo) {
    sm.setValue("kmm-orig-memo", memo);
    if(!memo.isEmpty())
      memo += "\n";
    memo += si.memo();
  }
  sm.setMemo(memo);

  // remember the split we matched
  sm.setValue("kmm-match-split", si.id());

  sm.addMatch(ti);
  tm.modifySplit(sm);

  MyMoneyFile::instance()->modifyTransaction(tm);
  // Delete the end transaction if it was stored in the engine
  if(!ti.id().isEmpty())
    MyMoneyFile::instance()->removeTransaction(ti);
}
void MyMoneyForecast::addScheduledTransactions (void)
{
  MyMoneyFile* file = MyMoneyFile::instance();

  // now process all the schedules that may have an impact
  QValueList<MyMoneySchedule> schedule;

  schedule = file->scheduleList("", MyMoneySchedule::TYPE_ANY, MyMoneySchedule::OCCUR_ANY, MyMoneySchedule::STYPE_ANY,
                                QDate(), forecastEndDate());
  if(schedule.count() > 0) {
    QValueList<MyMoneySchedule>::Iterator it;
    do {
      qBubbleSort(schedule);
      it = schedule.begin();
      if(it == schedule.end())
        break;

      if((*it).isFinished()) {
        schedule.erase(it);
        continue;
      }

      QDate date = (*it).nextPayment((*it).lastPayment());
      if(!date.isValid()) {
        schedule.remove(it);
        continue;
      }

      QDate nextDate =
        (*it).adjustedNextPayment((*it).adjustedDate((*it).lastPayment(),
                                                     (*it).weekendOption()));
      if (nextDate > forecastEndDate()) {
        // We're done with this schedule, let's move on to the next
        schedule.remove(it);
        continue;
      }

      // found the next schedule. process it

      MyMoneyAccount acc = (*it).account();

      if(!acc.id().isEmpty()) {
        try {
          if(acc.accountType() != MyMoneyAccount::Investment) {
            MyMoneyTransaction t = (*it).transaction();

            // only process the entry, if it is still active
            if(!(*it).isFinished() && nextDate != QDate()) {
              // make sure we have all 'starting balances' so that the autocalc works
              QValueList<MyMoneySplit>::const_iterator it_s;
              QMap<QString, MyMoneyMoney> balanceMap;

              for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s ) {
                MyMoneyAccount acc = file->account((*it_s).accountId());
                if(isForecastAccount(acc)) {
                  // collect all overdues on the first day
                  QDate forecastDate = nextDate;
                  if(QDate::currentDate() >= nextDate)
                    forecastDate = QDate::currentDate().addDays(1);

                  dailyBalances balance;
                  balance = m_accountList[acc.id()];
                  for(QDate f_day = QDate::currentDate(); f_day < forecastDate; ) {
                    balanceMap[acc.id()] += m_accountList[acc.id()][f_day];
                    f_day = f_day.addDays(1);
                  }
                }
              }

              // take care of the autoCalc stuff
              calculateAutoLoan(*it, t, balanceMap);

              // now add the splits to the balances
              for(it_s = t.splits().begin(); it_s != t.splits().end(); ++it_s ) {
                MyMoneyAccount acc = file->account((*it_s).accountId());
                if(isForecastAccount(acc)) {
                  dailyBalances balance;
                  balance = m_accountList[acc.id()];
                  //int offset = QDate::currentDate().daysTo(nextDate);
                  //if(offset <= 0) {  // collect all overdues on the first day
                  //  offset = 1;
                  //}
                  // collect all overdues on the first day
                  QDate forecastDate = nextDate;
                  if(QDate::currentDate() >= nextDate)
                    forecastDate = QDate::currentDate().addDays(1);

                  if(acc.accountType() == MyMoneyAccount::Income) {
                    balance[forecastDate] += ((*it_s).shares() * MyMoneyMoney(-1, 1));
                  } else {
                    balance[forecastDate] += (*it_s).shares();
                  }
                  m_accountList[acc.id()] = balance;
                }
              }
            }
          }
          (*it).setLastPayment(date);

        } catch(MyMoneyException* e) {
          kdDebug(2) << __func__ << " Schedule " << (*it).id() << " (" << (*it).name() << "): " << e->what() << endl;

          schedule.remove(it);
          delete e;
        }
      } else {
        // remove schedule from list
        schedule.remove(it);
      }
    }
    while(1);
  }

#if 0
{
  s << "\n\nAdded scheduled transactions\n";
  QMap<QString, dailyBalances>::Iterator it_a;
  QMap<QString, QString>::ConstIterator it_n;
  for(it_n = m_nameIdx.begin(); it_n != m_nameIdx.end(); ++it_n) {
  MyMoneyAccount acc = file->account(*it_n);
  it_a = m_accountList.find(*it_n);
  s << "\"" << acc.name() << "\",";
  for(int i = 0; i < 90; ++i) {
    s << "\"" << (*it_a)[i].formatMoney("") << "\",";
  }
  s << "\n";
}
}
#endif
}
Exemple #19
0
MyMoneyTransaction KNewLoanWizard::transaction() const
{
  MyMoneyTransaction t;
  bool hasInterest = !field("interestRateEdit").value<MyMoneyMoney>().isZero();

  MyMoneySplit sPayment, sInterest, sAmortization;
  // setup accounts. at this point, we cannot fill in the id of the
  // account that the amortization will be performed on, because we
  // create the account. So the id is yet unknown.
  sPayment.setAccountId(field("paymentAccountEdit").toStringList().first());


  //Only create the interest split if not zero
  if (hasInterest) {
    sInterest.setAccountId(field("interestAccountEdit").toStringList().first());
    sInterest.setValue(MyMoneyMoney::autoCalc);
    sInterest.setShares(sInterest.value());
    sInterest.setAction(MyMoneySplit::ActionInterest);
  }

  // values
  if (field("borrowButton").toBool()) {
    sPayment.setValue(-field("paymentEdit").value<MyMoneyMoney>());
  } else {
    sPayment.setValue(field("paymentEdit").value<MyMoneyMoney>());
  }

  sAmortization.setValue(MyMoneyMoney::autoCalc);
  // don't forget the shares
  sPayment.setShares(sPayment.value());

  sAmortization.setShares(sAmortization.value());

  // setup the commodity
  MyMoneyAccount acc = MyMoneyFile::instance()->account(sPayment.accountId());
  t.setCommodity(acc.currencyId());

  // actions
  sPayment.setAction(MyMoneySplit::ActionAmortization);
  sAmortization.setAction(MyMoneySplit::ActionAmortization);

  // payee
  QString payeeId = field("payeeEdit").toString();
  sPayment.setPayeeId(payeeId);
  sAmortization.setPayeeId(payeeId);

  MyMoneyAccount account("Phony-ID", MyMoneyAccount());
  sAmortization.setAccountId(account.id());

  // IMPORTANT: Payment split must be the first one, because
  //            the schedule view expects it this way during display
  t.addSplit(sPayment);
  t.addSplit(sAmortization);

  if (hasInterest) {
    t.addSplit(sInterest);
  }

  // copy the splits from the other costs and update the payment split
  foreach (const MyMoneySplit& it, m_transaction.splits()) {
    if (it.accountId() != account.id()) {
      MyMoneySplit sp = it;
      sp.clearId();
      t.addSplit(sp);
      sPayment.setValue(sPayment.value() - sp.value());
      sPayment.setShares(sPayment.value());
      t.modifySplit(sPayment);
    }
  }
  return t;
}