GnuCashew ~ Web Application compatible with GnuCash sql data files.
GCW
Loading...
Searching...
No Matches
SummaryWidget.cpp
Go to the documentation of this file.
1#line 2 "src/Gui/BillPay/SummaryWidget.cpp"
2
3#include <fstream>
4#include <filesystem>
5
6#include <Wt/WMenuItem.h>
7#include <Wt/WVBoxLayout.h>
8#include <Wt/WMessageBox.h>
9
10#include "../../Eng/AccountComboModel.h"
11#include "../../Glb/Core.h"
12#include "BillPay.h"
13
14namespace {
15
16/*
17** this produces a 'ordinal suffix' which is a thing
18** that is attached to the end of a number as follows;
19** 1st
20** 2nd
21** 3rd
22** 4th
23**
24*/
25auto ordinalSuffix( signed int _number )-> std::string
26{
27 /*
28 ** must be greater than zero to have a suffix
29 */
30 if( _number > 0 )
31 {
32 /*
33 ** edge detectors
34 */
35 int lastTwo = _number % 100;
36 int lastOne = _number % 10;
37
38 /*
39 ** these are straight forward 'th' for the special case
40 ** of 11, 12, and 13.
41 */
42 if( lastTwo >= 11
43 && lastTwo <= 13
44 )
45 return "th";
46
47 /*
48 ** switch on 1, 2 or 3 accordingly
49 */
50 switch( lastOne )
51 {
52 case 1: return "st";
53 case 2: return "nd";
54 case 3: return "rd";
55 default: return "th";
56
57 } // endswitch()
58
59 } // endif( _number > 0 )
60
61 /*
62 ** no suffix
63 */
64 return "";
65
66} // endauto ordinalSuffix( signed int number )-> std::string
67
68} // endnamespace {
69
70
73: Wt::WContainerWidget()
74{
75 addStyleClass( "SummaryWidget" );
76
77 m_title = addWidget( std::make_unique< Wt::WText >() );
78 m_title-> setStyleClass( "SummaryTitle" );
79 m_table = addWidget( std::make_unique< Wt::WTable >() );
80 m_table-> setStyleClass( "SummaryTable" );
81
82} // endSummaryWidget( const std::string & _accountGuid )
83
84
85auto
87showSummaryDetail( bool _state )-> void
88{
89 m_showDetail = _state;
90
91 refreshReport();
92
93} // endshowSummaryDetail( bool _state )-> void
94
95
96auto
98setDate( int _month, int _year )-> void
99{
100 m_month = _month;
101 m_year = _year;
102
103 refreshReport();
104
105} // endsetDate( int _month, int _year )-> void
106
107
108auto
110refreshReport() -> void
111{
112 /*
113 ** post the month we're in
114 */
115 m_title->
116 setText
117 (
118 Wt::WString( TR("gcw.billPay.lbl.selectedMonth") )
119 .arg( TR("gcw.billPay.ttp." + toString( m_month ) ) )
120 .arg( toString( m_year ) )
121 );
122
123 generateReport( m_table, m_showDetail );
124
125 outputReport();
126
127} // endrefreshReport() -> void
128
129auto
131generateReport( Wt::WTable * _table, bool _showDetail ) -> void
132{
133 /*
134 ** open a transaction, helps all the queries to run faster
135 */
136 Wt::Dbo::Transaction t( GCW::app()-> gnucashew_session() );
137
138 /*
139 ** reset the report
140 */
141 auto rowCount = _table-> rowCount();
142 _table-> clear();
143
144 _table-> setAttributeValue( "updated", Wt::WDateTime::currentDateTime().toString() );
145
146 /*
147 ** gather up all the payment splits and process them
148 ** in to the report
149 */
150 Splits splits( m_month, m_year );
151 int row = 0;
152 std::vector< DayTotal_t > dayTotals;
153
154 for( auto payFromAcct : splits.payFroms() )
155 {
156 /*
157 ** all payFromDay
158 */
159 for( auto payFromDay : splits.payFromDays( payFromAcct ) )
160 {
161 /*
162 ** put up the what-day-paid title
163 ** example: "1st ~ 4350-Mark"
164 */
165 auto acctDay =
166 Wt::WString("<span style=\"border-bottom:1px solid black;\">{1}<sup>{2}</sup> ~ {3}</span>")
167 .arg( payFromDay )
168 .arg( ordinalSuffix( payFromDay ) )
169 .arg( payFromAcct )
170 .toUTF8()
171 ;
172
173 _table-> elementAt( row, 0 )-> addNew< Wt::WText >( acctDay );
174 _table-> elementAt( row, 0 )-> setStyleClass( "acctDay" );
175 _table-> elementAt( row, 0 )-> setColumnSpan( 2 );
176 row++;
177
178 /*
179 ** loop through the splits for this pay-from-day and post them to the table
180 */
181 GCW_NUMERIC subTotal(0);
183 for( auto paymentSplit : splits.paymentSplits( payFromAcct, payFromDay ) )
184 {
185 auto splitItem = GCW::Dbo:: Splits ::byGuid( paymentSplit );
186 acctItem = GCW::Dbo:: Accounts ::byGuid( splitItem-> account_guid() );
187 auto txItem = GCW::Dbo:: Transactions ::byGuid( splitItem-> tx_guid() );
188
189 _table-> elementAt( row, 0 )-> addNew< Wt::WText >( splitItem -> valueAsString( true ) );
190 _table-> elementAt( row, 1 )-> addNew< Wt::WText >( txItem -> description() );
191
192 if( _showDetail )
193 {
194 auto otherSplit = GCW::Dbo::Splits::bySplitExcept( paymentSplit );
195 if( otherSplit.size() > 0 )
196 {
197 _table-> elementAt( row, 2 )-> addNew< Wt::WText >( otherSplit[0] -> memo() );
198 }
199 }
200
201 _table-> elementAt( row, 1 )-> clicked().connect( [=](){ m_clicked.emit( txItem-> description() ); } );
202 row++;
203
204 subTotal += splitItem-> value( true );
205
206 } // endfor( ..all payments.. )
207
208 _table-> elementAt( row, 0 )-> addNew< Wt::WText >( Wt::WString("{1}").arg( toString( subTotal, GCW::Cfg::decimal_format() ) ) );
209 _table-> elementAt( row, 0 )-> setStyleClass( "du" ); // double-underline
210 _table-> elementAt( row-1, 0 )-> setStyleClass( "su" ); // single-underline
211 row++;
212
213 /*
214 ** record the day total for the subsequent report
215 **
216 ** \todo total unbelievable hack
217 **
218 ** This 'hack' causes this summary report to print ~only~ the values
219 ** that are considered payments that came from a 'bank' or some type
220 ** of ASSET account. Note, that bills can also be paid with LIABILITY
221 ** account types, but we don't want those accounts to appear in the
222 ** transfer totals since the transfer totals are for showing how much
223 ** money needs to be transferred out of the checking account(s).
224 */
226 {
227 DayTotal_t dayTotal;
228 dayTotal.day = payFromDay;
229 dayTotal.bank = payFromAcct;
230 dayTotal.value = subTotal;
231 dayTotals.push_back( dayTotal );
232 }
233
234 } // endall payFromDay
235
236 } // endfor( ..all payFroms.. )
237
238 _table-> elementAt( row, 0 )-> addNew< Wt::WText >( TR("gcw.billPay.lbl.transfers") );
239 _table-> elementAt( row, 0 )-> setStyleClass( "acctDay" );
240 _table-> elementAt( row, 0 )-> setAttributeValue( "style", "text-align:center;border-bottom: 1px solid black;" );
241 _table-> elementAt( row, 0 )-> setColumnSpan( 2 );
242 row++;
243
244 std::sort
245 (
246 dayTotals.begin(),
247 dayTotals.end(),
248 []( DayTotal_t a, DayTotal_t b )
249 {
250 return a.day < b.day;
251 }
252 );
253
254 GCW_NUMERIC grand(0);
255 GCW_NUMERIC sum(0);
256 int day = 0;
257 for( auto dayTotal : dayTotals )
258 {
259 if( day != dayTotal.day )
260 {
261 auto payDay =
262 Wt::WString("<u>{1}<sup>{2}</sup></u>")
263 .arg( day )
264 .arg( ordinalSuffix( day ) )
265 .toUTF8()
266 ;
267
268 if( day != 0 )
269 {
270 _table-> elementAt( row, 0 )-> addNew< Wt::WText >( Wt::WString("{1}") .arg( toString( sum, GCW::Cfg::decimal_format() ) ) );
271 _table-> elementAt( row, 1 )-> addNew< Wt::WText >( Wt::WString(TR("gcw.billPay.lbl.totalfor")) .arg( payDay ) );
272 _table-> elementAt( row, 0 )-> setStyleClass( "du" );
273 _table-> elementAt( row-1, 0 )-> setStyleClass( "su" );
274 row++;
275 grand += sum;
276 sum = 0;
277
278 _table-> elementAt( row, 0 )-> addNew< Wt::WText >( "" );
279 _table-> elementAt( row, 0 )-> setColumnSpan( 2 );
280 _table-> elementAt( row, 0 )-> setAttributeValue( "style", "border-bottom:1px solid black;" );
281 row++;
282
283 } // endif( day != 0 )
284
285 day = dayTotal.day;
286
287 } // endif( day != dayTotal.day )
288
289 _table-> elementAt( row, 0 )-> addNew< Wt::WText >( Wt::WString("{1}").arg( toString( dayTotal.value, GCW::Cfg::decimal_format() ) ) );
290 _table-> elementAt( row, 1 )-> addNew< Wt::WText >( dayTotal.bank );
291 row++;
292
293 sum += dayTotal.value;
294
295 } // endfor( auto day : splits.dayTotals() )
296
297 if( day != 0 )
298 {
299 auto payDay =
300 Wt::WString("<u>{1}<sup>{2}</sup></u>")
301 .arg( day )
302 .arg( ordinalSuffix( day ) )
303 .toUTF8()
304 ;
305
306 _table-> elementAt( row, 0 )-> addNew< Wt::WText >( Wt::WString("{1}").arg( toString( sum, GCW::Cfg::decimal_format() ) ) );
307 _table-> elementAt( row, 1 )-> addNew< Wt::WText >( Wt::WString(TR("gcw.billPay.lbl.totalfor")).arg( payDay ) );
308 _table-> elementAt( row, 0 )-> setStyleClass( "du" ); // double-underline
309 _table-> elementAt( row-1, 0 )-> setStyleClass( "su" ); // single-underline
310 row++;
311 grand += sum;
312 sum = 0;
313 _table-> elementAt( row, 0 )-> addNew< Wt::WText >( "" );
314 _table-> elementAt( row, 0 )-> setColumnSpan( 2 );
315 _table-> elementAt( row, 0 )-> setAttributeValue( "style", "border-bottom:1px double black;" );
316 row++;
317
318 } // endif( day != 0 )
319
320 _table-> elementAt( row, 0 )-> addNew< Wt::WText >( Wt::WString("{1}").arg( toString( grand, GCW::Cfg::decimal_format() ) ) );
321 _table-> elementAt( row, 1 )-> addNew< Wt::WText >( Wt::WString(TR("gcw.billPay.lbl.totalfor")).arg( TR("gcw.billPay.ttp." + toString( m_month ) ) ) );
322 row++;
323
324 _table-> elementAt( row, 0 )-> addNew< Wt::WText >( "" );
325 _table-> elementAt( row, 0 )-> setColumnSpan( 2 );
326 _table-> elementAt( row, 0 )-> setAttributeValue( "style", "border-bottom:1px double black;" );
327 row++;
328
329} // endgenerateReport( Wt::WTable * _table ) -> void
330
331auto
333outputReport() -> void
334{
335 Wt::WTable table;
336
337 table.setStyleClass( "SummaryTable" );
338
339 generateReport( &table, true );
340
341 auto fileName =
342 Wt::WString( "logs/{1}-{2}-billPay.html" )
343 .arg( m_year )
344 .arg( toString( m_month ) )
345 .toUTF8()
346 ;
347
348 std::ofstream file( fileName, std::ios_base::out );
349
350 file << TR("gcw.billPay.report.head");
351 table.htmlText( file );
352 file << TR("gcw.billPay.report.foot");
353
354} // endoutputReport() const -> void
355
356
358Splits( int _month, int _year )
359: m_month( _month ),
360 m_year( _year )
361{
362 /*
363 ** get all the transactions that happened for this account for this month
364 */
366
367 /*
368 ** if transactions happened, get them open and see if they should be in the summary report
369 */
370 for( auto txItem : txItems )
371 {
372 /*
373 ** loop through all the splits
374 */
375 for( auto split : GCW::Dbo::Splits::byTransaction( txItem-> guid() ) )
376 {
377 /*
378 ** We want the pay-from account.
379 */
380 if( split-> value() < 0 )
381 {
382 m_splitGuids.push_back( split-> guid() );
383
384 } // endif( ..pay-from account.. )
385
386 } // endfor( auto split : GCW::Dbo::Splits::byTransaction( txItem-> guid() ) )
387
388 } // endfor( auto txItem : txItems )
389
390} // endSplits( int _month )
391
392
393auto
395splitGuids() const-> const std::vector< std::string > &
396{
397 return m_splitGuids;
398
399} // endsplitGuids() const-> std::vector< std::string >
400
401
402auto
404days() const-> std::set< int >
405{
406 std::set< int > retVal;
407
408 /*
409 ** loop through all the splits and produce a set
410 ** of days that payments were made
411 */
412 for( auto splitGuid : splitGuids() )
413 {
414 auto splitItem = GCW::Dbo:: Splits ::byGuid( splitGuid );
415 auto txItem = GCW::Dbo:: Transactions ::byGuid( splitItem-> tx_guid() );
416
417 retVal.insert( txItem-> post_date_as_date().date().day() );
418
419 } // endfor( auto splitGuid : splitGuids() )
420
421 return retVal;
422
423} // enddays() const-> std::set< int >
424
425
426auto
428payFroms() const-> std::set< std::string >
429{
430 std::set< std::string > retVal;
431
432 /*
433 ** loop throught all the splits and produce a set
434 ** of 'account name' that were used to make payments
435 */
436 for( auto splitGuid : splitGuids() )
437 {
438 auto splitItem = GCW::Dbo:: Splits ::byGuid( splitGuid );
439 auto acctItem = GCW::Dbo:: Accounts ::byGuid( splitItem-> account_guid() );
440
441 retVal.insert( acctItem-> name() );
442
443 } // endfor( auto splitGuid : splitGuids() )
444
445 return retVal;
446
447} // enddays() const-> std::set< int >
448
449
450auto
452payFromDays( const std::string & _payFrom ) const-> std::set< int >
453{
454 std::set< int > retVal;
455
456 /*
457 ** loop through all the splits and produce a set of
458 ** days that an account made payments on
459 */
460 for( auto splitGuid : splitGuids() )
461 {
462 auto splitItem = GCW::Dbo:: Splits ::byGuid( splitGuid );
463 auto acctItem = GCW::Dbo:: Accounts ::byGuid( splitItem-> account_guid() );
464 auto txItem = GCW::Dbo:: Transactions ::byGuid( splitItem-> tx_guid() );
465
466 if( acctItem-> name() == _payFrom )
467 retVal.insert( txItem-> post_date_as_date().date().day() );
468
469 } // endfor( auto splitGuid : splitGuids() )
470
471 return retVal;
472
473} // enddays() const-> std::set< int >
474
475
476auto
478paymentSplits( const std::string & _payFrom, int _day ) const-> std::vector< std::string >
479{
480 std::vector< std::string > retVal;
481
482 /*
483 ** loop through all the splits and produce a vector
484 ** of all the splits used from an account on specific
485 ** day
486 */
487 for( auto splitGuid : splitGuids() )
488 {
489 auto splitItem = GCW::Dbo:: Splits ::byGuid( splitGuid );
490 auto acctItem = GCW::Dbo:: Accounts ::byGuid( splitItem-> account_guid() );
491 auto txItem = GCW::Dbo:: Transactions ::byGuid( splitItem-> tx_guid() );
492
493 if( acctItem-> name() == _payFrom
494 && txItem-> post_date_as_date().date().day() == _day
495 )
496 retVal.push_back( splitGuid );
497
498 } // endfor( auto splitGuid : splitGuids() )
499
500 return retVal;
501
502} // endpaymentSplits( const std::string & _payFrom, int _day ) const-> std::vector< std::string >
503
504
505auto
507dayPayments( int _day ) const-> std::vector< std::string >
508{
509 std::vector< std::string > retVal;
510
511 /*
512 ** loop through all the splits and produce a list of
513 ** splits that were on a specific day.
514 */
515 for( auto splitGuid : splitGuids() )
516 {
517 auto splitItem = GCW::Dbo:: Splits ::byGuid( splitGuid );
518 auto txItem = GCW::Dbo:: Transactions ::byGuid( splitItem-> tx_guid() );
519
520 if( txItem-> post_date_as_date().date().day() == _day )
521 retVal.push_back( splitGuid );
522
523 } // endfor( auto splitGuid : splitGuids() )
524
525 return retVal;
526
527} // enddayPayments( int _day ) const-> std::vector< std::string >
528
529
static std::vector< std::string > & split(const std::string &s, char delim, std::vector< std::string > &elems)
Definition Core.cpp:26
auto payFromDays(const std::string &_payFrom) const -> std::set< int >
auto payFroms() const -> std::set< std::string >
std::vector< std::string > m_splitGuids
auto splitGuids() const -> const std::vector< std::string > &
auto dayPayments(int _day) const -> std::vector< std::string >
auto paymentSplits(const std::string &_payFrom, int _day) const -> std::vector< std::string >
auto days() const -> std::set< int >
auto setDate(int _month, int _year) -> void
auto showSummaryDetail(bool _state) -> void
auto generateReport(Wt::WTable *_table, bool _showDetail) -> void
Widget * addNew(Args &&...args)
virtual void addWidget(std::unique_ptr< WWidget > widget)
static WDateTime currentDateTime()
std::string toUTF8() const
WString & arg(const std::wstring &value)
virtual void setStyleClass(const WString &styleClass) override
virtual void addStyleClass(const WString &styleClass, bool force=false) override
virtual void htmlText(std::ostream &out)
#define TR(X)
Definition define.h:17
#define GCW_NUMERIC
Internal Numeric Type.
Definition gcwglobal.h:40
DECIMAL::decimal_format decimal_format()
Decimal Format Specifier.
Definition GnuCashew.cpp:21
@ ASSET
02 ~ generic generalized asset account
auto isType(const Item::Ptr _acctItem, GCW::Dbo::Accounts::Type _type) -> bool
Is Account Type.
auto byTransaction(const std::string &_txGuid) -> Item::Vector
Load Splits by Transaction.
Definition Splits.cpp:193
auto bySplitExcept(const std::string &_splitGuid) -> Item::Vector
Load Splits by Split.
Definition Splits.cpp:144
auto byNumDate(const std::string &_num, int _month, int _year) -> Transactions::Item::Vector
Load Transactions for 'num' and Month and Year.
auto toString(int _value) -> std::string
Convert Integer to String.
Definition BillPay.cpp:55
App * app()
Definition App.cpp:75