/************************* * * * * * * * * * * * * *************************** Copyright (c) 1999-2005 Ryan Bobko ryan@ostrich-emulators.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. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. ************************** * * * * * * * * * * * * **************************/ #include #include #include #ifndef Q_OS_WIN #include #else #include "acctreport.h" #include "areport.h" #include "bdgreport.h" #include "breport.h" #include "dreport.h" #include "jreport.h" #include "lreport.h" #include "mbdgreport.h" #include "preport.h" #include "tbalreport.h" #include "transreport.h" #include "doublebargraph.h" #include "doublelinegraph.h" #include "monthlygraph.h" #include "piegraph.h" #include "singlebargraph.h" #include "singlelinegraph.h" #endif #ifdef HAVE_CONFIG_H #include "config.h" #endif #include "qhacc.h" #include "qhaccutils.h" #include "qhacctable.h" #include "qhaccconstants.h" #include "qhaccsegmenter.h" #include "localfileplugin.h" #include "qhacc.moc" #include //const int QHacc::ROLLOVER=( int )std::pow( ( double )10, QC::DECIMALS ); const int QHacc::ROLLOVER=100; const int QHacc::PIDATABASE=0; const int QHacc::PIIMPORTER=1; const int QHacc::PIEXPORTER=2; const int QHacc::PIREPORTER=3; const int QHacc::PIGRAPHER= 4; const int QHacc::PITYPES= 5; const char * QHacc::CURRENCYSEPARATOR="."; const int QHacc::COMPATV=0x030300; const int QHacc::COMPATVLF=0x030300; QHacc::QHacc( const char * rootdir ) : QObject(){ db=0; conv=0; plugmen=new PluginManager * [PITYPES]; for( int i=0; imax( ACCOUNTS, QC::AID ).getu()+1 ) ); if( newA[QC::AOBAL].gets().toInt()==0 ) newA.set( QC::AOBAL, conv->convert( 0, Engine, Engine ) ); newA.set( QC::ACBAL, newA[QC::AOBAL] ); if( db->add( ACCOUNTS, newA )==QHaccTable::VALID ){ ret=newA[QC::AID].getu(); emit addedA( newA ); if( db->dirty() ) emit needSave( true ); } return ret; } void QHacc::removeA( const Account& a ) { // remove this account and all its transactions // reparent any accounts whose parent is this account TableCol aid( a[QC::AID] ); db->setAtom( BEGIN ); // find this account's children and delete them, too uint nars=0; auto_ptr ars=db->getWhere( ACCOUNTS, TableSelect( QC::APID, aid ), nars ); for( uint i=0; iat( i ) ); std::ostream * str=0; if( Utils::debug( Utils::DBGMAJOR, str ) ) *str<<"removing account "< ts( 1, TableSelect( QC::XSACCTID, aid ) ); auto_ptr deleters=db->getWhere( XTRANS, TableGet( QC::XTID ), ts, trows ); // ...remove the splits first, of course... db->deleteWhere( SPLITS, TableSelect( QC::SACCTID, aid ) ); // ...and then the transactions for( uint i=0; iat( i ).get( 0 ); db->deleteWhere( TRANSACTIONS, TableSelect( QC::TID, tid ) ); } // delete the account from the accounts table db->deleteWhere( ACCOUNTS, PosVal( QC::AID, aid ) ); // recalc all balances auto_ptr temp=db->getWhere( ACCOUNTS, TableSelect( QC::AID, TableCol( 0 ), TableSelect::NE ), trows ); for( uint i=0; iat( i ); emit updatedA( ac, calcBalOfA( ac ) ); } db->setAtom( COMMIT ); emit removedA( a ); if( db->dirty() ) emit needSave( true ); } void QHacc::updateA( const Account& older , const Account& newer ){ Account mynew( newer ); const TableCol AID( older[QC::AID] ); mynew.set( QC::AID, AID ); // cannot change accountids db->setAtom( BEGIN ); db->updateWhere( ACCOUNTS, TableSelect( QC::AID, AID ), mynew ); emit updatedA( older, calcBalOfA( mynew ) ); db->setAtom( COMMIT ); if( db->dirty() ) emit needSave( true ); } Account QHacc::getA( uint id ) { Account ret; uint rows=0; auto_ptr temp=db->getWhere( ACCOUNTS, TableSelect( QC::AID, TableCol( id ) ), rows ); if( rows>0 ) ret=temp->at( 0 ); return ret; } Account QHacc::getA( const QString& model ) { uint rows=0; auto_ptr temp=db->getWhere( ACCOUNTS, TableSelect( QC::AID, TableCol( 0 ), TableSelect::NE ), rows ); Account gotit; // search full names for( uint i=0; iat( i ) )==model ) gotit=temp->at( i ); } if( gotit.isNull() ){ // search for short names for( uint i=0; iat( i )[QC::ANAME]==model ) gotit=temp->at( i ); } } if( getBP( "USEANUMSFORNAMES" ) ){ // if we're using anums, check for that, too if( gotit.isNull() ){ for( uint i=0; iat( i )[QC::ANUM]==model ) gotit=temp->at( i ); } } } if( gotit.isNull() ){ // search IDs, just in case for( uint i=0; iat( i )[QC::AID]==model ) gotit=temp->at( i ); } } return gotit; } auto_ptr QHacc::getAs( const TableGet& tg ){ vector vec; vec.push_back( TableSelect( QC::AID, TableCol( 0 ), TableSelect::NE ) ); uint rr=0; return db->getWhere( ACCOUNTS, tg, vec, rr ); } QString QHacc::getFNameOfA( uint id ) { if( id==0 ) return QString(); return getFNameOfA( getA( id ) ); } QString QHacc::getFNameOfA( const Account& account ) { if( account.isNull() ) return QString(); uint pid=account.getu( QC::APID ); QString name=account.gets( QC::ANAME ); if( pid==0 ) return name; name=getFNameOfA( getA( pid ) )+QC::ASEP+name; return name; } Account QHacc::getBlankA() { return getA( 0 ); } int QHacc::getABalOn( const Account& acct, const QDate& date, const TableSelect& ts ) { // return the balance of the account at the beginning of the specified // date( i.e., before any transactions for that day are added) // get every transaction after date, subtract // their sum from the current balance vector criteria; criteria.push_back( TableSelect( QC::XSACCTID, acct[QC::AID] ) ); criteria.push_back( TableSelect( QC::XTDATE, date, TableSelect::LT ) ); criteria.push_back( TableSelect( QC::XTVOID, false ) ); criteria.push_back( ts ); uint rr=0; auto_ptr trs=getXTForA( acct, TableGet( QC::XSSUM ), criteria, rr ); int ret=conv->converti( acct[QC::AOBAL].gets(), Engine, Engine ); for( uint i=0; iconverti( trs->at( i )[0].gets(), Engine, Engine ); return ret; } bool QHacc::isLPA( const Account& acct ) { uint type=acct[QC::ATYPE].getu(); return ( type==QC::ASSET || type==QC::EXPENSE ); } /****** * Transaction table maintenace ******/ TableRow QHacc::makeXTrans( const Transaction& tr, const Split& sp ){ TableCol cols[]={ tr[QC::TID], sp[QC::SID], tr[QC::TLID], sp[QC::SRECO], sp[QC::SACCTID], tr[QC::TTYPE], tr[QC::TNUM], tr[QC::TPAYEE], tr[QC::TMEMO], sp[QC::SSUM], tr[QC::TDATE], sp[QC::SRECODATE], sp[QC::SMETA], tr[QC::TMETA], sp[QC::STAXABLE], tr[QC::TVOID] }; return TableRow( cols, QC::XCOLS ); } void QHacc::splitXTrans( const TableRow& xt, Transaction& tr, Split& spl ){ tr=splitXTrans( xt ); spl=TableRow( QC::SCOLS ); if( xt.isNull() ) return; int xt2[]={ QC::XTID, QC::XSID, QC::XSRECO, QC::XSACCTID, QC::XSSUM, QC::XSRECODATE, QC::XSMETA, QC::XSTAXABLE }; int s1[]={ QC::STID, QC::SID, QC::SRECO, QC::SACCTID, QC::SSUM, QC::SRECODATE, QC::SMETA, QC::XSTAXABLE }; for( int i=0; imax( TRANSACTIONS, QC::TID ).getu()+1 ); Transaction tr( tra ); tr.set( QC::TID, tid ); db->setAtom( BEGIN ); db->add( TRANSACTIONS, tr ); tbl.updateWhere( TableSelect(), TableUpdate( QC::STID, tid ) ); uint sid=db->max( SPLITS, QC::SID ).getu(); // get highest split id uint tsrows=tbl.rows(); // don't want to load here, because adding to an index is now extremely fast //db->startLoad( SPLITS, tsrows ); for( uint i=0; iadd( SPLITS, s ); // we can emit our addedT here, or the splits won't get found // because the split table index won't be updated until the // stopload is performed. Save the transactions for later xtrans.add( makeXTrans( tr, s ) ); } //db->stopLoad( SPLITS ); if( regt ){ // now that we're done loading the accounts, calc their balances for( uint i=0; isetAtom( COMMIT ); if( db->dirty() ) emit needSave( true ); // emit the signals for the new transactions for( uint i=0; isetAtom( BEGIN ); db->updateWhere( TRANSACTIONS, TableSelect( QC::TID, tr[QC::TID] ), tr ); /* cout<<"updating T"<deleteWhere( SPLITS, TableSelect( QC::STID, tr[QC::TID] ) ); // add a new split to replace the old one(s) TableCol topid( db->max( SPLITS, QC::SID ).getu()+1 ); s.set( QC::SID, topid ); s.set( QC::STID, tr[QC::TID] ); db->add( SPLITS, s ); emit updatedT( makeXTrans( tr, s ) ); if( regt ){ const Account& ac=getA( s[QC::SACCTID].getu() ); emit updatedA( ac, calcBalOfA( ac ) ); } } else{ /* cout<<"update t "<updateWhere( SPLITS, TableSelect( QC::SID, old[QC::SID] ), newy ); updates+=makeXTrans( tr, newy ); // don't process again newsplits.deleteWhere( PosVal( QC::SACCTID, newy[QC::SACCTID] ) ); } else{ // old split isn't in the new splits, so we'll remove it //cout<<" not found, so removing "<toString()<deleteWhere( SPLITS, PosVal( QC::SID, old[QC::SID] ) ); removes+=makeXTrans( tr, old ); } if( regt ) accts+=getA( old.getu( QC::SACCTID ) ); } // at this point, anything left in the newsplits table is a new split for( uint i=0; imax( SPLITS, QC::SID ).getu()+1; Split s=newsplits[i]; s.set( QC::SID, TableCol( topid ) ); if( s[QC::SRECO]=="" ) s.set( QC::SRECO, TableCol( QC::NREC ) ); s.set( QC::SRECODATE, ( s[QC::SRECO]==QC::YREC ) ? QDate::currentDate() : QC::XDATE ); db->add( SPLITS, s ); adds+=makeXTrans( tr, s ); if( regt ) accts+=getA( s.getu( QC::SACCTID ) ); } db->setAtom( COMMIT ); for( uint i=0; idirty() ) emit needSave( true ); emit updatedT(); } bool QHacc::condenseSG( QHaccTable * splits ) const { // make sure that each split has a unique account // otherwise combine non-unique transactions // the easiest way is to add an index on tacctid if( splits->isEmpty() ) return false; if( !getBP( "DOUBLEENTRY" ) ) return true; QHaccTableIndex idx( splits, QC::SACCTID, CTUINT ); uint * pos=0, sz=0; QHaccSegmenter::segment( this, splits, &idx, pos, sz ); for( uint i=0; i1 ){ // we have duplicate QC::TACCTIDs, so get all the // info we need, and then combine the trans TableRow change=splits->at( idx[pos[i]] ); int sum=0; for( uint j=pos[i]; jconverti( splits->at( idx[j] )[QC::SSUM].gets(), Engine, Engine ); } // we now know the sum, so delete the offending // transactions and add the one we just made change.set( QC::SSUM, TableCol( conv->convert( sum, Engine, Engine ) ) ); splits->deleteWhere( PosVal( QC::SACCTID, change[QC::SACCTID] ) ); idx.reindex(); splits->add( change ); idx.reindex(); } } delete [] pos; uint rr=0; auto_ptr rslt=splits->getWhere( TableSelect(), rr ); for( uint i=0; iat( i ); QString sum=s.gets( QC::SSUM ); int sc=sum.find( "%" ); if( sc>-1 && conv->converti( sum.left( sc ), Engine, Engine )==0 ){ splits->deleteWhere( TableSelect( QC::SACCTID, s[QC::SACCTID] ) ); } } if( splits->rows()<2 ){ std::ostream * str=0; if( Utils::error( Utils::DBGMAJOR, str ) ) *str<<"split transaction does not resolve to enough accounts"<converti( a[QC::ACBAL].gets(), Engine, Engine ); } QString str=sum.left( sum.find( "%" ) ); int tsum= conv->converti( QString::number( str.toFloat()*summer/100/ROLLOVER ), Engine, Engine ); s.set( QC::SSUM, TableCol( conv->converti( tsum, Engine, Engine ) ) ); if( tsum<0 ) debs+=tsum; else creds+=tsum; nsplits+=s; } else if( sum=="p" ){ // loan principal payment hasloan=true; loanprinc=s; // NOTE: didn't load s into nsplits } else if( sum=="i" ){ // loan interest payment hasloan=true; loanint=s; // NOTE: didn't load s into nsplits } else{ int tsum=conv->converti( sum, Engine, Engine ); if( tsum<0 ) debs+=tsum; else creds+=tsum; nsplits+=s; } } // a loan can be problematic in the following ways: // 1) we have a loan, but not a principal AND interest split // 2) we have all the splits, but the payment isn't to a loan account // 3) we don't have a loan, but we have a principal OR interest split if( hasloan ){ if( loanprinc.isNull() || loanint.isNull() ) good=false; // case 1 else{ // case 2 const Account& a=getA( loanprinc[QC::SACCTID].getu() ); float i; int n; QString p; if( Utils::isLoan( a, &i, &p, &n ) ){ // we're here, we might as well resolve the payments i=i/1200; // convert annual rate to monthly rate and / 100 to get % int bal=0-conv->converti( a[QC::ACBAL].gets(), Engine, Engine ); int pay=conv->converti( p, Engine, Engine ); const int S=( int )( bal/pow( 1+i,n-1 ) ); int interest=( int )(i*S*pow( i+1, n-1 ) ); loanprinc.set( QC::SSUM, conv->convert( pay-interest, Engine, Engine ) ); loanint.set( QC::SSUM, conv->convert( interest, Engine, Engine ) ); // add the splits to the newsplits table nsplits+=loanprinc; nsplits+=loanint; //cout<<"bal is "<converti( s[QC::SSUM].gets(), Engine, Engine ); // we want percentage of debit to be related to the current // credit field, and percentage of credit to be related // to the debit field. That means we find the percentages // accordingly, but then flip the sign if( perc<0 ) perc=creds*perc/100/ROLLOVER; else perc=debs*perc/100/ROLLOVER; perc=0-perc; if( perc!=0 ){ totsum+=perc; // we'll need to figure out if we're balanced later s.set( QC::SSUM, TableCol( conv->converti( perc, Engine, Engine ) ) ); nsplits+=s; } } } good=false; if( totsum==0 ) good=true; // no remainder is good else{ if( !remainder.isNull() ){ remainder.set( QC::SSUM, TableCol( conv->convert( -totsum, Engine, Engine ) ) ); nsplits+=remainder; good=true; } // else we have a remainder value but no remainder split } } //cout<<( good ? "good" : "bad" )<<" splits:"<clear(); splits->load( &nsplits ); } if( !good ){ std::ostream * str=0; if( Utils::error( Utils::DBGMAJOR, str ) ){ *str<<"split sums do not resolve"<rows(); i++ ){ *str<<" "<at( i ).toString()<setAtom( BEGIN ); removeNTFor( tid, false ); uint temprows=0; auto_ptr temp=db->getWhere( SPLITS, TableSelect( QC::STID, stid ), temprows ); db->deleteWhere( SPLITS, PosVal( QC::STID, stid ) ); db->deleteWhere( TRANSACTIONS, PosVal( QC::TID, stid ) ); for( uint i=0; iat( i ); emit removedT( makeXTrans( tr, s ) ); const Account& ac=getA( s.getu( QC::SACCTID ) ); emit updatedA( ac, calcBalOfA( ac ) ); } db->setAtom( COMMIT ); if( db->dirty() ) emit needSave( true ); emit removedT(); } Transaction QHacc::getT( uint id ) { Transaction ret; if( id!=0 ){ uint rr=0; auto_ptr temp=db->getWhere( TRANSACTIONS, TableSelect( QC::TID, TableCol( id ) ), rr ); if( rr>0 ) ret=temp->at( 0 ); } return ret; } void QHacc::setRec( QHaccTable * tbl, uint rt ){ // reconcile a whole bunch of splits at once // we need to recalc the balances of the accounts represented in the // splits after all the reconciling is done db->setAtom( BEGIN ); for( uint i=0; irows(); i++ ){ Transaction xt=tbl->at( i ); vector pvs; pvs.push_back( PosVal( QC::SRECO, TableCol( rt ) ) ); pvs.push_back( PosVal( QC::SRECODATE, TableCol( ( rt==QC::YREC ) ? QDate::currentDate() : QC::XDATE ) ) ); db->updateWhere( SPLITS, TableSelect( QC::SID, xt[QC::XSID] ), TableUpdate( pvs ) ); xt.set( QC::XSRECO, TableCol( rt ) ); emit updatedT( xt ); } QHaccTableIndex idx( tbl, QC::XSACCTID, CTUINT ); uint * arr, asz; QHaccSegmenter::segment( this, tbl, &idx, arr, asz ); for( uint i=0; iat( idx[arr[i]] ).getu( QC::XSACCTID ) ); emit updatedA( ac, calcBalOfA( ac ) ); } delete [] arr; db->setAtom( COMMIT ); if( db->dirty() ) emit needSave( true ); } void QHacc::setRecNR( const Transaction& xt, uint rt ){ // "No Recalc" version of setTReco. update the transaction's reconcile flag, // but don't mess with the account balances. vector pvs; pvs.push_back( PosVal( QC::SRECO, TableCol( rt ) ) ); pvs.push_back( PosVal( QC::SRECODATE, TableCol( ( rt==QC::YREC ) ? QDate::currentDate() : QC::XDATE ) ) ); db->updateWhere( SPLITS, TableSelect( QC::SID, xt[QC::XSID] ), TableUpdate( pvs ) ); Transaction tr( xt ); tr.set( QC::XSRECO, TableCol( rt ) ); emit updatedT( tr ); if( db->dirty() ) emit needSave( true ); } QHaccTable QHacc::getTSplits( uint tid ){ uint rr=0; auto_ptr rs=db->getWhere( SPLITS, TableSelect( QC::STID, TableCol( tid ) ), rr ); return QHaccTable( *rs ); } auto_ptr QHacc::getXTForA( const Account& a, const TableGet& tg, vector ts, uint& rows ){ // we want to retrieve (regular) transactions for the given account // put the account identifier first because we expect // that to reduce the table size the most. likewise, put // the XTTYPE selection last because it will do the least ts.insert( ts.begin(), TableSelect( QC::XSACCTID, a[QC::AID] ) ); ts.push_back( TableSelect( QC::XTTYPE, TableCol( QC::MEMORIZED ), TableSelect::NE ) ); return db->getWhere( XTRANS, tg, ts, rows ); } void QHacc::removeNTFor( uint id, bool idIsAID ){ vector ts( 1, TableSelect( ( idIsAID ? QC::NACCTID : QC::NTID ), TableCol( id ) ) ); uint rr=0; auto_ptr deleters=db->getWhere( NAMEDTRANS, TableGet( QC::NNAME ), ts, rr ); db->setAtom( BEGIN ); for( uint i=0; ideleteWhere( JOBS, TableSelect( QC::JWHAT, deleters->at( i ).get( 0 ) ) ); } db->deleteWhere( NAMEDTRANS, ts[0] ); db->setAtom( COMMIT ); } void QHacc::updateNT( uint nid, const TableRow& nt, const Transaction& t, QHaccTable& splits ){ //cout<<"updating namedtrans "< ts( 1, TableSelect( QC::NID, TableCol( nid ) ) ); // this procedure is a little convoluted because we don't have a concept // of an atomic transaction in all databases...we therefore need to // be careful to avoid RI inconsistencies (especially w/ the jobs table) uint rr=0; auto_ptr rslt=db->getWhere( NAMEDTRANS, TableGet(), ts, rr ); if( rr>0 ){ TableRow j( getJ( rslt->at( 0 )[QC::NNAME].gets() ) ); db->setAtom( BEGIN ); if( !j.isNull() ){ db->deleteWhere( JOBS, TableSelect( QC::JID, j[QC::JID] ) ); j.set( QC::JWHAT, nt[QC::NNAME] ); } TableRow nt2( nt ); nt2.set( QC::NID, nid ); // can't change the NID db->updateWhere( NAMEDTRANS, TableSelect( QC::NID, nid ), nt2 ); updateT( t, splits ); if( !j.isNull() ) db->add( JOBS, j ); db->setAtom( COMMIT ); if( db->dirty() ) emit needSave( true ); } } uint QHacc::addNTForA( const TableRow& nt, const Transaction& t, QHaccTable& splits ){ if( nt.isNull() ) return 0; uint ret=0; uint rr=0; auto_ptr rslt=db->getWhere( NAMEDTRANS, TableSelect( QC::NNAME, TableCol( nt[QC::NNAME].gets() ) ), rr ); if( rr==0 ){ db->setAtom( BEGIN ); Transaction t2( t ); t2.set( QC::TTYPE, TableCol( QC::MEMORIZED ) ); t2.set( QC::TDATE, TableCol( QC::XDATE ) ); blockSignals( true ); uint tid=addT( t2, splits ); t2.set( QC::TID, TableCol( tid ) ); blockSignals( false ); if( tid ){ TableRow tr( nt ); uint maxnid=db->max( NAMEDTRANS, QC::NID ).getu()+1; tr.set( QC::NID, TableCol( maxnid ) ); tr.set( QC::NTID, TableCol( tid ) ); if( db->add( NAMEDTRANS, tr )==QHaccTable::VALID ) ret=maxnid; for( uint i=0; isetAtom( COMMIT ); } else db->setAtom( ROLLBACK ); } return ret; } void QHacc::removeNT( const QString& name ){ Transaction t; QHaccTable s( QC::SCOLS ); TableRow r=getNT( name, t, s ); if( !r.isNull() ){ // if we even have a namedtrans with this name... const TableCol NAME( name ); // delete the jobs associated with it //db->deleteWhere( JOBS, TableSelect( QC::JWHAT, NAME ) ); TableRow r=getJ( name ); db->setAtom( BEGIN ); if( !r.isNull() ) removeJ( r.getu( QC::JID ) ); // delete the namedtrans itself db->deleteWhere( NAMEDTRANS, TableSelect( QC::NNAME, NAME ) ); // and delete the associated transaction removeT( t.getu( QC::TID ) ); db->setAtom( COMMIT ); } } TableRow QHacc::getNT( const QString& name, Transaction& t, QHaccTable& s ){ uint rr=0; TableRow ret; auto_ptr nts=db->getWhere( NAMEDTRANS, TableSelect( QC::NNAME, TableCol( name ) ), rr ); if( rr>0 ){ ret=nts->at( 0 ); uint ntu=ret[QC::NTID].getu(); t=getT( ntu ); s=getTSplits( ntu ); } return ret; } bool QHacc::isResolvable( const TableRow&, const QHaccTable& splits ){ // return true if the splits are all resolvable // we're really just interested if the form of these splits // could be resolved; we don't actually do any resolving here int numRem=0; // remainder accounts int credperc=0; int debperc=0; bool hasPercA=false; // account percentage int creds=0; int debs=0; int tsum=0; for( uint i=0; iconverti( sum.left( sum.find( "%" ) ), Engine, Engine ); if( sumval<0 ) debperc+=sumval; else credperc+=sumval; } else if( sum==QC::REMAINDERVAL ) numRem++; else if( sum.endsWith( "%a" ) ) hasPercA=true; else{ int sumval=conv->converti( sum, Engine, Engine ); if( sumval<0 ) debs+=sumval; else creds+=sumval; } } tsum=creds+debs; // we assume all accounts will resolve to a non-zero value // even though we don't really check that assumption here // the splits aren't resolvable if: // 1) we have more than one remainder account if( numRem>1 ) return false; // 2) we have creds!=debs, no percentages, and no remainder if( tsum!=0 && credperc==debperc==0 && numRem!=1 ) return false; // 3) we have percentages, but no hard values if( ( creds==0 && debperc!=0 ) || ( debs==0 && credperc!=0 ) ) return false; return true; } uint QHacc::addJ( const TableRow& j ){ uint ret=0; TableRow newJ=j; newJ.set( QC::JID, TableCol( db->max( JOBS, QC::JID ).getu()+1 ) ); if( db->add( JOBS, newJ )==QHaccTable::VALID ){ if( db->dirty() ) emit needSave( true ); ret=newJ[QC::JID].getu(); } return ret; } void QHacc::removeJ( uint jid ){ const TableSelect TS( QC::JID, TableCol( jid ) ); uint rr=0; auto_ptr rslt=db->getWhere( JOBS, TS, rr ); if( rr>0 ){ TableRow r=rslt->at( 0 ); db->deleteWhere( JOBS, TS ); if( db->dirty() ) emit needSave( true ); } } TableRow QHacc::getJ( const QString& str ){ uint rr=0; auto_ptr rslt=db->getWhere( JOBS, TableSelect( QC::JWHAT, TableCol( str ) ), rr ); if( rr>0 ) return rslt->at( 0 ); else return TableRow(); } auto_ptr QHacc::getNTsForA( uint aid ){ uint rr=0; vector ts( 1, TableSelect( QC::NACCTID, TableCol( aid ) ) ); return db->getWhere( NAMEDTRANS, TableGet(), ts, rr ); } /****** * Journal table maintenace ******/ auto_ptr QHacc::getLs(){ uint rr=0; return db->getWhere( JOURNALS, TableSelect(), rr ); } Journal QHacc::getL( const QString& lname ){ uint rr=0; auto_ptr temp=db->getWhere( JOURNALS, TableSelect( QC::LNAME, TableCol( lname ) ), rr ); if( rr>0 ) return temp->at( 0 ); else return getL( lname.toUInt() ); // try by LID } Journal QHacc::getL( uint lid ){ uint rr=0; auto_ptr temp=db->getWhere( JOURNALS, TableSelect( QC::LID, TableCol( lid ) ), rr ); if( rr>0 ) return temp->at( 0 ); else return TableRow(); } uint QHacc::addL( const Journal& l ){ uint ret=0; TableCol maxlid=db->max( JOURNALS, QC::LID ); maxlid=TableCol( maxlid.getu()+1 ); // new highest journal id Journal newy( l ); newy.set( QC::LID, maxlid ); if( db->add( JOURNALS, newy )==QHaccTable::VALID ){ emit addedL( newy ); ret=maxlid.getu(); if( db->dirty() ) emit needSave( true ); } return ret; } void QHacc::removeL( const Journal& l ) { if( db->cnt( JOURNALS )>1 ){ // delete namedtrans, scheduled trans, and regular transactions and splits // from this journal, then delete the journal uint rr=0; vector v( 1, TableSelect( QC::TLID, l[QC::LID] ) ); auto_ptr trans=getWhere( TRANSACTIONS, TableGet( QC::TID ), v, rr ); db->setAtom( BEGIN ); for( uint i=0; iat( i ).get( 0 ); removeNTFor( TID.getu(), false ); db->deleteWhere( SPLITS, TableSelect( QC::STID, TID ) ); } db->deleteWhere( TRANSACTIONS, v[0] ); db->deleteWhere( JOURNALS, TableSelect( QC::LID, l[QC::LID] ) ); rr=0; auto_ptr temp=db->getWhere( ACCOUNTS, TableSelect(), rr ); for( uint i=0; iat( i ); emit updateA( ac, calcBalOfA( ac ) ); } db->setAtom( COMMIT ); emit removedL( l ); if( db->dirty() ) emit needSave( true ); } } void QHacc::updateL( const Journal& older , const Journal& newer ){ Journal mynew=newer; const TableCol LID=older[QC::LID]; mynew.set( QC::LID, LID ); // can't change journal ids db->updateWhere( JOURNALS, TableSelect( QC::LID, LID ), mynew ); emit updatedL( older, mynew ); if( db->dirty() ) emit needSave( true ); } /****** * Preference table maintenace ******/ QString QHacc::getSP( const QString& pref ) const { QString ret=igetP( pref ); if( ret.isNull() ) ret=QString( "" ); return ret; } QColor QHacc::getCP( const QString& pref ) const { QString val=getSP( pref ); if( val.isEmpty() ){ if( pref=="MAINCOLOR" ) return QColor( 255, 255, 255 ); return QColor( 173, 216, 230 ); } // colors are saved as space-delimited ints QString ints[3]; Utils::parser( val, " ", 0, ints, 3 ); return QColor( ints[0].toInt(), ints[1].toInt(), ints[2].toInt() ); } QDate QHacc::getDP( const QString& pref ) const { QString val=getSP( pref ); QString sep=getSP( "DATESEPARATOR" ); if( val.isEmpty() ) return QDate::currentDate(); uint num=3; QString * strs=new QString[num]; Utils::parser( val, sep, 0, strs, num ); QDate d( strs[0].toInt(), strs[1].toInt(), strs[2].toInt() ); delete [] strs; return d; } QFont QHacc::getWP( const QString& pref ) const { QString val=getSP( pref ); if( val.isEmpty() ) return QFont( "SansSerif" ); QFont f; f.fromString( val ); return f; } bool QHacc::getBP( const QString& pref ) const { QString val=getSP( pref ); return ( val=="Y" ? true : false ); } int QHacc::getIP( const QString& pref ) const { QString val=getSP( pref ); return ( val.isEmpty() ? 0 : val.toInt() ); } float QHacc::getFP( const QString& pref ) const { QString val=getSP( pref ); return ( val.isEmpty() ? 0 : val.toFloat() ); } QString QHacc::igetP( const QString& pref ) const { map::const_iterator iter=prefcache.find( pref ); if( iter==prefcache.end() ) return QString(); else return iter->second; } bool QHacc::isetP( const QString& p, const QString& v ){ // return true if the preference needed changing // don't allow setting a null preference (change it to empty string) TableCol pref( p ), val( v.isNull() ? QString( "" ) : v ); QString oldy=igetP( p ); bool ret=true; if( oldy.isNull() ){ // add the new preference TableCol tcs[]={ pref, val }; db->add( PREFERENCES, TableRow( tcs, 2 ) ); } else{ // update the old preference if( oldy==v ) ret=false; else{ db->updateWhere( PREFERENCES, TableSelect( QC::PPREF, pref ), TableUpdate( QC::PVALUE, val ) ); } } //cout<<"cached pref val for "<dirty() ) emit needSave( true ); return ret; } void QHacc::setSP( const QString& pref, const QString& val ){ if( isetP( pref, val ) ){ conv->changedP( pref, val ); emit changedP( pref, val ); } } void QHacc::setCP( const QString& pref, const QColor& val ){ if( isetP( pref, QString::number( val.red() )+" " +QString::number( val.green() )+" " +QString::number( val.blue() ) ) ) emit changedP( pref, val ); } void QHacc::setDP( const QString& pref, const QDate& val ){ QString sep=getSP( "DATESEPARATOR" ); QString date; date=QString::number( val.year() )+sep+QString::number( val.month() ) +sep+QString::number( val.day() ); if( isetP( pref, date ) ) emit changedP( pref, val ); } void QHacc::setWP( const QString& pref, const QFont& val ){ if( isetP( pref, val.toString() ) ) emit changedP( pref, val ); } void QHacc::setFP( const QString& pref, float val ){ if( isetP( pref, QString::number( val ) ) ){ conv->changedP( pref, val ); emit changedP( pref, val ); } } void QHacc::setBP( const QString& pref, bool val ){ if( isetP( pref, val ? "Y" : "N" ) ){ conv->changedP( pref, val ); if( pref=="INCLUDESUBSONRECALC" ){ // if this pref is set, we need to recalc all account sums immediately! auto_ptr temp=getAs( TableGet() ); uint naccts=temp->rows(); for( uint i=0; iat( i ); Account ac2=calcBalOfA( ac ); if( ac2[QC::ACBAL]!=ac[QC::ACBAL] ) updateA( ac, ac2 ); } } emit changedP( pref, val ); } } void QHacc::setIP( const QString& pref, int val ){ if( isetP( pref, QString::number( val ) ) ) emit changedP( pref, val ); } bool QHacc::homeIsLocalFiles() const { if( db ) return ( db->info().descr()==LocalFileDBPlugin::pinfo.descr() ); return false; } QString QHacc::getHome() const { return qhacchome; } bool QHacc::setHome( const QString& home ) { std::ostream * str=0; if( home.isEmpty() ){ if( Utils::error( Utils::ERRFATAL, str ) ) *str<<"\nQHACC_HOME is not set. Please set it before running " <<"QHacc, or use the --home option.\n"<connect( this, qhacchome, error ) && db->load( error ) ) ){ if( Utils::error( Utils::ERRFATAL, str ) ){ *str<<"could not access QHACC_HOME: "<info().atomizer() ){ if( Utils::error( Utils::ERROPER, str ) ){ *str<info().stub()<<" does not support atomic operations"< prs=db->getWhere( PREFERENCES, TableSelect(), rr ); for( uint i=0; iat( i ); prefcache[row[QC::PPREF].gets()]=row[QC::PVALUE].gets(); } /* cout<<"pref table:"<at( i ); cout<::iterator iter=prefcache.begin(); iter!=prefcache.end(); ++iter ){ cout<first<<"||"<second<cnt( PREFERENCES )==0 ) setIP( "QHACCVERSION", COMPATV ); // make sure we have at least the bare-bones preferences needed for operation const int lim=3; const char * prefs[]={ "CSYMBOL", "DATESEPARATOR", "CURRENCYSEPARATOR" }; const char * vals[]={ "$", "/", CURRENCYSEPARATOR }; for( int i=0; iadd( PREFERENCES, TableRow( tcs, QC::PCOLS ) ); prefcache[prefs[i]]=vals[i]; } } // check the version number of the dataset bool ok; int prefver( getSP( "QHACCVERSION" ).toInt( &ok ) ); if( !ok ) prefver=0; if( prefver=COMPATVLF && homeIsLocalFiles() ){ // we can continue, but first update the QHACCVERSION preference // so we don't have to go through this again next time setIP( "QHACCVERSION", COMPATV ); failed=false; } else if( Utils::error( Utils::ERRFATAL, str ) ){ const int M=( COMPATV & 0xff0000 )>>16; const int m=( COMPATV & 0x00ff00 )>>8; const int p=COMPATV & 0x0000ff; const int pM=( prefver & 0xff0000 )>>16; const int pm=( prefver & 0x00ff00 )>>8; const int pp=prefver & 0x0000ff; *str<<"\nYour QHACC_HOME must be upgraded to at least version " < temp=getAs( TableGet() ); uint naccts=temp->rows(); for( uint i=0; iat( i ); Account ac2=calcBalOfA( ac ); if( ac2[QC::ACBAL]!=ac[QC::ACBAL] ) updateA( ac, ac2 ); } } if( db->cnt( JOURNALS )==0 ){ TableCol tcs[]={ TableCol( "1" ), TableCol( "General" ), TableCol( "" ) }; db->add( JOURNALS, TableRow( tcs, QC::LCOLS ) ); } // if we don't have any accounts, we NEED one to act as a pid to // every other account, including itself. (aid=pid=0) if( db->cnt( ACCOUNTS )==0 ){ Account a( QC::ACOLS ); a.set( QC::AID, TableCol( 0 ) ); a.set( QC::AOBAL, TableCol( 0 ) ); a.set( QC::ATRANSNUMS, TableCol( "ATM INT FEE WD" ) ); a.set( QC::ATAXED, TableCol( true ) ); a.set( QC::ACATEGORY, TableCol( true ) ); a.set( QC::ATYPE, QC::EXPENSE ); a.set( QC::APID, TableCol( 0 ) ); db->add( ACCOUNTS, a ); } if( processJobs ) processor(); blockSignals( false ); return true; } Account QHacc::calcBalOfA( const Account& acct ){ // recalculate an account's balance, which may include // its subaccount's balances if INCLUDESUBSONRECALC preference // is set. If this is the case, the procedure comes in three steps // 1) get the balance of all sub accounts // 2) recalculate the transaction sums // 3) recurse on this account's parent (until apid=0) uint rr=0; int ocbal=conv->converti( acct[QC::ACBAL].gets(), Engine, Engine ); int orbal=conv->converti( acct[QC::ARBAL].gets(), Engine, Engine ); int sum=conv->converti( acct[QC::AOBAL].gets(), Engine, Engine ); int rsum=sum; Account ret( acct ); // this is the account we'll operate on // this is stuff we need for step 2, but we want to print it here // before we print anything about the children or parent accounts static const int TSUM=0, TRECO=1, TDATE=2; vector gets; gets.push_back( QC::XSSUM ); gets.push_back( QC::XSRECO ); gets.push_back( QC::XTDATE ); vector v; // ignore void transactions when figuring balance v.push_back( TableSelect( QC::XTVOID, false ) ); auto_ptr trans=getXTForA( acct, TableGet( gets ), v, rr ); std::ostream * str=0; bool write=Utils::debug( Utils::DBGMINOR, str ); if( write ) *str<<"CalcBalOfA "<getas; getas.push_back( QC::ACBAL ); getas.push_back( QC::ARBAL ); getas.push_back( QC::ANAME ); vector vas( 1, TableSelect( QC::APID, acct[QC::AID] ) ); auto_ptr children=getWhere( ACCOUNTS, TableGet( getas ), vas, trr ); for( uint i=0; iat( i ); if( write ) *str<<" "<converti( a[0].gets(), Engine, Engine ); rsum+=conv->converti( a[1].gets(), Engine, Engine ); } } // step 2) sum all transactions for( uint i=0; iat( i ); int tsum=conv->converti( t[TSUM].gets(), Engine, Engine ); sum+=tsum; if( t[TRECO]==QC::YREC ) rsum+=tsum; } if( sum!=ocbal || rsum!=orbal ){ TableSelect ts( QC::AID, acct[QC::AID] ); vector pvs; PosVal pv1( QC::ACBAL, TableCol( conv->convert( sum, Engine, Engine ) ) ); PosVal pv2( QC::ARBAL, TableCol( conv->convert( rsum, Engine, Engine ) ) ); pvs.push_back( pv1 ); pvs.push_back( pv2 ); db->updateWhere( ACCOUNTS, ts, TableUpdate( pvs ) ); ret.set( pv1 ); ret.set( pv2 ); // only go to step 3 if this balance changed if( heavycalc ){ // step 3) recurse on parent uint apid=acct[QC::APID].getu(); if( apid!=0 ){ const Account& a=getA( apid ); if( write ) *str<<" CalcBalOfA recursing to " <converti( acct[QC::ABUDGET].gets(), Engine, Engine ); if( budget!=0 ){ QDate start=QDate::currentDate(); start.setYMD( start.year(), start.month(), 1 ); QDate end=start.addMonths( 1 ); // we only want the last month's worth of transactions // we do this here instead of above because the chances // aren't so good that a budget has been set, so why // waste the time if it's not needed? QHaccTable tbl( *trans ); tbl.addIndexOn( TDATE ); vector vec; vec.push_back( TableSelect( TDATE, TableCol( start ), TableSelect::GE ) ); vec.push_back( TableSelect( TDATE, TableCol( end ), TableSelect::LT ) ); uint rr=0; auto_ptr t2=tbl.getWhere( vec, rr ); trans=t2; sum=0; for( uint i=0; iconverti( trans->at( i )[TSUM].gets(), Engine, Engine ); if( ( budget<0 ) && ( sum0 && sum>budget ) ) emit overBudget( acct, sum-budget ); } return ret; } bool QHacc::save( QString& error ) { // save everything db->save( error ); bool good=!db->dirty(); emit needSave( !good ); return good; } const MonCon& QHacc::converter() const { return *conv; } void QHacc::readpre( const QString& rootdir ){ QString qhaccroot=rootdir; std::ostream * str=0; // first, read the preconf file, if it exists // file should have a debug value, langdir, plugin dir... QString plugindir=qhaccroot+"/plugins"; QString dbplugin; processJobs=true; QFile pre( qhaccroot+"/preconf" ); if( pre.exists() && pre.open( IO_ReadOnly ) ){ QTextStream in( &pre ); while ( !in.eof() ){ QString line=in.readLine(); int finder=line.find( "=" ); QString option=line.left( finder ); QString value=line.mid( finder+1 ); if( option=="PLUGINDIR" ) plugindir=value; else if( option=="DEBUG" ) Utils::setDebug( value.toInt() ); else if( option=="LANGDIR" ) langdir=value; else if( option=="NOJOBS" ) processJobs=false; } pre.close(); } // print this after DEBUG gets set (in preconf) or it'll never get printed if( Utils::debug( Utils::DBGMAJOR, str ) ) *str<<"using "< jobs=db->getWhere( JOBS, TableSelect(), rr ); if( rr>0 ){ const QDate TODAY=QDate::currentDate(); const bool forSched=true; for( uint i=0; iat( i ); QDate lastrun=job.getd( QC::JLASTRUN ); int freq=job.geti( QC::JFREQUENCY ); QString what=job.gets( QC::JWHAT ); bool didrun=false; uint tid=0; Transaction t; QHaccTable splits( QC::SCOLS ); const TableRow& r=getNT( what, t, splits ); if( !r.isNull() ){ t.set( QC::TTYPE, QC::REGULAR ); splits.updateWhere( TableSelect(), TableUpdate( QC::SRECO, QC::NREC ) ); splits.updateWhere( TableSelect(), TableUpdate( QC::SRECODATE, QC::XDATE ) ); if( freq==QC::MONTHFREQ ){ while( lastrun.addMonths( 1 )<=TODAY ){ lastrun=lastrun.addMonths( 1 ); t.set( QC::TDATE, TableCol( lastrun ) ); if( !didrun ) db->setAtom( BEGIN ); tid=addT( t, splits, forSched ); t.set( QC::TID, TableCol( tid+1 ) ); didrun=true; } } else if( freq==QC::BIMONTHFREQ ){ while( lastrun.addDays( lastrun.daysInMonth()/2 )<=TODAY ){ lastrun=lastrun.addDays( lastrun.daysInMonth()/2 ); t.set( QC::TDATE, TableCol( lastrun ) ); if( !didrun ) db->setAtom( BEGIN ); tid=addT( t, splits, forSched ); t.set( QC::TID, TableCol( tid+1 ) ); didrun=true; } } else if( freq==QC::QUARTERLYFREQ ){ while( lastrun.addMonths( 3 )<=TODAY ){ lastrun=lastrun.addMonths( 3 ); t.set( QC::TDATE, TableCol( lastrun ) ); if( !didrun ) db->setAtom( BEGIN ); tid=addT( t, splits, forSched ); t.set( QC::TID, TableCol( tid+1 ) ); didrun=true; } } else if( freq==QC::ONETIMEFREQ ){ if( TODAY>=lastrun ){ t.set( QC::TDATE, TableCol( lastrun ) ); if( !didrun ) db->setAtom( BEGIN ); tid=addT( t, splits, forSched ); t.set( QC::TID, TableCol( tid+1 ) ); didrun=true; // after we've run once, remove the job from the schedule db->deleteWhere( JOBS, TableSelect( QC::JID, job[QC::JID] ) ); } } else if( QC::LASTMONTHDAYFREQ==freq ){ lastrun=lastrun.addMonths( 1 ); lastrun.setYMD( lastrun.year(), lastrun.month(), lastrun.daysInMonth() ); while( lastrun<=TODAY ){ t.set( QC::TDATE, TableCol( lastrun ) ); if( !didrun ) db->setAtom( BEGIN ); tid=addT( t, splits, forSched ); t.set( QC::TID, TableCol( tid+1 ) ); didrun=true; lastrun=lastrun.addMonths( 1 ); lastrun.setYMD( lastrun.year(), lastrun.month(), lastrun.daysInMonth() ); } } else{ while( lastrun.addDays( freq )<=TODAY ){ lastrun=lastrun.addDays( freq ); t.set( QC::TDATE, TableCol( lastrun ) ); if( !didrun ) db->setAtom( BEGIN ); tid=addT( t, splits, forSched ); t.set( QC::TID, TableCol( tid+1 ) ); didrun=true; } } // update the lastrun time of the job if( didrun ){ db->updateWhere( JOBS, TableSelect( QC::JID, job[QC::JID] ), TableUpdate( QC::JLASTRUN, t[QC::TDATE] ) ); db->setAtom( COMMIT ); } } // if getNT } // job loop } // if jobs if( write ) *str<<"done processing jobs"< QHacc::getPluginInfo( int type, int * curr ) const { if( curr ) *curr=-1; #ifdef Q_OS_WIN vector ret; if( type==PIGRAPHER ){ const QString FN="(built-in)"; PluginInfo inf0( SingleBarGraph::pinfo ); PluginInfo inf1( DoubleBarGraph::pinfo ); PluginInfo inf2( SingleLineGraph::pinfo ); PluginInfo inf3( DoubleBarGraph::pinfo ); PluginInfo inf4( PieGraph::pinfo ); const int infs=5; PluginInfo infa[]={ inf0, inf1, inf2, inf3, inf4 }; for( int i=0; i ret( plugmen[type]->getPluginInfo() ); #endif if( type==PIDATABASE || type==PIEXPORTER || type==PIIMPORTER ){ // add the native db info, too PluginInfo lfin( LocalFileDBPlugin::pinfo ); lfin.setFilename( "(built-in)" ); ret.push_back( lfin ); if( type==PIDATABASE && db && curr ){ for( int i=0; i<( int )ret.size(); i++ ){ if( db->info().descr()==ret[i].descr() ) *curr=i; } } } return ret; } void QHacc::resetOBals(){ // this is pretty much calcBalOfA in reverse. We're going to figure out // the opening balance of all accounts by keeping the current balance // and subtracting the transactions' sums std::ostream * str=0; bool outer=Utils::debug( Utils::DBGMINOR, str ); auto_ptr tempacc=getAs( TableGet() ); uint rr=tempacc->rows(); db->setAtom( BEGIN ); for( uint i=0; iat( i ); static const int TSUM=0; uint rws=0; vector cols( 1, QC::XSSUM ); vector v; // ignore void transactions when figuring balance v.push_back( TableSelect( QC::XTVOID, false ) ); auto_ptr trans=getXTForA( acct, TableGet( cols ), v, rws ); if( outer ) *str<<"ResetOBal for "<1 ? "s" : "" )<converti( acct[QC::ACBAL].gets(), Engine, Engine ); for( uint j=0; jconverti( trans->at( j )[TSUM].gets(), Engine, Engine ); if( obal!=conv->converti( acct[QC::AOBAL].gets(), Engine, Engine ) ){ TableCol sum( conv->convert( obal, Engine, Engine ) ); db->updateWhere( ACCOUNTS, TableSelect( QC::AID, acct[QC::AID] ), TableUpdate( QC::AOBAL, sum ) ); acct.set( QC::AOBAL, sum ); emit updatedA( acct, acct ); } } db->setAtom( COMMIT ); } auto_ptr QHacc::getWhere( Table t, vector ts, uint& rows ){ return db->getWhere( t, ts, rows ); } auto_ptr QHacc::getWhere( Table t, const TableGet& tg, vector ts, uint& rows ){ return db->getWhere( t, tg, ts, rows ); } void QHacc::deleteWhere( Table t, const TableSelect& ts ){ db->deleteWhere( t, ts ); } void QHacc::updateWhere( Table t, const TableSelect& ts, const TableUpdate& tu ){ db->updateWhere( t, ts, tu ); } uint QHacc::cnt( Table t ){ return db->cnt( t ); } bool QHacc::load( Table t, const QHaccResultSet * rslt ){ return db->load( t, rslt ); } uint QHacc::add( Table t, const TableRow& tr ){ return db->add( t, tr ); } TableCol QHacc::min( Table t, int col ){ return db->min( t, col ); } TableCol QHacc::max( Table t, int col ){ return db->max( t, col ); } void QHacc::exprt( QHaccResultSet * rslts ){ db->exprt( rslts ); } void QHacc::imprt( QHaccResultSet * rslts ){ // the accounts import information contains the account id=0, // which we want to update (but not delete), and everything else // we just want to add. So, we need to extract the id=0 and do // the update, then delete it from the stuff to add. QHaccTable accts( rslts[QC::ACCTT] ); const TableSelect TS( QC::AID, TableCol( 0 ) ); db->setAtom( BEGIN ); Account acct=accts.getWhere( TS ); accts.deleteWhere( TS ); if( !acct.isNull() ) db->updateWhere( ACCOUNTS, TS, acct ); rslts[QC::ACCTT]=accts; // now we're ready to import db->imprt( rslts ); // now, recalc all balances auto_ptr temp=getAs( TableGet() ); uint naccts=temp->rows(); for( uint i=0; iat( i ); Account ac2=calcBalOfA( ac ); if( ac2[QC::ACBAL]!=ac[QC::ACBAL] ) updateA( ac, ac2 ); } db->setAtom( COMMIT ); } QString QHacc::getPluginFor( int type, const QString& home, QHaccPlugin *& pi ) const { pi=0; QHaccPlugin * temp=0; #ifndef Q_OS_WIN QString ret=plugmen[type]->getPluginFor( home, temp ); #else int idx=-1, loc=home.find( ":" ); QString ret=home.mid( loc+1 ); QString goodstub; vector infos=getPluginInfo( type ); if( loc!=-1 ){ const QString KEY=home.upper(); for( int i=0; i<( int )infos.size(); i++ ){ if( KEY.startsWith( infos[i].stub()+":" ) ){ idx=i; goodstub=infos[i].stub(); } } } if( idx>=0 ){ if( type==PIREPORTER ) { // cycle through our list of "plugins" to find the stub if( goodstub==AccountsReport::pinfo.stub() ) temp=new AccountsReport(); else if( goodstub==AvesReport::pinfo.stub() ) temp=new AvesReport(); else if( goodstub==BudgetReport::pinfo.stub() ) temp=new BudgetReport(); else if( goodstub==BalancesReport::pinfo.stub() ) temp=new BalancesReport(); else if( goodstub==DeltasReport::pinfo.stub() ) temp=new DeltasReport(); else if( goodstub==JournalReport::pinfo.stub() ) temp=new JournalReport(); else if( goodstub==ProfitLossReport::pinfo.stub() ) temp=new ProfitLossReport(); else if( goodstub==MonthlyBudgetReport::pinfo.stub() ) temp=new MonthlyBudgetReport(); else if( goodstub==PayeeReport::pinfo.stub() ) temp=new PayeeReport(); else if( goodstub==TransBalReport::pinfo.stub() ) temp=new TransBalReport(); else if( goodstub==TransReport::pinfo.stub() ) temp=new TransReport(); } else if( type==PIGRAPHER ){ if( idx>=0 ){ // cycle through our list of "plugins" to find the stub if( goodstub==DoubleBarGraph::pinfo.stub() ) temp=new DoubleBarGraph(); else if( goodstub==SingleBarGraph::pinfo.stub() ) temp=new SingleBarGraph(); else if( goodstub==DoubleLineGraph::pinfo.stub() ) temp=new DoubleLineGraph(); else if( goodstub==SingleLineGraph::pinfo.stub() ) temp=new SingleLineGraph(); else if( goodstub==PieGraph::pinfo.stub() ) temp=new PieGraph(); } } } #endif if( ( type==PIDATABASE || type==PIIMPORTER || type==PIEXPORTER ) ){ if( temp==0 ) pi=new LocalFileDBPlugin(); else pi=( QHaccIOPlugin *& )temp; } else if( type==PIREPORTER ) pi=( QHaccReportPlugin *& )temp; else if( type==PIGRAPHER ) pi=( QHaccGraphPlugin *& )temp; return ret; } bool QHacc::destroyPlugin( int type, QHaccPlugin * pi ){ if( pi==0 ) return true; bool done=plugmen[type]->destroyPlugin( pi ); if( !done ){ // must be a built-in plugin delete pi; done=true; } pi=0; return done; } /************************************************************/ /** PluginManager **/ /** Keeps track of plugins that the engine might use **/ /** **/ /** **/ /************************************************************/ PluginManager::PluginManager( const QString& dir, const QString& ext ){ std::ostream * str=0; if( Utils::debug( Utils::DBGMINOR, str ) ) *str<<"adding "<info() ); infoi.setFilename( libpath ); info.push_back( infoi ); destroyer( db ); if( Utils::debug( Utils::DBGMINOR, str ) ){ *str<<"added "<=0 ){ if( !libs[idx] ){ libs[idx]=new QLibrary( info[idx].fname() ); libs[idx]->setAutoUnload( true ); } Creator creator=( Creator )libs[idx]->resolve( "create" ); if ( creator ){ pluginUsage[idx]++; pi=creator(); if( pluginUsage[idx]==1 ){ // first load of this library std::ostream * str=0; if( Utils::debug( Utils::DBGMAJOR, str ) ) *str<<"loaded "<info().descr()<<" plugin library"<info().descr() ){ Destroyer destroyer=( Destroyer )libs[i]->resolve( "destroy" ); if( destroyer ){ destroyer( pi ); pi=0; pluginUsage[i]--; if( pluginUsage[i]==0 ){ // we are no longer using any plugins from this library, so unload it delete libs[i]; libs[i]=0; std::ostream * str=0; if( Utils::debug( Utils::DBGMAJOR, str ) ) *str<<"unloaded "< PluginManager::getPluginInfo() const{ vector ret( info ); return ret; }