GnuCashew ~ GnuCash Enabled Web
GCW
AccountRegisterModel.cpp
Go to the documentation of this file.
1 #line 2 "src/Eng/AccountRegisterModel.cpp"
2 
3 #include "../Glb/gcwglobal.h"
4 #include "../Dbo/SessionGnuCash.h"
5 #include "../Dbo/Splits/Splits.h"
6 #include "../Dbo/Prefrences.h"
7 #include "../Dbo/Transactions/Manager.h"
8 #include "../Dbo/Vars/Vars.h"
9 #include "../Glb/Core.h"
10 #include "AccountRegisterModel.h"
11 
12 /*
13 ** column definitions:
14 **
15 ** 0,0 - date : transaction -> post_date
16 ** 0,1 - action : split -> action
17 ** 0,2 - description : transaction -> description
18 ** 0,3 - transfer : split -> {tx_guid 2nd split}
19 ** 0,4 - reconcile : split -> reconcile_state, reconcile_date
20 ** 0,5 - debit : split -> value_num, value_denom (+positive value)
21 ** 0,6 - credit : split -> value_num, value_denom (-negative value)
22 ** 1,2 - notes : split -> memo
23 **
24 */
25 #define COL_DATE (0)
26 #define COL_ACTION (1)
27 #define COL_DESCRIPTION (2)
28 #define COL_TRANSFER (3)
29 #define COL_RECONCILE (4)
30 #define COL_DEBIT (5)
31 #define COL_CREDIT (6)
32 #define COL_BALANCE (7)
33 #define COL_NOTES (2)
34 
36 AccountRegisterModel( const std::string & _accountGuid, bool _editable )
37 : Wt::WStandardItemModel( 0, 8 ), // 8-columns
38  m_accountGuid( _accountGuid ),
39  m_editable( _editable )
40 {
41  /*
42  ** set the lastDate to match the todays date, so when first
43  ** opening the register, the date is automatically set.
44  **
45  */
46  m_lastDate = Wt::WDate::currentDate().toString( GCW::Cfg::date_format() ).toUTF8();
47 
49 
50 #ifdef NEVER
51  dataChanged().connect( [=]( Wt::WModelIndex _index1, Wt::WModelIndex _index2 )
52  {
53  std::cout << __FILE__ << ":" << __LINE__ << " model<signal>.dataChanged()"
54  << "\n tst:" << std::string( Wt::asString( _index1.data() ) == Wt::asString( _index2.data() )? "same":"different" )
55  << "\n r1:" << _index1.row()
56  << " c1:" << _index1.column()
57  << " v1:" << Wt::asString( _index1.data() )
58  << "\n r2:" << _index2.row()
59  << " c2:" << _index2.column()
60  << " v2:" << Wt::asString( _index2.data() )
61  << std::endl;
62 
63  });
64 #endif
65 
66 #ifdef NEVER
67  itemChanged().connect( [=]( Wt::WStandardItem * _item )
68  {
69  std::cout << __FILE__ << ":" << __LINE__ << " model<signal>.itemChanged()"
70  << "\n r:" << _item-> row()
71  << " c:" << _item-> column()
72  << "\n d:" << Wt::asString( _item-> data( Wt::ItemDataRole::Display ) )
73  << "\n t:" << Wt::asString( _item-> text() )
74  << std::endl;
75 
76  });
77 #endif
78 
79 } // endGCW::Eng::AccountRegisterModel::AccountRegisterModel( const std::string & _accountGuid )
80 
81 auto
83 setAccountGuid( const std::string & _accountGuid )-> void
84 {
85  m_accountGuid = _accountGuid;
86  refreshFromDisk();
87 
88 } // endsetAccountGuid( const std::string & _accountGuid )-> void
89 
90 auto
92 setViewMode( ViewMode _viewMode )-> void
93 {
94  m_viewMode = _viewMode;
95 }
96 
97 auto
99 setDoubleLine( bool _doubleLine )-> void
100 {
101  m_doubleLine = _doubleLine;
102 }
103 
104 auto
106 isDeletable( const Wt::WModelIndex & _index )-> bool
107 {
108  /*!
109  ** If this transaction split has no guid
110  ** then it's a new row, and cannot be deleted
111  */
112  if( getSplitGuid( _index ) == "" )
113  return false;
114 
115  /*!
116  ** If this transaction split is reconciled, then it is
117  ** considered not deletable
118  */
120  transMan.loadSplit( getSplitGuid( _index ) );
121  if( transMan.thisSplit()-> isReconciled() )
122  return false;
123 
124  /*
125  ** deletable
126  */
127  return true;
128 
129 } // endisDeletable( const Wt::WModelIndex & _index )-> bool
130 
131 auto
133 isEditable( const Wt::WModelIndex & _index )-> bool
134 {
135  /*!
136  ** If this transaction split has no guid
137  ** then it's a new row, and cannot be deleted
138  */
139  if( getSplitGuid( _index ) == "" )
140  return true;
141 
142  /*!
143  ** If this transaction split is reconciled, then it is
144  ** considered not deletable
145  */
147  transMan.loadSplit( getSplitGuid( _index ) );
148  if( transMan.thisSplit()-> isReconciled() )
149  return false;
150 
151  /*
152  ** editable
153  */
154  return true;
155 
156 } // endisEditable( const Wt::WModelIndex & _index )-> bool
157 
158 auto
160 isEditable( int _row )-> bool
161 {
162  return isEditable( index( _row, 0 ) );
163 }
164 
165 auto
167 saveToDisk()-> void
168 {
169  std::cout << FUNCTION_HEADER << std::endl;
170 
171 } // endsaveToDisk()
172 
173 auto
175 getString( const Wt::WModelIndex & _index, int column )-> std::string
176 {
177  return
178  Wt::asString // convert the index.data() to a WString
179  (
180  index( _index.row(), column ) // get the index of the ACTION column
181  .data( Wt::ItemDataRole::Display ) // get the (string/display) data from it
182  )
183  .toUTF8(); // convert the WString to a std::string
184  //
185 } // endgetString( const Wt::WModelIndex & _index, int column )-> std::string
186 
187 auto
189 getDate( const Wt::WModelIndex & _index )-> Wt::WDateTime
190 {
191  auto retVal =
192  Wt::WDateTime::fromString // convert the WString to a WDateTime
193  (
194  getString( _index, COL_DATE ),
195  GCW_DATE_FORMAT_DISPLAY // use this DATE format for the conversion
196  );
197 
198  /*
199  ** set the default time-component
200  */
201  retVal.setTime( GCW_DATE_DEFAULT_TIME );
202 
203  return retVal;
204 
205 } // endgetDate( const Wt::WModelIndex & _index )-> std::string
206 
207 auto
209 getAction( const Wt::WModelIndex & _index )-> std::string
210 {
211  return getString( _index, COL_ACTION );
212 
213 } // endgetDescription( const Wt::WModelIndex & _index )-> std::string
214 
215 auto
217 getDescription( const Wt::WModelIndex & _index )-> std::string
218 {
219  return getString( _index, COL_DESCRIPTION );
220 
221 } // endgetDescription( const Wt::WModelIndex & _index )-> std::string
222 
223 auto
225 getTransferText( const Wt::WModelIndex & _index )-> std::string
226 {
227  return getString( _index, COL_TRANSFER );
228 
229 } // endgetDescription( const Wt::WModelIndex & _index )-> std::string
230 
231 auto
233 getTransferGuid( const Wt::WModelIndex & _index )-> std::string
234 {
235  return
236  GCW::Dbo::Accounts::byFullName( getTransferText( _index ) )-> guid();
237 
238 } // endgetDescription( const Wt::WModelIndex & _index )-> std::string
239 
240 auto
242 getReconcile( const Wt::WModelIndex & _index )-> std::string
243 {
244  return getString( _index, COL_RECONCILE );
245 
246 } // endgetDescription( const Wt::WModelIndex & _index )-> std::string
247 
248 auto
250 getNumeric( const Wt::WModelIndex & _index )-> GCW_NUMERIC
251 {
252  GCW_NUMERIC retVal( 0 );
253 
254  if( !_index.data( Wt::ItemDataRole::Display ).empty() )
255  retVal = GCW_NUMERIC( Wt::asString( _index.data( Wt::ItemDataRole::Display ) ).toUTF8() );
256 
257  return retVal;
258 
259 } // endgetNumeric( const Wt::WModelIndex & _index )-> GCW_NUMERIC
260 
261 auto
263 getDebit( const Wt::WModelIndex & _index )-> GCW_NUMERIC
264 {
265  return
266  getNumeric( index( _index.row(), COL_DEBIT ) );
267 
268 } // endgetDebit( const Wt::WModelIndex & _index )-> GCW_NUMERIC
269 
270 auto
272 getCredit( const Wt::WModelIndex & _index )-> GCW_NUMERIC
273 {
274  return
275  getNumeric( index( _index.row(), COL_CREDIT ) );
276 
277 } // endgetCredit( const Wt::WModelIndex & _index )-> GCW_NUMERIC
278 
279 auto
281 getValue( const Wt::WModelIndex & _index )-> GCW_NUMERIC
282 {
283  GCW_NUMERIC retVal( 0 );
284 
285  // get both values so we can determine (+) or (-)
286  auto debit = getDebit( _index );
287  auto credit = getCredit( _index );
288  retVal = debit - credit;
289 
290  return retVal;
291 
292 } // endgetValue( const Wt::WModelIndex & _index )-> GCW_NUMERIC
293 
294 auto
296 getSplitGuid( const Wt::WModelIndex & _index )-> std::string
297 {
298  return
299  Wt::asString // convert the index.data() to a WString
300  (
301  index( _index.row(), COL_DATE ) // get the index of the DATE column
302  .data( Wt::ItemDataRole::User ) // get the (string/User) data from it
303  )
304  .toUTF8(); // convert the WString to a std::string
305 
306 } // endgetSplitGuid( const Wt::WModelIndex & _index )-> std::string
307 
308 auto
310 getSplitGuid( int _row )-> std::string
311 {
312  return getSplitGuid( index( _row, COL_DATE ) );
313 
314 } // endgetSplitGuid( int _row )-> std::string
315 
316 auto
318 saveToDisk( const Wt::WModelIndex & _index )-> void
319 {
320 #ifdef NEVER
321  std::cout << __FILE__ << ":" << __LINE__ << " " << __FUNCTION__ << "(): "
322  << "\n row:" << _index.row()
323  << "\n col:" << _index.column()
324  << "\n gui:" << getSplitGuid( _index )
325  << std::endl;
326 #endif
327 
328  /*
329  ** Prepare to update everything
330  **
331  */
333 
334  /*
335  ** If we don't have a split guid, then this is a new row. It also
336  ** means we don't have a transaction, either. So, build up a whole
337  ** set of transaction-items that we'll be needing to set in these
338  ** new values.
339  **
340  */
341  auto splitGuid = getSplitGuid( _index );
342  if( splitGuid == "" )
343  {
344  /*
345  ** Create a new transaction
346  **
347  */
348  transMan.newTransaction( m_accountGuid, getTransferGuid( _index ) );
349 
350  } // endif( ..no split.. )
351 
352  /*
353  ** We have a split item, so load up the transaction associated with it as
354  ** well as the split-pair item, we will poke changes in to it accordingly below.
355  */
356  else
357  {
358  transMan.loadSplit( splitGuid );
359  }
360 
361  /*
362  ** write out the data that changed
363  */
364  switch( _index.column() )
365  {
366  case COL_DATE:
367  {
368  transMan.setDate( getDate( _index ) );
369  break;
370  }
371 
372  case COL_ACTION:
373  {
374  transMan.setAction( getAction( _index ) );
375  break;
376  }
377 
378  case COL_DESCRIPTION:
379  {
380  transMan.setDescription( getDescription( _index ) );
381  break;
382  }
383 
384  case COL_TRANSFER:
385  {
386  transMan.setTransferGuid( getTransferGuid( _index ) );
387  break;
388  }
389 
390  case COL_RECONCILE:
391  {
392  transMan.setReconcile( getReconcile( _index ) );
393  break;
394  }
395 
396  case COL_DEBIT:
397  {
398  transMan.setValue( getValue( _index ) );
399  break;
400  }
401 
402  case COL_CREDIT:
403  {
404  transMan.setValue( getValue( _index ) );
405  break;
406  }
407 
408 #ifdef NEVER
409  case COL_NOTES:
410  {
411  transMan.setNotes( getNotes( _index ) );
412  break;
413  }
414 #endif
415 
416  } // endswitch( index.column() )
417 
418 } // endsaveToDisk( const Wt::WModelIndex & _index, const Wt::cpp17::any & _value, Wt::ItemDataRole _role )-> void
419 
420 auto
422 setData( const Wt::WModelIndex & _index, const Wt::cpp17::any & _value, Wt::ItemDataRole _role )-> bool
423 {
424  /*
425  ** This is not an edit role - fast quit!
426  **
427  */
428  if( _role != Wt::ItemDataRole::Edit )
429  return false;
430 
431  /*
432  ** Nothing happening constitutes a success
433  **
434  */
435  bool retVal = true;
436 
437  /*
438  ** This compare function compares two _any_ values
439  **
440  */
441  auto _valuesMatch = []( const Wt::cpp17::any & _any1, const Wt::cpp17::any & _any2 )
442  {
443  /*
444  ** In any case, the two values must be of the same type.
445  **
446  */
447  if( _any1.type() == _any2.type() )
448  {
449 #ifdef NEVER
450  std::cout << __FILE__ << ":" << __LINE__ << " " << __FUNCTION__
451  << "\n " << typeid(Wt::WString).name()
452  << "\n " << typeid(Wt::WString).hash_code()
453  << "\n " << _any1.type().name()
454  << "\n " << _any1.type().hash_code()
455  << std::endl;
456 #endif
457 
458  if( typeid(std::string) == _any1.type() )
459  {
460  auto v1 = Wt::cpp17::any_cast< std::string >( _any1 );
461  auto v2 = Wt::cpp17::any_cast< std::string >( _any2 );
462  return v1 == v2;
463  }
464 
465  else
466  if( typeid(Wt::WString) == _any1.type() )
467  {
468  auto v1 = Wt::cpp17::any_cast< Wt::WString >( _any1 );
469  auto v2 = Wt::cpp17::any_cast< Wt::WString >( _any2 );
470  return v1 == v2;
471  }
472 
473  else
474  if( typeid(int) == _any1.type() )
475  {
476  auto v1 = Wt::cpp17::any_cast< int >( _any1 );
477  auto v2 = Wt::cpp17::any_cast< int >( _any2 );
478  return v1 == v2;
479  }
480 
481  else
482  {
483  std::cout << __FILE__ << ":" << __LINE__ << " unhandled type" << std::endl;
484  }
485 
486  }
487 
488  /*
489  ** not a match!
490  **
491  */
492  return false;
493 
494  }; // endauto _valuesMatch = []( const Wt::cpp17::any & _any1, const Wt::cpp17::any & _any2 )
495 
496  /*
497  ** Only updating if the data actually changed
498  **
499  */
500  if( !_valuesMatch( _index.data( _role ), _value ) )
501  {
502 #ifdef NEVER
503  std::cout << BREAKHEADER
504  << "\n row:" << _index.row()
505  << "\n col:" << _index.column()
506  << "\n cur:" << Wt::asString( _index.data( _role ) )
507  << "\n new:" << Wt::asString( _value )
508  << std::endl;
509 #endif
510 
511  /*
512  ** saving to the model causes _index to be updated with the new value
513  */
514  retVal = Wt::WStandardItemModel::setData( _index, _value, _role );
515 
516  saveToDisk( _index );
517 
518 // m_dirtyRows.insert( _index.row() );
519 
520  m_goneDirty.emit( _index );
521 
522  dataChanged().emit( index( _index.row(), COL_DATE ), index( _index.row(), COL_BALANCE ) );
523 
524 #ifdef NEVER
525  std::cout << BREAKFOOTER
526  << std::endl;
527 #endif
528 
529  } // endif( data( _index ) != _value )
530 
531  /*
532  ** Return success fail
533  **
534  */
535  return retVal;
536 
537 } // endsetData( const Wt::WModelIndex & _index, const Wt::cpp17::any & _value, Wt::ItemDataRole _role )-> bool
538 
539 /*!
540 ** \brief Refresh From Disk
541 **
542 ** This procedure reads from the gnucash storage source
543 ** (either postgres or sqlite) and loads all of the
544 ** transactions and their associated splits in to the
545 ** model suitable for editing within an automatic
546 ** table view.
547 **
548 */
549 auto
551 refreshFromDisk()-> void
552 {
553  if( m_accountGuid == "" )
554  return;
555 
556  /*
557  ** Signal the model is about to be reset.
558  **
559  */
560  layoutAboutToBeChanged().emit();
561 
562  /*!
563  ** \par Model Columns
564  ** \code
565  ** col name notes
566  ** -----+---------------------+----------------------
567  ** 0 date
568  ** 1 num (check number)
569  ** 2 description
570  ** 3 account / transfer
571  ** 4 reconciliation
572  ** 5 debit
573  ** 6 credit
574  ** 7 balance r/o (computed)
575  ** \endcode
576  **
577  */
578  auto _addColumn = [&]( RowItem & columns, auto _value )
579  {
580  auto item = std::make_unique< Wt::WStandardItem >( _value );
581 
582  item-> setToolTip( _value );
583 
584  auto retVal = item.get();
585  columns.push_back( std::move( item ) );
586  return retVal;
587  };
588 
589  /*!
590  ** Before refreshing from disk, the entire contents of the
591  ** model are cleared, so it is important to make sure anything
592  ** to be saved from the model should be done first.
593  **
594  */
595  clear();
596 
597  /*
598  ** Get the prefrence item that can inform us about prefrences
599  ** to be applied to this model.
600  **
601  */
602  auto prefrenceItem = GCW::Dbo::Prefrences::get();
603 
604  /*
605  ** Get an account item loaded. This is the account that _is_ this
606  ** register.
607  **
608  */
609  auto registerAccountItem = GCW::Dbo::Accounts::byGuid( m_accountGuid );
610 
611 #ifdef NEVER
612  std::cout << __FILE__ << ":" << __LINE__
613  << " guid:" << registerAccountItem-> guid()
614  << " name:" << registerAccountItem-> name()
615  << " dbcr:" << static_cast<int>( registerAccountItem-> accountDrCr() )
616  << " type:" << static_cast<int>( registerAccountItem-> accountType() )
617  << " typn:" << registerAccountItem-> accountTypeName()
618  << std::endl;
619 #endif
620 
621  /*!
622  ** In order to produce a proper 'register' of items, it is important
623  ** to load the data from the 'splits' side of the transaction rather
624  ** than the transaction itself.
625  **
626  ** Note that when the splits are loaded based on the account ID, they
627  ** are returned in a std::vector(sorted_by_date) that is sorted based on
628  ** the transction date. This chosen sort method insures that the
629  ** running balance can be accurately calculated on the fly, since each
630  ** item is pulled from the vector in a sorted order, and the running
631  ** balance is included in the model row. The user can sort the user
632  ** interface later and still have the line-item-balance remain accurate.
633  **
634  */
635  auto splitItems = GCW::Dbo::Splits::byAccount( m_accountGuid );
636 
637  /*!
638  ** Each item is processed from the vector in sequential order.
639  ** In this process we grab the contents of the split, and
640  ** generate a model item row containing all of the column values.
641  ** Maintain a running balance as we go along to keep the balance
642  ** reflected within the view. The result is a multi-column row
643  ** item that is added to the model. This allows the model to be
644  ** subsequently re-sorted or subset-extracted without affecting
645  ** the running balances and so forth.
646  **
647  */
648  GCW_NUMERIC runningBalance( 0 );
649  for( auto splitItem : splitItems )
650  {
651  /*
652  ** Start out read-only.
653  **
654  */
655  bool editable = false;
656 
657  /*!
658  ** From the initial split item, we get a handle on the transaction,
659  ** and then load all of the other splits associated with this
660  ** transaction.
661  **
662  */
663  auto transactionItem = GCW::Dbo::Transactions ::byGuid ( splitItem-> tx_guid () );
664  auto transactionSplits = GCW::Dbo::Splits ::bySplit ( splitItem-> guid () );
665 
666  /*
667  ** The first row comprises all of the basic account register information, such
668  ** as the transaction date, and target account and amounts and so forth. It is
669  ** the first line in the basic one-line ledger.
670  **
671  */
672  auto _append1stRow = [&]()
673  {
674  /*
675  ** Prepare a row of columns.
676  **
677  ** Values of the transaction appear either in the Debit column (positive-Left)
678  ** or in the Credit column (negative-Right) depending on if they are positive
679  ** or negative. The 'decimal.h' library is used to perform the
680  ** arithmetic to prevent the floating point math problems.
681  **
682  ** The balance on the transaction is computed on-the-fly, which
683  ** makes clear the importance of having the initial vector of splits
684  ** to appear in the correct chronological order. It also makes clear that
685  ** if the 'view' is re-sorted on anything other than the date column, the
686  ** balance column will ~not~ be recomputed.
687  **
688  */
689  RowItem columns;
690  ColItem post_date = nullptr;
691  ColItem num = nullptr;
692  ColItem description = nullptr;
693  ColItem account = nullptr;
694  ColItem reconcile = nullptr;
695  ColItem debit = nullptr;
696  ColItem credit = nullptr;
697  ColItem balance = nullptr; // (computed on-the-fly)
698 
699  /*!
700  ** \note The post_date column (col-0) also carries with it the guid of the split
701  ** item itself, so that the originating split can be located from the table
702  ** view. The guid can be accessed by;
703  **
704  ** \code
705  ** Wt::WString splitRowGuid = Wt::asString( standardItem.data( Wt::ItemDataRole::User ) )
706  ** \endcode
707  **
708  ** \sa getSplitGuid
709  **
710  */
711  post_date = _addColumn( columns, transactionItem-> post_date_as_date().toString( GCW::Cfg::date_format() ) );
712  post_date-> setData( splitItem-> guid(), Wt::ItemDataRole::User );
713  post_date-> setData( splitItem , Wt::ItemDataRole::User + 1 );
714 
715  auto tip =
716  Wt::WString
717  (
718  "row: {1}\n"
719  "acg: {2}\n"
720  "spg: {3}\n"
721  )
722  .arg( rowCount() )
723  .arg( m_accountGuid )
724  .arg( splitItem-> guid() )
725  ;
726  post_date-> setToolTip( tip );
727 
728  /*!
729  ** The 'num' column is a simple text-column.
730  **
731  */
732  num = _addColumn( columns, transactionItem-> num() );
733 
734  /*!
735  ** The 'description' column is a simple text-column.
736  **
737  */
738  description = _addColumn( columns, transactionItem-> description() );
739 
740  /*!
741  ** The 'account' text depends on the
742  ** target account defined in the split. There are three
743  ** possibilities here;
744  **
745  ** -# no splits... this shows up as an <b>'imbalance'</b> (this is an error condition)
746  ** -# 1 split... this just shows the split account on the same single line
747  ** -# >1 split... this is more than one target account, so just indicate 'split'
748  **
749  */
750  switch( transactionSplits.size() )
751  {
752  /*!
753  ** \par Imbalance
754  ** This is actually a problem... We don't have another split, and
755  ** according to 'generally accepted accounting practices' we
756  ** should! So, just plop an 'imbalance' indicator in the view.
757  ** A style-class is also applied to the item to allow the rendering
758  ** in the view to highlight this problem.
759  **
760  */
761  case 0:
762  {
763  account = _addColumn( columns, TR("gcw.AccountRegister.account.imbalanceUSD") ); // account
764  account-> setStyleClass( "errval" );
765  account-> setToolTip( TR("gcw.AccountRegister.account.imbalanceUSD.toolTip") );
766  break;
767  }
768 
769  /*!
770  ** \par Normal Split
771  ** This is a straight and simple 1:1 split transaction, so we can pull
772  ** the account name from the other side of the split and pop that in
773  ** to the model directly.
774  **
775  */
776  case 1:
777  {
778  auto txSplitItem = *transactionSplits.begin();
779  auto splitAccountItem = GCW::Dbo::Accounts::byGuid( txSplitItem-> account_guid() );
780 
781  // yes, we have one account item
782  if( splitAccountItem )
783  {
784  account = _addColumn( columns, GCW::Dbo::Accounts::fullName( splitAccountItem-> guid() ) );
785 
786  auto tip =
787  Wt::WString
788  (
789  "spa:{1}\n"
790  "txi:{2}\n"
791  )
792  .arg( splitAccountItem-> guid() )
793  .arg( txSplitItem-> guid() )
794  ;
795  account-> setToolTip( tip );
796  }
797 
798  // no, we don't have an account item
799  else
800  {
801  /*!
802  ** \par Another Imbalance
803  ** This is another problem... We have another split, but the account
804  ** we are split-to doesn't exist. This is a problem and should not
805  ** happen and represents an error in the database. This means the
806  ** account containing this guid nolonger exists. That should never
807  ** happen.
808  **
809  */
810  account = _addColumn( columns, TR("gcw.AccountRegister.account.imbalanceUSD") );
811  account-> setStyleClass( "errval" );
812 
813  auto toolTip =
814  Wt::WString("target guid:{1}\n{2}")
815  .arg( txSplitItem-> account_guid() )
816  .arg( TR("gcw.AccountRegister.account.invalidTarget.toolTip") )
817  .toUTF8()
818  ;
819 
820  account-> setToolTip( toolTip );
821 
822  } // endelse no account item
823 
824  break;
825 
826  } // endcase 1:
827 
828  /*!
829  ** \par Multi-Split
830  ** When we have more than one split then we cannot display
831  ** all of the split accounts on just one line, so just pop
832  ** a message that indicates that we're in a multisplit
833  ** transaction.
834  **
835  */
836  default:
837  {
838  account = _addColumn( columns, TR("gcw.AccountRegister.account.multisplit") ); // account
839  }
840 
841  } // endswitch( transactionSplits.size() )
842 
843  /*!
844  ** The reconcile column is a simple text-column.
845  **
846  */
847  reconcile = _addColumn( columns, splitItem-> reconcile_state() ); // Reconciled
848 
849  /*!
850  ** \par Balance Computation Notes
851  **
852  ** There are two 'types' of accounts; Debit/Credit. Gnucash
853  ** stores split information as a single value that is positive
854  ** or negative. If the value is positive, then it is posted
855  ** to the debit (left) column. If the value is negative, it
856  ** is posted to credit (right) column.
857  **
858  ** Depending on the account type (debit/credit), that value is
859  ** then either 'added' or 'subtracted' from the account balance.
860  ** If this is a 'credit' account, then the value is subtracted,
861  ** and if it is a debit account, the value is added.
862  **
863  ** Therefore, if this is a credit account, such as a credit card,
864  ** then a 'positive' value, posted to the debit column, would
865  ** 'decrease' the balance in that account. Therefore, the value,
866  ** being positive, is 'subtracted' from the running balance. If
867  ** the value were negative, it would be posted to the credit
868  ** column, and again would be 'subtracted' from the running
869  ** balance, and a negative value being subtracted from a value
870  ** causes the result to 'increase'.
871  **
872  ** If this is a debit account, such as a bank checking account, a
873  ** 'positive' value, posted to the debit column (again), would
874  ** 'increase' the balance in that account.
875  **
876  ** So, that's the funky GAAP math done here.
877  **
878  ** What follows is a pretty good explanation of the debit/credit
879  ** stuff;
880  **
881  ** >>>>>>>>>>>>>>>
882  ** Debit/Credit is just Left/Right.
883  ** Maybe this will help
884  **
885  ** \par The Accounting Equation:
886  ** \code
887  ** Assets - Liabilities = Equity
888  ** \endcode
889  **
890  ** \par let's make all terms 'positive'
891  ** \code
892  ** Assets = Liabilities + Equity
893  ** \endcode
894  **
895  ** \par now, we'll split off a subset of Equity
896  ** \code
897  ** Assets = Liabilities + Equity + Retained Earnings
898  ** \endcode
899  **
900  ** \par now, we'll substitute temporary accounts for Retained Earnings)
901  ** \code
902  ** Assets = Liabilities + Equity + (Income - Expenses)
903  ** \endcode
904  **
905  ** (now, we'll once again, make all terms 'positive')
906  ** Assets + Expenses = Liabilities + Equity + Income
907  **
908  ** And there, you have the full Accounting Equation with the five major account types that GnuCash uses.
909  **
910  ** In double-entry accounting, ALL transactions are in the form of:
911  ** Debit = Credit
912  ** Left = Right
913  **
914  ** The 'Debit' accounts (those that are normally (positive) a Debit balance, and increase with a Debit, decrease with a Credit) are on the left of the equation:
915  ** Assets
916  ** Expenses
917  **
918  ** The 'Credit' accounts (those that are normally (positive) a Credit balance, and increase with a Credit, decrease with a Debit) are those on the right of the equation:
919  ** Liabilities
920  ** Equity
921  ** Income
922  **
923  ** A negative balance in any account would indicate either an entry error or a contra-balance situation. (rare for individuals)
924  **
925  ** You can move funds from the left to the right, or vice versa, or between any accounts or types on the same side of the equation. (I will use the abbreviations Dr. and Cr. here)
926  ** Most texts will write transactions Debit first, then Credit as shown below. The amounts are not shown, because they *must* be equal.
927  **
928  ** \par Example Left to Right - Asset to Liability (paying down a debt)
929  ** \code
930  ** Dr. Liabilities:Loan
931  ** Cr. Assets:Cash
932  ** result: decreased Loan owed, decreased Cash on hand, Assets decreased, Liabilities decreased - equation still in balance
933  ** \endcode
934  **
935  ** \par Example Right to Left - Income to Asset (receipt of income)
936  ** \code
937  ** Dr. Assets:Cash
938  ** Cr. Income:Salary
939  ** result: increased Cash on hand, increased Salary earned, Assets increased, Income increased - equation still in balance
940  ** \endcode
941  **
942  ** \par Example Left to Left(same type) - Asset to Asset (buying land outright)
943  ** \code
944  ** Dr. Assets:Land
945  ** Cr. Assets:Cash
946  ** result: increased Land owned, decreased Cash on hand, Assets shifted - equation still in balance
947  ** \endcode
948  **
949  ** \par Example Left to Left(different type) - Asset to Expense (buying groceries)
950  ** \code
951  ** Dr. Expenses:Food
952  ** Cr. Assets:Cash
953  ** result: increased Food expense, decreased Cash on hand, Expenses increased, Assets decreased - equation still in balance
954  ** \endcode
955  **
956  ** \par Example Right to Right(same type) - Liability to Liability (paying down a loan with a credit card)
957  ** \code
958  ** Dr. Liabilities:Loan
959  ** Cr. Liabilities:Credit Card
960  ** result: decreased Loan owed, increased Credit Card owed, Liabilities shifted - equation still in balance
961  ** \endcode
962  **
963  ** \par Example Right to Right(different type) - Equity to Liability (recognition of dividends to be paid - business transaction)
964  ** \code
965  ** Dr. Equity:Retained Earnings
966  ** Cr. Liabilities:Dividends Payable
967  ** result: decreased Retained Earnings, increased Dividends owed to shareholders, Equity decreased, Liability increased - equation remains in balance.
968  ** \endcode
969  **
970  ** *it is rare and unusual for an individual to shift Equity to Liabilities and vice versa. Forgiveness of Debt may in some jurisdictions be a transfer from Liabilities to Income.
971  **
972  ** \par Original Post
973  ** \ref https://lists.gnucash.org/pipermail/gnucash-user/2023-October/109219.html
974  ** \par Accounting Basics
975  ** \ref https://www.gnucash.org/docs/v5/C/gnucash-guide/basics-accounting1.html
976  ** >>>>>>>>>>>>>>>
977  **
978  */
979  bool invert = false;
980  switch( prefrenceItem.reverseBalanceAccounts() )
981  {
983  {
984 #ifdef NO_DRCR_YET
985  if( registerAccountItem-> accountType() == GCW::Dbo::Account::Type::INCOME
986  || registerAccountItem-> accountType() == GCW::Dbo::Account::Type::EXPENSE
987  )
988  {
989  invert = true; // math inverted
990  }
991 #endif
992  break;
993  }
994 
996  {
997 #ifdef NO_DRCR_YET
998  if( registerAccountItem-> accountDrCr() == GCW::Dbo::Account::DrCr::CREDIT )
999  {
1000  invert = true; // math inverted
1001  }
1002 #endif
1003  break;
1004  }
1005 
1006  } // endswitch( prefrenceItem.reverseBalanceAccounts() )
1007 
1008  /*
1009  ** Compute the running balance.
1010  **
1011  */
1012  runningBalance += splitItem-> value( invert );
1013 
1014  /*!
1015  ** \todo Add up the static running accumulators
1016  **
1017  */
1018  m_present += splitItem-> value( invert );
1019  // m_future ;
1020  // m_cleared ;
1021  // m_reconciled ;
1022  // m_projected ;
1023 
1024  /*
1025  ** if the value is positive, we post it to the debit (left) column.
1026  */
1027  if( splitItem-> value() > 0 )
1028  {
1029  debit = _addColumn( columns, splitItem-> valueAsString() );
1030  credit = _addColumn( columns, "" );
1031 
1032  /// \bug may not need to store GCW_NUMERIC on the AccountRegisterModel items. it is redundant and we're not using the values and updates from the delegates don't update these values.
1033  debit -> setData( splitItem-> value(), Wt::ItemDataRole::User );
1034  credit-> setData( GCW_NUMERIC(0) , Wt::ItemDataRole::User );
1035  }
1036 
1037  /*
1038  ** if the value is negative, we post it to the credit (right) column.
1039  ** however, we invert the value in this column, so that it does not
1040  ** carry the (-) leading minus sign... all the numbers we enter are
1041  ** positive... only the 'balance' column can show negative numbers.
1042  */
1043  else
1044  if( splitItem-> value() < 0 )
1045  {
1046  debit = _addColumn( columns, "" );
1047  credit = _addColumn( columns, splitItem-> valueAsString(true) );
1048 
1049  debit -> setData( GCW_NUMERIC(0) , Wt::ItemDataRole::User );
1050  credit-> setData( splitItem-> value(), Wt::ItemDataRole::User );
1051  }
1052 
1053  /*
1054  ** if the value is zero, we make sure both columns are blank.
1055  */
1056  else
1057  if( splitItem-> value() == 0 )
1058  {
1059  debit = _addColumn( columns, "" );
1060  credit = _addColumn( columns, "" );
1061 
1062  debit -> setData( GCW_NUMERIC(0), Wt::ItemDataRole::User );
1063  credit-> setData( GCW_NUMERIC(0), Wt::ItemDataRole::User );
1064  }
1065 
1066  /*
1067  ** Poke the balance in
1068  **
1069  */
1070  balance =
1071  _addColumn
1072  (
1073  columns,
1074  Wt::WString( "{1}" )
1075  .arg( toString( runningBalance, GCW::Cfg::decimal_format() ) )
1076  );
1077  balance-> setData( runningBalance, Wt::ItemDataRole::User );
1078 
1079  /*
1080  ** If the balance hit negative, highlight the number with a bit
1081  ** of bad-news-red.
1082  **
1083  */
1084  if( runningBalance < 0 )
1085  {
1086  if( prefrenceItem.accountRegisterHighlight( GCW::Dbo::Prefrences::AccountRegisterHighlight::NEGVAL_EXTRA ) )
1087  {
1088  post_date -> setStyleClass( "negval" );
1089  num -> setStyleClass( "negval" );
1090  description -> setStyleClass( "negval" );
1091  account -> setStyleClass( "negval" );
1092  reconcile -> setStyleClass( "negval" );
1093  debit -> setStyleClass( "negval" );
1094  credit -> setStyleClass( "negval" );
1095  }
1096 
1097  if( prefrenceItem.accountRegisterHighlight( GCW::Dbo::Prefrences::AccountRegisterHighlight::NORMAL ) )
1098  {
1099  balance -> setStyleClass( "negval" );
1100  }
1101  }
1102 
1103  /*
1104  ** If this model is editable, then check the reconciliation
1105  ** state. If the split has already been reconciled then
1106  ** we really don't want the user messing around with it.
1107  **
1108  */
1109  if( m_editable )
1110  {
1111  if( splitItem-> reconcile_state() == "y" )
1112  {
1113  editable = false;
1114  }
1115  else
1116  {
1117  editable = true;
1118  }
1119  }
1120 
1121  /*
1122  ** If this item can be edited then unlock everything.
1123  **
1124  ** TODO: note, it would be possible here to do things
1125  ** like, if the transaction has been reconciled,
1126  ** allow for the description to be edited, but
1127  ** perhaps not the date or amounts... that could
1128  ** be handy.
1129  **
1130  */
1131  if( editable )
1132  {
1133  post_date -> setFlags( Wt::ItemFlag::Editable );
1134  num -> setFlags( Wt::ItemFlag::Editable );
1135  description -> setFlags( Wt::ItemFlag::Editable );
1136  account -> setFlags( Wt::ItemFlag::Editable );
1137  reconcile -> setFlags( Wt::ItemFlag::Editable );
1138  debit -> setFlags( Wt::ItemFlag::Editable );
1139  credit -> setFlags( Wt::ItemFlag::Editable );
1140  balance -> setFlags( Wt::ItemFlag::Editable );
1141  }
1142 
1143  /*
1144  ** Add the row to the model
1145  **
1146  */
1147  appendRow( std::move( columns ) );
1148 
1149  }; // endauto _append1stRow = [&]()
1150 
1151  /*
1152  ** The second row represents the second line in a "double-line" view
1153  ** of the ledger.
1154  **
1155  */
1156  auto _append2ndRow = [&]()
1157  {
1158  /*
1159  ** Prepare a row of columns.
1160  **
1161  */
1162  RowItem columns;
1163 
1164  /*!
1165  ** Load everything blank (except for the memo)
1166  */
1167  _addColumn( columns, "" ); // date
1168  _addColumn( columns, "" ); // action
1169  auto memo = _addColumn( columns, splitItem-> memo() );
1170  _addColumn( columns, "" ); // account
1171  _addColumn( columns, "" ); // reconcile
1172  _addColumn( columns, "" ); // debit
1173  _addColumn( columns, "" ); // credit
1174  _addColumn( columns, "" ); // balance
1175 
1176  /*
1177  ** If this item can be edited then unlock everything.
1178  */
1179  if( editable )
1180  {
1181  memo -> setFlags( Wt::ItemFlag::Editable );
1182  }
1183 
1184  /*
1185  ** Add the row to the model
1186  */
1187  appendRow( std::move( columns ) );
1188 
1189  }; // endauto _append2ndRow = [&]()
1190 
1191  _append1stRow();
1192 
1193  if( doubleLine() )
1194  _append2ndRow();
1195 
1196  } // endfor( auto splitItem : splitItems )
1197 
1198  /*!
1199  ** After all the split items are loaded, an additional ~blank~ item
1200  ** is included at the end of the vector, for coding new entries.
1201  **
1202  */
1203  if( m_editable )
1204  {
1205  /*
1206  ** Create a row with blank values
1207  */
1208  RowItem columns;
1209  auto post_date = _addColumn( columns, m_lastDate ); // Date
1210  post_date-> setFlags( Wt::ItemFlag::Editable );
1211 
1212  _addColumn( columns, "" )-> setFlags( Wt::ItemFlag::Editable ); // Num
1213  _addColumn( columns, "" )-> setFlags( Wt::ItemFlag::Editable ); // Memo
1214  _addColumn( columns, "" )-> setFlags( Wt::ItemFlag::Editable ); // Account
1215  _addColumn( columns, "n" )-> setFlags( Wt::ItemFlag::Editable ); // R
1216  _addColumn( columns, "" )-> setFlags( Wt::ItemFlag::Editable ); // Deposit
1217  _addColumn( columns, "" )-> setFlags( Wt::ItemFlag::Editable ); // Withdrawal
1218  _addColumn( columns, "" )-> setFlags( Wt::ItemFlag::Editable ); // Balance
1219  appendRow( std::move( columns ) ) ;
1220 
1221  } // endif( m_editable )
1222 
1223  /*!
1224  ** poke all the header labels in. Note that some of the labels change
1225  ** depending on the account debit/credit type. We get those from the
1226  ** accountDef.
1227  **
1228  ** \bug Needs work
1229  ** this is modified a bit to allow for a default account def.
1230  ** this is necessary since it is possible to ask for an account
1231  ** register that is not (yet) associated to an account... this
1232  ** can happen in the BillPay module when setting up a new
1233  ** account for bill-pay functions. (kind of sloppy doing it here)
1234  ** The first item at(0) represents the default-register settings,
1235  ** suitable for any register view.
1236  **
1237  */
1239 #ifdef NO_DRCR_YET
1240  if( registerAccountItem )
1241  accountDef = registerAccountItem-> accountDef();
1242 #endif
1243 
1244  /*!
1245  ** \anchor account_type_labels
1246  */
1247  int col = 0;
1248  setHeaderData( col++, TR( "gcw.AccountRegister.column.date" ) );
1249  setHeaderData( col++, TR( "gcw.AccountRegister.column.num" ) );
1250  setHeaderData( col++, TR( "gcw.AccountRegister.column.memo" ) );
1251  setHeaderData( col++, TR( "gcw.AccountRegister.column." + accountDef.colAccount ) );
1252  setHeaderData( col++, TR( "gcw.AccountRegister.column.reconcile" ) );
1253  setHeaderData( col++, TR( "gcw.AccountRegister.column." + accountDef.colDr ) );
1254  setHeaderData( col++, TR( "gcw.AccountRegister.column." + accountDef.colCr ) );
1255  setHeaderData( col++, TR( "gcw.AccountRegister.column.balance" ) );
1256 
1257  /*
1258  ** Let the rest of the world know the model changed.
1259  **
1260  */
1261  layoutChanged().emit();
1262 
1263 } // endrefreshFromDisk()-> void
1264 
1265 auto
1267 makeRow( const std::string & _splitGuid )-> GCW::Eng::AccountRegisterModel::RowItem
1268 {
1269  RowItem rowItem;
1270 
1271  return rowItem;
1272 
1273 } // endmakeRow( const std::string & _splitGuid )-> GCW::Eng::AccountRegisterModel::RowItem
1274 
1275 auto
1277 suggestionsFromColumn( int _column ) const-> std::set< std::string >
1278 {
1279  /*
1280  ** First, make a set of unique values.
1281  **
1282  */
1283  std::set< std::string > retVal;
1284  for( int row=0; row< rowCount(); row++ )
1285  retVal.insert( Wt::asString( item( row, _column )-> text() ).toUTF8() );
1286 
1287  return retVal;
1288 
1289 } // endsuggestionsFromColumn( int _column ) const-> std::set< std::string >
1290 
1291 
#define COL_DATE
#define COL_RECONCILE
#define COL_CREDIT
#define COL_TRANSFER
#define COL_BALANCE
#define COL_NOTES
#define COL_DEBIT
#define COL_DESCRIPTION
#define COL_ACTION
Transaction Manager.
Definition: Manager.h:30
auto setReconcile(const std::string &_value) -> void
Definition: Manager.cpp:205
auto setDate(const Wt::WDateTime &_value) -> void
Definition: Manager.cpp:161
auto newTransaction(const std::string &_accountGuid1, const std::string &_accountGuid2) -> void
New Transaction.
Definition: Manager.cpp:20
auto setValue(GCW_NUMERIC _value) -> void
Definition: Manager.cpp:230
auto loadSplit(const std::string &_splitGuid) -> void
Set Split.
Definition: Manager.cpp:92
auto setNotes(const std::string &_value) -> void
Definition: Manager.cpp:240
auto setAction(const std::string &_value) -> void
Set Action.
Definition: Manager.cpp:170
auto setTransferGuid(const std::string &_value) -> void
Set Transfer GUID.
Definition: Manager.cpp:196
auto thisSplit() const -> GCW::Dbo::Splits::Item::Ptr
Definition: Manager.cpp:137
auto setDescription(const std::string &_value) -> void
Definition: Manager.cpp:187
auto getCredit(const Wt::WModelIndex &_index) -> GCW_NUMERIC
Get Credit value.
auto getDescription(const Wt::WModelIndex &_index) -> std::string
Get Description.
AccountRegisterModel(const std::string &_accountGuid="", bool _editable=true)
ctor
auto makeRow(const std::string &_splitGuid) -> RowItem
auto getTransferText(const Wt::WModelIndex &_index) -> std::string
Get Transfer Account Text.
auto setAccountGuid(const std::string &_accountGuid) -> void
auto setDoubleLine(bool _doubleLine) -> void
auto getValue(const Wt::WModelIndex &_index) -> GCW_NUMERIC
Get Value (positive or negative)
auto suggestionsFromColumn(int _column) const -> std::set< std::string >
Column Suggestions.
auto setData(const Wt::WModelIndex &_index, const Wt::cpp17::any &_value, Wt::ItemDataRole _role) -> bool
auto setViewMode(ViewMode _viewMode) -> void
auto getReconcile(const Wt::WModelIndex &_index) -> std::string
Get Reconciliation.
auto getDate(const Wt::WModelIndex &_index) -> Wt::WDateTime
Get Date from the index.
auto getString(const Wt::WModelIndex &_index, int column) -> std::string
auto isDeletable(const Wt::WModelIndex &_index) -> bool
Is Read Only.
auto getSplitGuid(const Wt::WModelIndex &_index) -> std::string
Get GUID from row.
auto getTransferGuid(const Wt::WModelIndex &_index) -> std::string
Get Transfer Account GUID.
auto refreshFromDisk() -> void
Refresh From Disk.
std::vector< std::unique_ptr< Wt::WStandardItem > > RowItem
auto getAction(const Wt::WModelIndex &_index) -> std::string
Get Action.
auto getDebit(const Wt::WModelIndex &_index) -> GCW_NUMERIC
Get Debit value.
auto getNumeric(const Wt::WModelIndex &_index) -> GCW_NUMERIC
Get numeric value.
#define TR(X)
Definition: define.h:17
#define FUNCTION_HEADER
Definition: gcwglobal.h:39
#define BREAKHEADER
Definition: gcwglobal.h:42
#define BREAKFOOTER
Definition: gcwglobal.h:43
#define GCW_DATE_DEFAULT_TIME
Default Time.
Definition: gcwglobal.h:24
#define GCW_DATE_FORMAT_DISPLAY
Definition: gcwglobal.h:13
#define GCW_NUMERIC
Internal Numeric Type.
Definition: gcwglobal.h:37
std::string date_format()
Date Format Specifier.
Definition: GnuCashew.cpp:27
DECIMAL::decimal_format decimal_format()
Decimal Format Specifier.
Definition: GnuCashew.cpp:21
const Wt::WFormModel::Field name
Definition: Accounts.cpp:47
const Wt::WFormModel::Field guid
Definition: Accounts.cpp:46
const Wt::WFormModel::Field description
Definition: Accounts.cpp:54
const std::vector< AccountDef_t > s_accountDefs
Definition: Accounts.cpp:20
auto byFullName(const std::string &_fullName) -> Item::Ptr
Load Account by 'full name' with ':' account separator.
Definition: Accounts.cpp:188
auto byGuid(const std::string &_guid) -> Item::Ptr
Load Account by GUID.
Definition: Accounts.cpp:162
auto fullName(const std::string &_guid) -> std::string
Account Fullname via GUID.
Definition: Accounts.cpp:282
@ INCOME_EXPENSE
income and expense account balances are reversed
@ CREDIT
credit account balances are reversed
@ NORMAL
normal handling - neg values are red
@ NEGVAL_EXTRA
extra handling - neg values are gold-background-full-line
auto get() -> GCW::Dbo::Prefrences::Item
Definition: Prefrences.cpp:19
auto bySplit(const std::string &_splitGuid) -> Item::Vector
Load Splits by Split.
Definition: Splits.cpp:166
auto byAccount(const std::string &_accountGuid) -> Item::Vector
Load Splits by Account.
Definition: Splits.cpp:129
std::string asString(Status _status)
Get Status as String.
Definition: Status.cpp:7
auto toString(int _value) -> std::string
Convert Integer to String.
Definition: BillPay.cpp:41
Definition: GncLock.h:6
std::string colAccount
a printable 'label' for the 'account' column in the registers
Definition: Definition.h:142
std::string colDr
a printable 'label' for the 'debit' column in the registers
Definition: Definition.h:145
std::string colCr
a printable 'label' for the 'credit' column in the registers
Definition: Definition.h:148