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