You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
638 lines
25 KiB
638 lines
25 KiB
/***************************************************************************
|
|
listtable.cpp
|
|
-------------------
|
|
begin : Sat 28 jun 2008
|
|
copyright : (C) 2004-2005 by Ace Jones
|
|
2008 by Alvaro Soliverez
|
|
email : acejones@users.sourceforge.net
|
|
asoliverez@gmail.com
|
|
***************************************************************************/
|
|
|
|
/***************************************************************************
|
|
* *
|
|
* This program is free software; you can redistribute it and/or modify *
|
|
* it under the terms of the GNU General Public License as published by *
|
|
* the Free Software Foundation; either version 2 of the License, or *
|
|
* (at your option) any later version. *
|
|
* *
|
|
***************************************************************************/
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
#include <config.h>
|
|
#endif
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// TQt Includes
|
|
#include <tqvaluelist.h>
|
|
#include <tqfile.h>
|
|
#include <tqtextstream.h>
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// TDE Includes
|
|
// This is just needed for i18n(). Once I figure out how to handle i18n
|
|
// without using this macro directly, I'll be freed of KDE dependency.
|
|
|
|
#include <tdelocale.h>
|
|
#include <kdebug.h>
|
|
|
|
// ----------------------------------------------------------------------------
|
|
// Project Includes
|
|
#include "../mymoney/mymoneyfile.h"
|
|
#include "../mymoney/mymoneyreport.h"
|
|
#include "../mymoney/mymoneyexception.h"
|
|
#include "../kmymoneyutils.h"
|
|
#include "../kmymoneyglobalsettings.h"
|
|
#include "reportdebug.h"
|
|
#include "listtable.h"
|
|
|
|
namespace reports {
|
|
|
|
TQStringList ListTable::TableRow::m_sortCriteria;
|
|
|
|
// ****************************************************************************
|
|
//
|
|
// Group Iterator
|
|
//
|
|
// ****************************************************************************
|
|
|
|
class GroupIterator
|
|
{
|
|
public:
|
|
GroupIterator ( const TQString& _group, const TQString& _subtotal, unsigned _depth ) : m_depth ( _depth ), m_groupField ( _group ), m_subtotalField ( _subtotal ) {}
|
|
GroupIterator ( void ) {}
|
|
void update ( const ListTable::TableRow& _row )
|
|
{
|
|
m_previousGroup = m_currentGroup;
|
|
m_currentGroup = _row[m_groupField];
|
|
if ( isSubtotal() )
|
|
{
|
|
m_previousSubtotal = m_currentSubtotal;
|
|
m_currentSubtotal = MyMoneyMoney();
|
|
}
|
|
m_currentSubtotal += _row[m_subtotalField];
|
|
}
|
|
|
|
bool isNewHeader ( void ) const { return ( m_currentGroup != m_previousGroup ); }
|
|
bool isSubtotal ( void ) const { return ( m_currentGroup != m_previousGroup ) && ( !m_previousGroup.isEmpty() ); }
|
|
const MyMoneyMoney& subtotal ( void ) const { return m_previousSubtotal; }
|
|
const MyMoneyMoney& currenttotal ( void ) const { return m_currentSubtotal; }
|
|
unsigned depth ( void ) const { return m_depth; }
|
|
const TQString& name ( void ) const { return m_currentGroup; }
|
|
const TQString& oldName ( void ) const { return m_previousGroup; }
|
|
const TQString& groupField ( void ) const { return m_groupField; }
|
|
const TQString& subtotalField ( void ) const { return m_subtotalField; }
|
|
// ***DV*** HACK make the currentGroup test different but look the same
|
|
void force ( void ) { m_currentGroup += " "; }
|
|
private:
|
|
MyMoneyMoney m_currentSubtotal;
|
|
MyMoneyMoney m_previousSubtotal;
|
|
unsigned m_depth;
|
|
TQString m_currentGroup;
|
|
TQString m_previousGroup;
|
|
TQString m_groupField;
|
|
TQString m_subtotalField;
|
|
};
|
|
|
|
// ****************************************************************************
|
|
//
|
|
// ListTable implementation
|
|
//
|
|
// ****************************************************************************
|
|
|
|
bool ListTable::TableRow::operator< ( const TableRow& _compare ) const
|
|
{
|
|
bool result = false;
|
|
|
|
TQStringList::const_iterator it_criterion = m_sortCriteria.begin();
|
|
while ( it_criterion != m_sortCriteria.end() )
|
|
{
|
|
if ( this->operator[] ( *it_criterion ) < _compare[ *it_criterion ] )
|
|
{
|
|
result = true;
|
|
break;
|
|
}
|
|
else if ( this->operator[] ( *it_criterion ) > _compare[ *it_criterion ] )
|
|
break;
|
|
|
|
++it_criterion;
|
|
}
|
|
return result;
|
|
}
|
|
|
|
// needed for KDE < 3.2 implementation of qHeapSort
|
|
bool ListTable::TableRow::operator<= ( const TableRow& _compare ) const
|
|
{
|
|
return ( ! ( _compare < *this ) );
|
|
}
|
|
|
|
bool ListTable::TableRow::operator== ( const TableRow& _compare ) const
|
|
{
|
|
return ( ! ( *this < _compare ) && ! ( _compare < *this ) );
|
|
}
|
|
|
|
bool ListTable::TableRow::operator> ( const TableRow& _compare ) const
|
|
{
|
|
return ( _compare < *this );
|
|
}
|
|
|
|
/**
|
|
* TODO
|
|
*
|
|
* - Collapse 2- & 3- groups when they are identical
|
|
* - Way more test cases (especially splits & transfers)
|
|
* - Option to collapse splits
|
|
* - Option to exclude transfers
|
|
*
|
|
*/
|
|
|
|
ListTable::ListTable ( const MyMoneyReport& _report ) : m_config ( _report )
|
|
{
|
|
}
|
|
|
|
void ListTable::render ( TQString& result, TQString& csv ) const
|
|
{
|
|
MyMoneyMoney grandtotal;
|
|
MyMoneyFile* file = MyMoneyFile::instance();
|
|
|
|
result = "";
|
|
csv = "";
|
|
result += TQString ( "<h2 class=\"report\">%1</h2>\n" ).arg ( m_config.name() );
|
|
csv += "\"Report: " + m_config.name() + "\"\n";
|
|
//actual dates of the report
|
|
result += TQString("<div class=\"subtitle\">");
|
|
if(!m_config.fromDate().isNull()) {
|
|
result += i18n("Report date range", "%1 through %2").arg(TDEGlobal::locale()->formatDate(m_config.fromDate(), true)).arg(TDEGlobal::locale()->formatDate(m_config.toDate(), true));
|
|
result += TQString("</div>\n");
|
|
result += TQString("<div class=\"gap\"> </div>\n");
|
|
|
|
csv += i18n("Report date range", "%1 through %2").arg(TDEGlobal::locale()->formatDate(m_config.fromDate(), true)).arg(TDEGlobal::locale()->formatDate(m_config.toDate(), true));
|
|
csv += TQString("\n");
|
|
}
|
|
|
|
|
|
result += TQString ( "<div class=\"subtitle\">" );
|
|
if ( m_config.isConvertCurrency() )
|
|
{
|
|
result += i18n ( "All currencies converted to %1" ).arg ( file->baseCurrency().name() );
|
|
csv += i18n ( "All currencies converted to %1\n" ).arg ( file->baseCurrency().name() );
|
|
}
|
|
else
|
|
{
|
|
result += i18n ( "All values shown in %1 unless otherwise noted" ).arg ( file->baseCurrency().name() );
|
|
csv += i18n ( "All values shown in %1 unless otherwise noted\n" ).arg ( file->baseCurrency().name() );
|
|
}
|
|
result += TQString ( "</div>\n" );
|
|
result += TQString ( "<div class=\"gap\"> </div>\n" );
|
|
|
|
// retrieve the configuration parameters from the report definition.
|
|
// the things that we care about for query reports are:
|
|
// how to group the rows, what columns to display, and what field
|
|
// to subtotal on
|
|
TQStringList groups = TQStringList::split ( ",", m_group );
|
|
TQStringList columns = TQStringList::split ( ",", m_columns );
|
|
columns += m_subtotal;
|
|
TQStringList postcolumns = TQStringList::split ( ",", m_postcolumns );
|
|
columns += postcolumns;
|
|
|
|
//
|
|
// Table header
|
|
//
|
|
TQMap<TQString, TQString> i18nHeaders;
|
|
i18nHeaders["postdate"] = i18n ( "Date" );
|
|
i18nHeaders["value"] = i18n ( "Amount" );
|
|
i18nHeaders["number"] = i18n ( "Num" );
|
|
i18nHeaders["payee"] = i18n ( "Payee" );
|
|
i18nHeaders["category"] = i18n ( "Category" );
|
|
i18nHeaders["account"] = i18n ( "Account" );
|
|
i18nHeaders["memo"] = i18n ( "Memo" );
|
|
i18nHeaders["topcategory"] = i18n ( "Top Category" );
|
|
i18nHeaders["categorytype"] = i18n ( "Category Type" );
|
|
i18nHeaders["month"] = i18n ( "Month" );
|
|
i18nHeaders["week"] = i18n ( "Week" );
|
|
i18nHeaders["reconcileflag"] = i18n ( "Reconciled" );
|
|
i18nHeaders["action"] = i18n ( "Action" );
|
|
i18nHeaders["shares"] = i18n ( "Shares" );
|
|
i18nHeaders["price"] = i18n ( "Price" );
|
|
i18nHeaders["latestprice"] = i18n ( "Price" );
|
|
i18nHeaders["netinvvalue"] = i18n ( "Net Value" );
|
|
i18nHeaders["buys"] = i18n ( "Buys" );
|
|
i18nHeaders["sells"] = i18n ( "Sells" );
|
|
i18nHeaders["reinvestincome"] = i18n ( "Dividends Reinvested" );
|
|
i18nHeaders["cashincome"] = i18n ( "Dividends Paid Out" );
|
|
i18nHeaders["startingbal"] = i18n ( "Starting Balance" );
|
|
i18nHeaders["endingbal"] = i18n ( "Ending Balance" );
|
|
i18nHeaders["return"] = i18n ( "Annualized Return" );
|
|
i18nHeaders["returninvestment"] = i18n ( "Return On Investment" );
|
|
i18nHeaders["fees"] = i18n ( "Fees" );
|
|
i18nHeaders["interest"] = i18n ( "Interest" );
|
|
i18nHeaders["payment"] = i18n ( "Payment" );
|
|
i18nHeaders["balance"] = i18n ( "Balance" );
|
|
i18nHeaders["type"] = i18n ( "Type" );
|
|
i18nHeaders["name"] = i18n ( "Name" );
|
|
i18nHeaders["nextduedate"] = i18n ( "Next Due Date" );
|
|
i18nHeaders["occurence"] = i18n ( "Occurence" );
|
|
i18nHeaders["paymenttype"] = i18n ( "Payment Method" );
|
|
i18nHeaders["institution"] = i18n ( "Institution" );
|
|
i18nHeaders["description"] = i18n ( "Description" );
|
|
i18nHeaders["openingdate"] = i18n ( "Opening Date" );
|
|
i18nHeaders["currencyname"] = i18n ( "Currency" );
|
|
i18nHeaders["balancewarning"] = i18n ( "Balance Early Warning" );
|
|
i18nHeaders["maxbalancelimit"] = i18n ( "Balance Max Limit" );
|
|
i18nHeaders["creditwarning"] = i18n ( "Credit Early Warning" );
|
|
i18nHeaders["maxcreditlimit"] = i18n ( "Credit Max Limit" );
|
|
i18nHeaders["tax"] = i18n ( "Tax" );
|
|
i18nHeaders["favorite"] = i18n ( "Preferred" );
|
|
i18nHeaders["loanamount"] = i18n ( "Loan Amount" );
|
|
i18nHeaders["interestrate"] = i18n ( "Interest Rate" );
|
|
i18nHeaders["nextinterestchange"] = i18n ( "Next Interest Change" );
|
|
i18nHeaders["periodicpayment"] = i18n ( "Periodic Payment" );
|
|
i18nHeaders["finalpayment"] = i18n ( "Final Payment" );
|
|
i18nHeaders["currentbalance"] = i18n ( "Current Balance" );
|
|
|
|
// the list of columns which represent money, so we can display them correctly
|
|
TQStringList moneyColumns = TQStringList::split ( ",", "value,shares,price,latestprice,netinvvalue,buys,sells,cashincome,reinvestincome,startingbal,fees,interest,payment,balance,balancewarning,maxbalancelimit,creditwarning,maxcreditlimit,loanamount,periodicpayment,finalpayment,currentbalance" );
|
|
|
|
// the list of columns which represent shares, which is like money except the
|
|
// transaction currency will not be displayed
|
|
TQStringList sharesColumns = TQStringList::split ( ",", "shares" );
|
|
|
|
// the list of columns which represent a percentage, so we can display them correctly
|
|
TQStringList percentColumns = TQStringList::split ( ",", "return,returninvestment,interestrate" );
|
|
|
|
// the list of columns which represent dates, so we can display them correctly
|
|
TQStringList dateColumns = TQStringList::split ( ",", "postdate,entrydate,nextduedate,openingdate,nextinterestchange" );
|
|
|
|
result += "<table class=\"report\">\n<thead><tr class=\"itemheader\">";
|
|
|
|
TQStringList::const_iterator it_column = columns.begin();
|
|
while ( it_column != columns.end() )
|
|
{
|
|
TQString i18nName = i18nHeaders[*it_column];
|
|
if ( i18nName.isEmpty() )
|
|
i18nName = *it_column;
|
|
result += "<th>" + i18nName + "</th>";
|
|
csv += i18nName + ",";
|
|
++it_column;
|
|
}
|
|
|
|
result += "</tr></thead>\n";
|
|
csv = csv.left ( csv.length() - 1 );
|
|
csv += "\n";
|
|
|
|
//
|
|
// Set up group iterators
|
|
//
|
|
// There is one active iterator for each level of grouping.
|
|
// As we step through the rows
|
|
// we update the group iterators each time based on the row data. If
|
|
// the group iterator changes and it had a previous value, we print a
|
|
// subtotal. Whether or not it had a previous value, we print a group
|
|
// header. The group iterator keeps track of a subtotal also.
|
|
|
|
int depth = 1;
|
|
TQValueList<GroupIterator> groupIteratorList;
|
|
TQStringList::const_iterator it_grouplevel = groups.begin();
|
|
while ( it_grouplevel != groups.end() )
|
|
{
|
|
groupIteratorList += GroupIterator ( ( *it_grouplevel ), m_subtotal, depth++ );
|
|
++it_grouplevel;
|
|
}
|
|
|
|
//
|
|
// Rows
|
|
//
|
|
|
|
bool row_odd = true;
|
|
|
|
// ***DV***
|
|
MyMoneyMoney startingBalance;
|
|
for ( TQValueList<TableRow>::const_iterator it_row = m_rows.begin();
|
|
it_row != m_rows.end();
|
|
++it_row ) {
|
|
|
|
// the standard fraction is the fraction of an non-cash account in the base currency
|
|
// this could be overridden using the "fraction" element of a row for each row.
|
|
// Currently (2008-02-21) this override is not used at all (ipwizard)
|
|
int fraction = file->baseCurrency().smallestAccountFraction();
|
|
if ( ( *it_row ).find ( "fraction" ) != ( *it_row ).end() )
|
|
fraction = ( *it_row ) ["fraction"].toInt();
|
|
|
|
//
|
|
// Process Groups
|
|
//
|
|
|
|
// ***DV*** HACK to force a subtotal and header, since this render doesn't
|
|
// always detect a group change for different accounts with the same name
|
|
// (as occurs with the same stock purchased from different investment accts)
|
|
if ( it_row != m_rows.begin() )
|
|
if ( ( ( * it_row ) ["rank"] == "-2" ) && ( ( * it_row ) ["id"] == "A" ) )
|
|
( groupIteratorList.last() ).force();
|
|
|
|
// There's a subtle bug here. If an earlier group gets a new group,
|
|
// then we need to force all the downstream groups to get one too.
|
|
|
|
// Update the group iterators with the current row value
|
|
TQValueList<GroupIterator>::iterator it_group = groupIteratorList.begin();
|
|
while ( it_group != groupIteratorList.end() )
|
|
{
|
|
( *it_group ).update ( *it_row );
|
|
++it_group;
|
|
}
|
|
|
|
// Do subtotals backwards
|
|
if ( m_config.isConvertCurrency() )
|
|
{
|
|
it_group = groupIteratorList.fromLast();
|
|
while ( it_group != groupIteratorList.end() )
|
|
{
|
|
if ( ( *it_group ).isSubtotal() )
|
|
{
|
|
if ( ( *it_group ).depth() == 1 )
|
|
grandtotal += ( *it_group ).subtotal();
|
|
grandtotal = grandtotal.convert(fraction);
|
|
|
|
TQString subtotal_html = ( *it_group ).subtotal().formatMoney ( fraction );
|
|
TQString subtotal_csv = ( *it_group ).subtotal().formatMoney ( fraction, false );
|
|
|
|
// ***DV*** HACK fix the side-effiect from .force() method above
|
|
TQString oldName = TQString ( ( *it_group ).oldName() ).stripWhiteSpace();
|
|
|
|
result +=
|
|
"<tr class=\"sectionfooter\">"
|
|
"<td class=\"left" + TQString::number ( ( ( *it_group ).depth() - 1 ) ) + "\" "
|
|
"colspan=\"" +
|
|
TQString::number ( columns.count() - 1 - postcolumns.count() ) + "\">" +
|
|
i18n ( "Total" ) + " " + oldName + "</td>"
|
|
"<td>" + subtotal_html + "</td></tr>\n";
|
|
|
|
csv +=
|
|
"\"" + i18n ( "Total" ) + " " + oldName + "\",\"" + subtotal_csv + "\"\n";
|
|
}
|
|
--it_group;
|
|
}
|
|
}
|
|
|
|
// And headers forwards
|
|
it_group = groupIteratorList.begin();
|
|
while ( it_group != groupIteratorList.end() )
|
|
{
|
|
if ( ( *it_group ).isNewHeader() )
|
|
{
|
|
row_odd = true;
|
|
result += "<tr class=\"sectionheader\">"
|
|
"<td class=\"left" + TQString::number ( ( ( *it_group ).depth() - 1 ) ) + "\" "
|
|
"colspan=\"" + TQString::number ( columns.count() ) + "\">" +
|
|
( *it_group ).name() + "</td></tr>\n";
|
|
csv += "\"" + ( *it_group ).name() + "\"\n";
|
|
}
|
|
++it_group;
|
|
}
|
|
|
|
//
|
|
// Columns
|
|
//
|
|
|
|
// skip the opening and closing balance row,
|
|
// if the balance column is not shown
|
|
if ( ( columns.contains ( "balance" ) == 0 ) && ( ( *it_row ) ["rank"] == "-2" ) )
|
|
continue;
|
|
|
|
bool need_label = true;
|
|
|
|
// ***DV***
|
|
if ( ( * it_row ) ["rank"] == "0" ) row_odd = ! row_odd;
|
|
|
|
if ( ( * it_row ) ["rank"] == "-2" )
|
|
result += TQString ( "<tr class=\"item%1\">" ).arg ( ( * it_row ) ["id"] );
|
|
else
|
|
if ( ( * it_row ) ["rank"] == "1" )
|
|
result += TQString ( "<tr class=\"%1\">" ).arg ( row_odd ? "item1" : "item0" );
|
|
else
|
|
result += TQString ( "<tr class=\"%1\">" ).arg ( row_odd ? "row-odd " : "row-even" );
|
|
|
|
TQStringList::const_iterator it_column = columns.begin();
|
|
while ( it_column != columns.end() )
|
|
{
|
|
TQString data = ( *it_row ) [*it_column];
|
|
|
|
// ***DV***
|
|
if ( ( * it_row ) ["rank"] == "1" ) {
|
|
if ( * it_column == "value" )
|
|
data = ( * it_row ) ["split"];
|
|
else if ( *it_column == "postdate"
|
|
|| *it_column == "number"
|
|
|| *it_column == "payee"
|
|
|| *it_column == "action"
|
|
|| *it_column == "shares"
|
|
|| *it_column == "price"
|
|
|| *it_column == "nextduedate"
|
|
|| *it_column == "balance"
|
|
|| *it_column == "account"
|
|
|| *it_column == "name" )
|
|
data = "";
|
|
}
|
|
|
|
// ***DV***
|
|
if ( ( * it_row ) ["rank"] == "-2" ) {
|
|
if ( *it_column == "balance" ) {
|
|
data = ( * it_row ) ["balance"];
|
|
if ( ( * it_row ) ["id"] == "A" ) // opening balance?
|
|
startingBalance = MyMoneyMoney ( data );
|
|
}
|
|
|
|
if ( need_label ) {
|
|
if ( ( * it_column == "payee" ) ||
|
|
( * it_column == "category" ) ||
|
|
( * it_column == "memo" ) ) {
|
|
if ( ( * it_row ) ["shares"] != "" ) {
|
|
data = ( ( * it_row ) ["id"] == "A" )
|
|
? i18n ( "Initial Market Value" )
|
|
: i18n ( "Ending Market Value" );
|
|
} else {
|
|
data = ( ( * it_row ) ["id"] == "A" )
|
|
? i18n ( "Opening Balance" )
|
|
: i18n ( "Closing Balance" );
|
|
}
|
|
need_label = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
// The 'balance' column is calculated at render-time
|
|
// but not printed on split lines
|
|
else if ( *it_column == "balance" && ( * it_row ) ["rank"] == "0" )
|
|
{
|
|
// Take the balance off the deepest group iterator
|
|
data = ( groupIteratorList.back().currenttotal() + startingBalance ).toString();
|
|
}
|
|
|
|
// Figure out how to render the value in this column, depending on
|
|
// what its properties are.
|
|
//
|
|
// TODO: This and the i18n headings are handled
|
|
// as a set of parallel vectors. Would be much better to make a single
|
|
// vector of a properties class.
|
|
if ( sharesColumns.contains ( *it_column ) )
|
|
{
|
|
if ( data.isEmpty() ) {
|
|
result += TQString ( "<td></td>" );
|
|
csv += "\"\",";
|
|
}
|
|
else {
|
|
result += TQString ( "<td>%1</td>" ).arg ( MyMoneyMoney ( data ).formatMoney ( "", 3 ) );
|
|
csv += "\"" + MyMoneyMoney ( data ).formatMoney ( "", 3, false ) + "\",";
|
|
}
|
|
}
|
|
else if ( moneyColumns.contains ( *it_column ) )
|
|
{
|
|
if ( data.isEmpty() ) {
|
|
result += TQString ( "<td%1></td>" )
|
|
.arg ( ( *it_column == "value" ) ? " class=\"value\"" : "" );
|
|
csv += "\"\",";
|
|
} else if ( MyMoneyMoney( data ) == MyMoneyMoney::autoCalc ) {
|
|
result += TQString ( "<td%1>%2</td>" )
|
|
.arg ( ( *it_column == "value" ) ? " class=\"value\"" : "" )
|
|
.arg (i18n("Calculated"));
|
|
csv += "\""+ i18n("Calculated") +"\",";
|
|
} else if ( *it_column == "price" ) {
|
|
result += TQString ( "<td>%2</td>" )
|
|
.arg ( MyMoneyMoney ( data ).formatMoney ( MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision()) ) );
|
|
csv += "\"" + ( *it_row ) ["currency"] + " " + MyMoneyMoney ( data ).formatMoney ( MyMoneyMoney::precToDenom(KMyMoneyGlobalSettings::pricePrecision()), false ) + "\",";
|
|
} else {
|
|
result += TQString ( "<td%1>%2 %3</td>" )
|
|
.arg ( ( *it_column == "value" ) ? " class=\"value\"" : "" )
|
|
.arg ( ( *it_row ) ["currency"] )
|
|
.arg ( MyMoneyMoney ( data ).formatMoney ( fraction ) );
|
|
csv += "\"" + ( *it_row ) ["currency"] + " " + MyMoneyMoney ( data ).formatMoney ( fraction, false ) + "\",";
|
|
}
|
|
}
|
|
else if ( percentColumns.contains ( *it_column ) )
|
|
{
|
|
data = ( MyMoneyMoney ( data ) * MyMoneyMoney ( 100, 1 ) ).formatMoney ( fraction );
|
|
result += TQString ( "<td>%1%</td>" ).arg ( data );
|
|
csv += data + "%,";
|
|
}
|
|
else if ( dateColumns.contains ( *it_column ) )
|
|
{
|
|
// do this before we possibly change data
|
|
csv += "\"" + data + "\",";
|
|
|
|
// if we have a locale() then use its date formatter
|
|
if ( TDEGlobal::locale() && ! data.isEmpty() ) {
|
|
TQDate qd = TQDate::fromString ( data, TQt::ISODate );
|
|
data = TDEGlobal::locale()->formatDate ( qd, true );
|
|
}
|
|
result += TQString ( "<td class=\"left\">%1</td>" ).arg ( data );
|
|
}
|
|
else
|
|
{
|
|
result += TQString ( "<td class=\"left\">%1</td>" ).arg ( data );
|
|
csv += "\"" + data + "\",";
|
|
}
|
|
++it_column;
|
|
}
|
|
|
|
result += "</tr>\n";
|
|
csv = csv.left ( csv.length() - 1 ); // remove final comma
|
|
csv += "\n";
|
|
}
|
|
|
|
//
|
|
// Final group totals
|
|
//
|
|
|
|
// Do subtotals backwards
|
|
if ( m_config.isConvertCurrency() )
|
|
{
|
|
int fraction = file->baseCurrency().smallestAccountFraction();
|
|
TQValueList<GroupIterator>::iterator it_group = groupIteratorList.fromLast();
|
|
while ( it_group != groupIteratorList.end() )
|
|
{
|
|
( *it_group ).update ( TableRow() );
|
|
|
|
if ( ( *it_group ).depth() == 1 ) {
|
|
grandtotal += ( *it_group ).subtotal();
|
|
grandtotal = grandtotal.convert(fraction);
|
|
}
|
|
|
|
|
|
TQString subtotal_html = ( *it_group ).subtotal().formatMoney ( fraction );
|
|
TQString subtotal_csv = ( *it_group ).subtotal().formatMoney ( fraction, false );
|
|
|
|
result += "<tr class=\"sectionfooter\">"
|
|
"<td class=\"left" + TQString::number ( ( *it_group ).depth() - 1 ) + "\" "
|
|
"colspan=\"" + TQString::number ( columns.count() - 1 - postcolumns.count() ) + "\">" +
|
|
i18n ( "Total" ) + " " + ( *it_group ).oldName() + "</td>"
|
|
"<td>" + subtotal_html + "</td></tr>\n";
|
|
csv += "\"" + i18n ( "Total" ) + " " + ( *it_group ).oldName() + "\",\"" + subtotal_csv + "\"\n";
|
|
--it_group;
|
|
}
|
|
|
|
//
|
|
// Grand total
|
|
//
|
|
|
|
TQString grandtotal_html = grandtotal.formatMoney ( fraction );
|
|
TQString grandtotal_csv = grandtotal.formatMoney ( fraction, false );
|
|
|
|
result += "<tr class=\"sectionfooter\">"
|
|
"<td class=\"left0\" "
|
|
"colspan=\"" + TQString::number ( columns.count() - 1 - postcolumns.count() ) + "\">" +
|
|
i18n ( "Grand Total" ) + "</td>"
|
|
"<td>" + grandtotal_html + "</td></tr>\n";
|
|
csv += "\"" + i18n ( "Grand Total" ) + "\",\"" + grandtotal_csv + "\"\n";
|
|
}
|
|
result += "</table>\n";
|
|
}
|
|
|
|
TQString ListTable::renderHTML ( void ) const
|
|
{
|
|
TQString html, csv;
|
|
render ( html, csv );
|
|
return html;
|
|
}
|
|
|
|
TQString ListTable::renderCSV ( void ) const
|
|
{
|
|
TQString html, csv;
|
|
render ( html, csv );
|
|
return csv;
|
|
}
|
|
|
|
void ListTable::dump ( const TQString& file, const TQString& context ) const
|
|
{
|
|
TQFile g ( file );
|
|
g.open ( IO_WriteOnly );
|
|
|
|
if ( ! context.isEmpty() )
|
|
TQTextStream ( &g ) << context.arg ( renderHTML() );
|
|
else
|
|
TQTextStream ( &g ) << renderHTML();
|
|
g.close();
|
|
}
|
|
|
|
void ListTable::includeInvestmentSubAccounts()
|
|
{
|
|
// if we're not in expert mode, we need to make sure
|
|
// that all stock accounts for the selected investment
|
|
// account are also selected
|
|
TQStringList accountList;
|
|
if(m_config.accounts(accountList)) {
|
|
if(!KMyMoneyGlobalSettings::expertMode()) {
|
|
TQStringList::const_iterator it_a, it_b;
|
|
for(it_a = accountList.begin(); it_a != accountList.end(); ++it_a) {
|
|
MyMoneyAccount acc = MyMoneyFile::instance()->account(*it_a);
|
|
if(acc.accountType() == MyMoneyAccount::Investment) {
|
|
for(it_b = acc.accountList().begin(); it_b != acc.accountList().end(); ++it_b) {
|
|
if(!accountList.contains(*it_b)) {
|
|
m_config.addAccount(*it_b);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|