GnuCash 038f90a+
Loading...
Searching...
No Matches
assistant-loan.cpp
1/********************************************************************\
2 * assistant-loan.c : An Assistant for setting up loan-repayment *
3 * scheduled transactions. *
4 * Copyright (C) 2002,2007 Joshua Sled <jsled@asynchronous.org> *
5 * Copyright (C) 2006 David Hampton <hampton@employees.org> *
6 * Copyright (C) 2011 Robert Fewell *
7 * *
8 * This program is free software; you can redistribute it and/or *
9 * modify it under the terms of the GNU General Public License as *
10 * published by the Free Software Foundation; either version 2 of *
11 * the License, or (at your option) any later version. *
12 * *
13 * This program is distributed in the hope that it will be useful, *
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
16 * GNU General Public License for more details. *
17 * *
18 * You should have received a copy of the GNU General Public License*
19 * along with this program; if not, contact: *
20 * *
21 * Free Software Foundation Voice: +1-617-542-5942 *
22 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
23 * Boston, MA 02110-1301, USA gnu@gnu.org *
24\********************************************************************/
25
26#include <glib.h>
27#include <glib/gi18n.h>
28#include <gtk/gtk.h>
29
30#include <config.h>
31#include <string.h>
32#include <stdlib.h>
33#include <math.h>
34#include "assistant-loan.h"
35#include "SchedXaction.h"
36#include "SchedXaction.hpp"
37#include "SX-book.h"
38#include "SX-ttinfo.hpp"
39#include "gnc-amount-edit.h"
40#include "gnc-account-sel.h"
41#include "gnc-date.h"
42#include "gnc-exp-parser.h"
43#include "gnc-component-manager.h"
44#include "dialog-utils.h"
45#include "Account.h"
46#include "gnc-ui.h"
47#include "gnc-gui-query.h"
48#include "gnc-ui-util.h"
49#include "gnc-frequency.h"
50#include "gnc-engine.h"
51#ifdef __MINGW32__
52#include <Windows.h>
53#endif
54
55#include <gnc-locale-utils.hpp>
56#include <boost/locale.hpp>
57#include <string>
58#include <sstream>
59#include <iomanip>
60#ifdef __MINGW32__
61#include <codecvt>
62#endif
63
64namespace bl = boost::locale;
65
66#define DIALOG_LOAN_ASSISTANT_CM_CLASS "assistant-loan-setup"
67
68static QofLogModule log_module = GNC_MOD_ASSISTANT;
69
70enum loan_cols
71{
72 LOAN_COL_DATE = 0,
73 LOAN_COL_PAYMENT,
74 LOAN_COL_PRINCIPAL,
75 LOAN_COL_INTEREST,
76 NUM_LOAN_COLS
77};
78
79typedef enum
80{
81 CURRENT_YEAR,
82 NOW_PLUS_ONE,
83 WHOLE_LOAN,
84 CUSTOM
85} REV_RANGE_OPTS;
86
87struct LoanAssistantData_;
88
93typedef struct RepayOptData_
94{
95 gboolean enabled;
96 gboolean optValid;
97 gboolean FreqUniq;
98 char *name; /* { "insurance", "pmi", "taxes", ... } */
99 char *txnMemo;
100 float amount;
101 gboolean throughEscrowP;
102 gboolean specSrcAcctP;
103 Account *to;
104 Account *from; /* If NULL { If throughEscrowP, then through escrowAcct };
105 * else: undefined. */
106 GList *schedule;
107 /* If NULL, part of repayment; otherwise: defined
108 * here. */
109 GDate *startDate;
111
115typedef struct RepayOptDataDefault_
116{
117 const char *name;
118 const char *defaultTxnMemo;
119 gboolean escrowDefault;
120 gboolean specSrcAcctDefault;
122
123static RepayOptDataDefault REPAY_DEFAULTS[] =
124{
125 /* { name, default txn memo, throughEscrowP, specSrcAcctP } */
126 { N_("Taxes"), N_("Tax Payment"), TRUE, FALSE },
127 { N_("Insurance"), N_("Insurance Payment"), TRUE, FALSE },
128 /* Translators: PMI stands for Private Mortgage Insurance. */
129 { N_("PMI"), N_("PMI Payment"), TRUE, FALSE },
130 { N_("Other Expense"), N_("Miscellaneous Payment"), FALSE, FALSE },
131 { NULL }
132};
133
137typedef struct RepayOptUI_
138{
139 /* must be stated this way [instead of 'LoanAssistantData*'] because of
140 * forward decl. */
141 struct LoanAssistantData_ *ldd;
142 GtkCheckButton *optCb;
143 GtkCheckButton *escrowCb;
144 RepayOptData *optData;
146
147typedef enum
148{
149 GNC_FIXED = 0,
150 GNC_VARIABLE,
151 GNC_VARIABLE_3_1 = GNC_VARIABLE,
152 GNC_VARIABLE_5_1,
153 GNC_VARIABLE_7_1,
154 GNC_VARIABLE_10_1,
155 /* ... FIXME */
156} LoanType;
157
158typedef enum
159{
160 GNC_MONTHS = 0,
161 GNC_YEARS
162} PeriodSize;
163
164/*type of interest rate entered*/
165typedef enum
166{
167 GNC_IRATE_SIMPLE,
168 GNC_IRATE_APR_DAILY,
169 GNC_IRATE_APR_WEEKLY,
170 GNC_IRATE_APR_MONTHLY,
171 GNC_IRATE_APR_QUARTERLY,
172 GNC_IRATE_APR_SEMIANNUALLY,
173 GNC_IRATE_APR_ANNUALLY
174} IRateType;
175
181typedef struct rev_repayment_row
182{
183 GDate date;
184 gnc_numeric *numCells;
186
190typedef struct LoanData_
191{
192 Account *primaryAcct;
193 gnc_numeric principal;
194 float interestRate;
195 IRateType rateType;
196 LoanType type;
197 GList *loan_schedule;
198 GDate *startDate;
199 GDate *varStartDate;
200 int numPer;
201 PeriodSize perSize;
202 int numMonRemain;
203
204 char *repMemo;
205 std::string repAmount;
206 Account *repFromAcct;
207 Account *repPriAcct;
208 Account *repIntAcct;
209 Account *escrowAcct;
210 GList *repayment_schedule;
211 GDate *repStartDate;
212
213 int repayOptCount;
214 RepayOptData **repayOpts;
215
216 /* Data concerning the review of repayments. */
217 int revNumPmts;
218 int revRepayOptToColMap[ (sizeof(REPAY_DEFAULTS)
219 / sizeof(RepayOptDataDefault))
220 - 1 ];
221 GList *revSchedule;
222} LoanData;
223
227typedef struct LoanAssistantData_
228{
229 GtkWidget *window;
230 GtkWidget *assistant;
231
232 LoanData ld;
233 /* The UI-side storage of repayment data; this is 1:1 with the array
234 * in LoanData */
235 RepayOptUIData **repayOptsUI;
236
237 /* Current index of the payment opt for multiplexing the 'payment'
238 * page. */
239 int currentIdx;
240
241 /* widgets */
242 /* prm = params */
243 GtkGrid *prmTable;
244 GNCAccountSel *prmAccountGAS;
245 GNCAmountEdit *prmOrigPrincGAE;
246 GtkSpinButton *prmIrateSpin;
247 GtkComboBox *prmType;
248 GtkFrame *prmVarFrame;
249 GncFrequency *prmVarGncFreq;
250 GNCDateEdit *prmStartDateGDE;
251 GtkSpinButton *prmLengthSpin;
252 GtkComboBox *prmLengthType;
253 GtkSpinButton *prmRemainSpin;
254 GtkComboBox *prmIrateType;
255
256 /* opt = options */
257 GtkBox *optVBox;
258 GtkCheckButton *optEscrowCb;
259 GtkBox *optEscrowHBox;
260 GNCAccountSel *optEscrowGAS;
261
262 /* rep = repayment */
263 GtkEntry *repTxnName;
264 GtkGrid *repTable;
265 GtkEntry *repAmtEntry;
266 GNCAccountSel *repAssetsFromGAS;
267 GNCAccountSel *repPrincToGAS;
268 GNCAccountSel *repIntToGAS;
269 GtkFrame *repFreqFrame;
270 GncFrequency *repGncFreq;
271
272 /* pay = payment[s] */
273 GtkEntry *payTxnName;
274 GtkEntry *payAmtEntry;
275 GNCAccountSel *payAcctFromGAS;
276 GNCAccountSel *payAcctEscToGAS;
277 GNCAccountSel *payAcctEscFromGAS;
278 GNCAccountSel *payAcctToGAS;
279 GtkGrid *payTable;
280 GtkCheckButton *payUseEscrow;
281 GtkCheckButton *paySpecSrcAcct;
282 GtkLabel *payAcctFromLabel;
283 GtkLabel *payEscToLabel;
284 GtkLabel *payEscFromLabel;
285 GtkRadioButton *payTxnFreqPartRb;
286 GtkRadioButton *payTxnFreqUniqRb;
287 GtkBox *payFreqHBox;
288 GncFrequency *payGncFreq;
289
290 /* rev = review */
291 GtkComboBox *revRangeOpt;
292 GtkFrame *revDateFrame;
293 GtkGrid *revTable;
294 GNCDateEdit *revStartDate;
295 GNCDateEdit *revEndDate;
296 GtkScrolledWindow *revScrollWin;
297 GtkTreeView *revView;
299
303typedef struct toCreateSX_
304{
306 gchar *name;
308 GDate start, last, end;
310 GList *schedule;
314 TTInfoPtr mainTxn;
316 TTInfoPtr escrowTxn;
317} toCreateSX;
318
319/**************************************************************************/
320
321static void loan_assistant_window_destroy_cb( GtkWidget *object, gpointer user_data );
322static void loan_assistant_close_handler( gpointer user_data );
323static void loan_assistant_data_init( LoanAssistantData *ldd );
324
325static void loan_info_prep( GtkAssistant *assistant, gpointer user_data );
326static void loan_info_prm_type_cb( GtkWidget *w, gpointer user_data );
327static void loan_info_calc_update_cb( GtkWidget *widget, gpointer user_data );
328void loan_info_page_valid_cb( GtkWidget *widget, gpointer user_data );
329static gboolean loan_info_page_complete( GtkAssistant *assistant, gpointer user_data );
330static void loan_info_page_save( GtkAssistant *assistant, gpointer user_data );
331
332static void loan_opt_prep( GtkAssistant *assistant, gpointer user_data );
333static void loan_opt_toggled_cb( GtkToggleButton *tb, gpointer user_data );
334static void loan_opt_consistency_cb( GtkToggleButton *tb, gpointer user_data );
335static void loan_opt_escrow_toggle_cb( GtkToggleButton *tb, gpointer user_data );
336static void loan_opt_escrow_toggled_cb( GtkToggleButton *tb, gpointer user_data );
337void loan_opt_page_valid_cb (GtkWidget *widget, gpointer user_data );
338static gboolean loan_opt_page_complete( GtkAssistant *assistant, gpointer user_data );
339
340static void loan_rep_prep ( GtkAssistant *assistant, gpointer user_data );
341void loan_rep_page_valid_cb (GtkWidget *widget, gpointer user_data );
342static gboolean loan_rep_page_complete( GtkAssistant *assistant, gpointer user_data );
343static void loan_rep_page_save( GtkAssistant *assistant, gpointer user_data );
344
345static void loan_pay_prep ( GtkAssistant *assistant, gpointer user_data );
346static void loan_pay_use_esc_setup( LoanAssistantData *ldd, gboolean newState );
347static void loan_pay_use_esc_toggle_cb( GtkToggleButton *tb, gpointer user_data );
348static void loan_pay_spec_src_setup( LoanAssistantData *ldd, gboolean newState );
349static void loan_pay_spec_src_toggle_cb( GtkToggleButton *tb, gpointer user_data );
350static void loan_pay_freq_toggle_cb( GtkToggleButton *tb, gpointer user_data );
351static void loan_pay_page_valid_cb (GtkWidget *widget, gpointer user_data );
352static gboolean loan_pay_complete( GtkAssistant *assistant, gpointer user_data );
353static gboolean loan_pay_all_opt_valid ( GtkAssistant *assistant, gpointer user_data );
354static void loan_pay_back_button_cb( GtkButton *button, gpointer user_data );
355static void loan_pay_next_button_cb( GtkButton *button, gpointer user_data );
356
357static void loan_rev_prep ( GtkAssistant *assistant, gpointer user_data );
358static void loan_rev_recalc_schedule( LoanAssistantData *ldd );
359static void loan_rev_range_opt_changed_cb( GtkComboBox *combo, gpointer user_data );
360static void loan_rev_range_changed_cb( GNCDateEdit *gde, gpointer user_data );
361static void loan_rev_get_loan_range( LoanAssistantData *ldd, GDate *start, GDate *end );
362static void loan_rev_get_dates( LoanAssistantData *ldd, GDate *start, GDate *end );
363static void loan_rev_update_view( LoanAssistantData *ldd, GDate *start, GDate *end );
364static void loan_rev_sched_list_free( gpointer data, gpointer user_data );
365static void loan_rev_hash_to_list( gpointer key, gpointer val, gpointer user_data );
366static void loan_rev_hash_free_date_keys( gpointer key, gpointer val, gpointer user_data );
367
368static std::string loan_get_pmt_formula(LoanAssistantData *ldd);
369static std::string loan_get_ppmt_formula(LoanAssistantData *ldd);
370static std::string loan_get_ipmt_formula(LoanAssistantData *ldd);
371static float loan_apr_to_simple_formula (double rate, double pmt_periods, double comp_periods);
372
373static void loan_create_sxes( LoanAssistantData *ldd );
374static void loan_create_sx_from_tcSX( LoanAssistantData *ldd, toCreateSX *tcSX );
375static void loan_tcSX_free( gpointer data, gpointer user_data );
376
377extern "C" {
378void loan_assistant_prepare( GtkAssistant *assistant, GtkWidget *page, gpointer user_data );
379void loan_assistant_finish( GtkAssistant *gtkassistant, gpointer user_data );
380void loan_assistant_cancel( GtkAssistant *gtkassistant, gpointer user_data );
381void loan_assistant_close( GtkAssistant *gtkassistant, gpointer user_data );
382}
383
384/*****************************************************************************/
385
386
387static
388void
389loan_assistant_close_handler( gpointer user_data )
390{
391 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
392 gtk_widget_destroy( ldd->window );
393}
394
395
396static
397void
398loan_assistant_window_destroy_cb( GtkWidget *object, gpointer user_data )
399{
400 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
401
402 g_assert( ldd );
403
404 gnc_unregister_gui_component_by_data (DIALOG_LOAN_ASSISTANT_CM_CLASS, ldd);
405
406 /* free alloc'd mem; cleanup */
407
408 /* repay opts */
409 {
410 int i;
411
412 g_date_free( ldd->ld.startDate );
413 g_date_free( ldd->ld.varStartDate );
414 recurrenceListFree(&ldd->ld.loan_schedule);
415
416 if ( ldd->ld.repMemo )
417 g_free( ldd->ld.repMemo );
418
419 for ( i = 0; i < ldd->ld.repayOptCount; i++ )
420 {
421 RepayOptData *rod = ldd->ld.repayOpts[i];
422 if ( rod->name )
423 g_free( rod->name );
424 if ( rod->txnMemo )
425 g_free( rod->txnMemo );
426
427 if ( rod->startDate )
428 g_date_free( rod->startDate );
429
430 if (rod->schedule != NULL)
431 recurrenceListFree(&rod->schedule);
432
433 g_free( ldd->ld.repayOpts[i] );
434 g_free( ldd->repayOptsUI[i] );
435 }
436 g_free( ldd->ld.repayOpts );
437 g_free( ldd->repayOptsUI );
438
439 g_date_free( ldd->ld.repStartDate );
440 }
441
442 /* review */
443 {
444 if ( ldd->ld.revSchedule )
445 {
446 g_list_foreach( ldd->ld.revSchedule,
447 loan_rev_sched_list_free,
448 NULL );
449 g_list_free( ldd->ld.revSchedule );
450 ldd->ld.revSchedule = NULL;
451 }
452 }
453
454 g_free( ldd );
455}
456
457
458static GtkWidget *
459gnc_loan_assistant_create( LoanAssistantData *ldd )
460{
461 GtkBuilder *builder;
462 GtkWidget *window;
463
464 loan_assistant_data_init( ldd );
465
466 builder = gtk_builder_new();
467
468 gnc_builder_add_from_file (builder , "assistant-loan.glade", "len_liststore");
469 gnc_builder_add_from_file (builder , "assistant-loan.glade", "range_liststore");
470 gnc_builder_add_from_file (builder , "assistant-loan.glade", "type_liststore");
471 gnc_builder_add_from_file (builder , "assistant-loan.glade", "rate_liststore");
472
473 gnc_builder_add_from_file (builder , "assistant-loan.glade", "loan_mortgage_assistant");
474 window = GTK_WIDGET(gtk_builder_get_object (builder, "loan_mortgage_assistant"));
475 ldd->window = window;
476
477 // Set the name for this assistant so it can be easily manipulated with css
478 gtk_widget_set_name (GTK_WIDGET(window), "gnc-id-assistant-loan");
479
480 /* Enable buttons on complete pages. */
481 gtk_assistant_set_page_complete (GTK_ASSISTANT (window),
482 GTK_WIDGET(gtk_builder_get_object(builder, "loan_intro_page")),
483 TRUE);
484 gtk_assistant_set_page_complete (GTK_ASSISTANT (window),
485 GTK_WIDGET(gtk_builder_get_object(builder, "loan_options_page")),
486 TRUE);
487 gtk_assistant_set_page_complete (GTK_ASSISTANT (window),
488 GTK_WIDGET(gtk_builder_get_object(builder, "loan_review_page")),
489 TRUE);
490
491 /* Information Page */
492 {
493 ldd->prmTable = GTK_GRID(gtk_builder_get_object(builder, "param_table"));
494 ldd->prmVarFrame = GTK_FRAME(gtk_builder_get_object(builder, "type_freq_frame"));
495 ldd->prmIrateSpin = GTK_SPIN_BUTTON (gtk_builder_get_object(builder, "irate_spin"));
496 ldd->prmType = GTK_COMBO_BOX (gtk_builder_get_object(builder, "type_combobox"));
497 gtk_combo_box_set_active( GTK_COMBO_BOX( ldd->prmType), FALSE );
498 ldd->prmLengthSpin = GTK_SPIN_BUTTON (gtk_builder_get_object(builder, "len_spin"));
499 ldd->prmLengthType = GTK_COMBO_BOX (gtk_builder_get_object(builder, "len_opt"));
500 gtk_combo_box_set_active( GTK_COMBO_BOX( ldd->prmLengthType), FALSE );
501 ldd->prmRemainSpin = GTK_SPIN_BUTTON (gtk_builder_get_object(builder, "rem_spin"));
502 ldd->prmIrateType = GTK_COMBO_BOX (gtk_builder_get_object(builder, "irate_type_combobox"));
503 gtk_combo_box_set_active( GTK_COMBO_BOX( ldd->prmIrateType), FALSE );
504 /* ldd->prmStartDateGDE */
505 }
506 /* Repayment Page */
507 {
508 ldd->repTable = GTK_GRID(gtk_builder_get_object(builder, "repay_table"));
509 ldd->repTxnName = GTK_ENTRY(gtk_builder_get_object(builder, "txn_title"));
510 ldd->repAmtEntry = GTK_ENTRY(gtk_builder_get_object(builder, "amount_ent"));
511 ldd->repFreqFrame = GTK_FRAME(gtk_builder_get_object(builder, "freq_frame"));
512 }
513 /* Options Page */
514 {
515 ldd->optVBox = GTK_BOX(gtk_builder_get_object(builder, "loan_options_page"));
516 ldd->optEscrowCb = GTK_CHECK_BUTTON(gtk_builder_get_object(builder, "opt_escrow_cb"));
517 ldd->optEscrowHBox = GTK_BOX(gtk_builder_get_object(builder, "opt_escrow_hbox"));
518 }
519 /* Payment Page */
520 {
521 ldd->payTable = GTK_GRID(gtk_builder_get_object(builder, "pay_table"));
522 ldd->payTxnName = GTK_ENTRY(gtk_builder_get_object(builder, "pay_txn_title"));
523 ldd->payAmtEntry = GTK_ENTRY(gtk_builder_get_object(builder, "pay_amt_ent"));
524 ldd->payUseEscrow = GTK_CHECK_BUTTON(gtk_builder_get_object(builder, "pay_use_escrow"));
525 ldd->paySpecSrcAcct = GTK_CHECK_BUTTON(gtk_builder_get_object(builder, "pay_specify_source"));
526 ldd->payAcctFromLabel = GTK_LABEL(gtk_builder_get_object(builder, "pay_from_account_label"));
527 ldd->payEscToLabel = GTK_LABEL(gtk_builder_get_object(builder, "pay_escrow_to_label"));
528 ldd->payEscFromLabel = GTK_LABEL(gtk_builder_get_object(builder, "pay_escrow_from_label"));
529 ldd->payTxnFreqPartRb = GTK_RADIO_BUTTON(gtk_builder_get_object(builder, "pay_txn_part_rb"));
530 ldd->payTxnFreqUniqRb = GTK_RADIO_BUTTON(gtk_builder_get_object(builder, "pay_uniq_freq_rb"));
531 ldd->payFreqHBox = GTK_BOX(gtk_builder_get_object(builder, "pay_freq_hbox"));
532 }
533 /* Review Page */
534 {
535 ldd->revTable = GTK_GRID(gtk_builder_get_object(builder, "rev_date_range_table"));
536 ldd->revRangeOpt = GTK_COMBO_BOX(gtk_builder_get_object(builder, "rev_range_opt"));
537 ldd->revDateFrame = GTK_FRAME(gtk_builder_get_object(builder, "rev_date_frame"));
538 ldd->revScrollWin = GTK_SCROLLED_WINDOW(gtk_builder_get_object(builder, "rev_scrollwin"));
539 /* GNCDateEdit *revStartDate */
540 /* GNCDateEdit *revEndDate */
541 }
542
543 /* non-buildable widget setup */
544 {
545 GtkWidget *butt;
546 int i;
547 // ACCT_TYPE_LIABILITY
548 GList *liabilityAcct;
549 // ACCT_TYPE_BANK, ACCT_TYPE_CASH, ACCT_TYPE_CREDIT,
550 // ACCT_TYPE_ASSET + ACCT_TYPE_LIABILITY
551 GList *paymentFromAccts;
552 // ACCT_TYPE_EXPENSE, ACCT_TYPE_LIABILITY, + payment-froms.
553 GList *paymentToAccts;
554 int fromLen = 5;
555 GNCAccountType paymentFroms[] = { ACCT_TYPE_BANK, ACCT_TYPE_CASH,
558 };
559 int toLen = 1;
560 GNCAccountType paymentTos[] = { ACCT_TYPE_EXPENSE };
561
562 liabilityAcct = NULL;
563 paymentFromAccts = NULL;
564 paymentToAccts = NULL;
565
566 liabilityAcct = g_list_append( liabilityAcct,
567 GINT_TO_POINTER( ACCT_TYPE_LIABILITY ) );
568 for ( i = 0; i < fromLen; i++ )
569 {
570 paymentFromAccts
571 = g_list_append( paymentFromAccts,
572 GINT_TO_POINTER( paymentFroms[i] ) );
573 paymentToAccts
574 = g_list_append( paymentToAccts,
575 GINT_TO_POINTER( paymentFroms[i] ) );
576 }
577
578 for ( i = 0; i < toLen; i++ )
579 {
580 paymentToAccts = g_list_append( paymentToAccts,
581 GINT_TO_POINTER( paymentTos[i] ) );
582 }
583
584 /* All of the GncAccountSel[ectors]... */
585 {
586 int i;
587 /* "gas" == GncAccountSel */
588 struct gas_in_tables_data
589 {
590 GNCAccountSel **loc;
591 GtkGrid *table;
592 gboolean newAcctAbility;
593 int left, top, width, height;
594 GList *allowableAccounts;
595 } gas_data[] =
596 {
597 /* These ints are the GtkGrid boundaries */
598 { &ldd->prmAccountGAS, ldd->prmTable, TRUE, 1, 0, 1, 1, liabilityAcct },
599 { &ldd->repAssetsFromGAS, ldd->repTable, TRUE, 1, 2, 1, 1, paymentFromAccts },
600 { &ldd->repPrincToGAS, ldd->repTable, TRUE, 1, 3, 1, 1, paymentToAccts },
601 { &ldd->repIntToGAS, ldd->repTable, TRUE, 3, 3, 1, 1, paymentToAccts },
602 { &ldd->payAcctFromGAS, ldd->payTable, TRUE, 1, 4, 1, 1, paymentFromAccts },
603 { &ldd->payAcctEscToGAS, ldd->payTable, FALSE, 3, 4, 1, 1, paymentToAccts },
604 { &ldd->payAcctEscFromGAS, ldd->payTable, FALSE, 1, 5, 1, 1, paymentFromAccts },
605 { &ldd->payAcctToGAS, ldd->payTable, TRUE, 3, 5, 1, 1, paymentToAccts },
606 { NULL }
607 };
608
609 ldd->prmOrigPrincGAE = GNC_AMOUNT_EDIT(gnc_amount_edit_new());
610 gtk_grid_attach (GTK_GRID(ldd->prmTable), GTK_WIDGET(ldd->prmOrigPrincGAE), 1, 1, 1, 1);
611
612 gtk_widget_set_halign (GTK_WIDGET(ldd->prmOrigPrincGAE), GTK_ALIGN_FILL);
613 gtk_widget_set_hexpand (GTK_WIDGET(ldd->prmOrigPrincGAE), FALSE);
614 g_object_set (GTK_WIDGET(ldd->prmOrigPrincGAE), "margin", 2, nullptr);
615
616 g_signal_connect (G_OBJECT(ldd->prmOrigPrincGAE), "changed",
617 G_CALLBACK(loan_info_page_valid_cb), ldd);
618
619 for ( i = 0; gas_data[i].loc != NULL; i++ )
620 {
621 GNCAccountSel *gas = GNC_ACCOUNT_SEL(gnc_account_sel_new());
622
623 gnc_account_sel_set_new_account_ability(
624 gas, gas_data[i].newAcctAbility );
625 if ( gas_data[i].allowableAccounts != NULL )
626 {
627 gnc_account_sel_set_acct_filters(
628 gas, gas_data[i].allowableAccounts, NULL );
629 }
630
631 gtk_grid_attach (GTK_GRID(gas_data[i].table),
632 GTK_WIDGET(gas),
633 gas_data[i].left,
634 gas_data[i].top,
635 gas_data[i].width,
636 gas_data[i].height);
637
638 gtk_widget_set_halign (GTK_WIDGET(gas), GTK_ALIGN_FILL);
639 gnc_account_sel_set_new_account_modal (GNC_ACCOUNT_SEL(gas), true);
640 g_object_set (GTK_WIDGET(gas), "margin", 2, nullptr);
641 *(gas_data[i].loc) = gas;
642 }
643 }
644
645 /* Setup the payment page always-insensitive GASes */
646 gtk_widget_set_sensitive( GTK_WIDGET(ldd->payAcctEscToGAS), FALSE );
647 gtk_widget_set_sensitive( GTK_WIDGET(ldd->payAcctEscFromGAS), FALSE );
648
649 /* The GNCDateEdit[s] */
650 {
651 /* "gde" == GNCDateEdit */
652 struct gde_in_tables_data
653 {
654 GNCDateEdit **loc;
655 GtkGrid *table;
656 int left, top, width, height;
657 } gde_data[] =
658 {
659 /* These ints are the GtkGrid boundaries */
660 { &ldd->prmStartDateGDE, ldd->prmTable, 1, 4, 1, 1 },
661 { &ldd->revStartDate, ldd->revTable, 1, 0, 1, 1 },
662 { &ldd->revEndDate, ldd->revTable, 1, 1, 1, 1 },
663 { NULL }
664 };
665
666 for ( i = 0; gde_data[i].loc != NULL; i++ )
667 {
668 *gde_data[i].loc =
669 GNC_DATE_EDIT(
670 gnc_date_edit_new( gnc_time (NULL),
671 FALSE, FALSE ) );
672
673 gtk_grid_attach (GTK_GRID(gde_data[i].table),
674 GTK_WIDGET( *gde_data[i].loc ),
675 gde_data[i].left,
676 gde_data[i].top,
677 gde_data[i].width,
678 gde_data[i].height);
679
680 gtk_widget_set_halign (GTK_WIDGET( *gde_data[i].loc ), GTK_ALIGN_START);
681 gtk_widget_set_hexpand (GTK_WIDGET( *gde_data[i].loc ), FALSE);
682 g_object_set (GTK_WIDGET( *gde_data[i].loc ), "margin", 0, nullptr);
683 }
684
685 }
686
687 gtk_widget_set_sensitive( GTK_WIDGET(ldd->prmVarFrame), FALSE );
688 {
689 g_signal_connect( ldd->prmType, "changed",
690 G_CALLBACK( loan_info_prm_type_cb ),
691 ldd );
692 }
693
694 {
695 GtkAdjustment *a;
696
697 /* 8.0 [%], range of 0.005..100.0 with ticks at 0.001[%]. */
698 a = GTK_ADJUSTMENT(gtk_adjustment_new( 8.0, 0.001,
699 100.0, 0.001,
700 1.0, 0.0 ));
701 gtk_spin_button_set_adjustment( ldd->prmIrateSpin, a );
702 gtk_spin_button_set_value( ldd->prmIrateSpin, 8.00 );
703 gtk_spin_button_set_snap_to_ticks( ldd->prmIrateSpin, TRUE );
704
705 a = GTK_ADJUSTMENT(gtk_adjustment_new( 360, 1,
706 9999, 1,
707 12, 0.0 ));
708 gtk_spin_button_set_adjustment( ldd->prmLengthSpin, a );
709 g_signal_connect( ldd->prmLengthSpin, "changed",
710 G_CALLBACK( loan_info_calc_update_cb ),
711 ldd );
712 g_signal_connect( ldd->prmStartDateGDE, "date-changed",
713 G_CALLBACK( loan_info_calc_update_cb ),
714 ldd );
715 g_signal_connect( ldd->prmLengthSpin, "value-changed",
716 G_CALLBACK( loan_info_calc_update_cb ),
717 ldd );
718 g_signal_connect( ldd->prmLengthType, "changed",
719 G_CALLBACK( loan_info_calc_update_cb ),
720 ldd );
721
722 a = GTK_ADJUSTMENT(gtk_adjustment_new( 360, 1,
723 9999, 1,
724 12, 0.0 ));
725 gtk_spin_button_set_adjustment( ldd->prmRemainSpin, a );
726 }
727
728 g_signal_connect( ldd->optEscrowCb, "toggled",
729 G_CALLBACK(loan_opt_escrow_toggle_cb), ldd );
730 gtk_widget_set_sensitive( GTK_WIDGET(ldd->optEscrowHBox), FALSE );
731 ldd->optEscrowGAS = GNC_ACCOUNT_SEL(gnc_account_sel_new());
732 g_object_set (ldd->optEscrowGAS, "entry-width", 50, NULL);
733 gnc_account_sel_set_new_account_modal (GNC_ACCOUNT_SEL(ldd->optEscrowGAS), true);
734 gnc_account_sel_set_new_account_ability( ldd->optEscrowGAS, TRUE );
735 gtk_container_add( GTK_CONTAINER(ldd->optEscrowHBox),
736 GTK_WIDGET(ldd->optEscrowGAS) );
737
738 {
739 /* . Each RepayOpt gets an "entry" in the optContainer.
740 * . Each "entry" is a 2-line vbox containing:
741 * . The checkbox for the option itself
742 * . an alignment-contained sub-checkbox for "through the
743 * escrow account".
744 * . Hook up each to bit-twiddling the appropriate line.
745 */
746
747 RepayOptUIData *rouid;
748 GtkWidget *vb;
749 GString *str;
750
751 str = g_string_sized_new( 32 );
752
753 for ( i = 0; i < ldd->ld.repayOptCount; i++ )
754 {
755 rouid = ldd->repayOptsUI[i];
756 vb = gtk_box_new (GTK_ORIENTATION_VERTICAL, 2);
757 gtk_box_set_homogeneous (GTK_BOX (vb), FALSE);
758 gtk_widget_set_margin_start (GTK_WIDGET(vb), 12);
759
760 /* Add payment checkbox. */
761
762 /* Translators: %s is "Taxes",
763 "Insurance", or similar. */
764 g_string_printf( str, _("… pay \"%s\"?"),
765 rouid->optData->name );
766 rouid->optCb =
767 GTK_CHECK_BUTTON(
768 gtk_check_button_new_with_label(
769 str->str ));
770 gtk_box_pack_start( GTK_BOX(vb),
771 GTK_WIDGET(rouid->optCb),
772 FALSE, FALSE, 2 );
773 rouid->escrowCb =
774 GTK_CHECK_BUTTON(
775 gtk_check_button_new_with_label(
776 _("via Escrow account?") ));
777 gtk_widget_set_sensitive(
778 GTK_WIDGET(rouid->escrowCb),
779 FALSE );
780
781 gtk_box_pack_start( GTK_BOX(vb), GTK_WIDGET(rouid->escrowCb), FALSE, FALSE, 2 );
782 gtk_widget_set_margin_start (GTK_WIDGET(rouid->escrowCb), 12);
783
784 g_signal_connect( rouid->optCb, "toggled",
785 G_CALLBACK(loan_opt_toggled_cb),
786 rouid );
787 g_signal_connect( rouid->optCb, "toggled",
788 G_CALLBACK(loan_opt_consistency_cb),
789 rouid );
790 g_signal_connect( rouid->escrowCb, "toggled",
791 G_CALLBACK(loan_opt_escrow_toggled_cb),
792 rouid );
793
794 gtk_box_pack_start( GTK_BOX(ldd->optVBox), GTK_WIDGET(vb), FALSE, FALSE, 2 );
795 gtk_widget_show_all( GTK_WIDGET(ldd->optVBox) );
796 }
797 g_string_free( str, TRUE );
798 }
799
800 g_signal_connect( ldd->payUseEscrow, "toggled",
801 G_CALLBACK(loan_pay_use_esc_toggle_cb), ldd );
802 g_signal_connect( ldd->paySpecSrcAcct, "toggled",
803 G_CALLBACK(loan_pay_spec_src_toggle_cb), ldd );
804 g_signal_connect( ldd->payTxnFreqUniqRb, "toggled",
805 G_CALLBACK(loan_pay_freq_toggle_cb), ldd );
806
807 {
808 GtkBox *hbox;
809 hbox = GTK_BOX(gtk_builder_get_object(builder, "type_freq_hbox"));
810 ldd->prmVarGncFreq = GNC_FREQUENCY(gnc_frequency_new( NULL, NULL ));
811 gtk_box_pack_start( GTK_BOX(hbox) , GTK_WIDGET(ldd->prmVarGncFreq), TRUE, FALSE, 0 );
812 g_signal_connect (ldd->prmVarGncFreq, "changed",
813 G_CALLBACK (loan_info_page_valid_cb), ldd);
814 }
815 {
816 GtkBox *hbox;
817 hbox = GTK_BOX(gtk_builder_get_object(builder, "freq_frame_hbox"));
818 ldd->repGncFreq = GNC_FREQUENCY(gnc_frequency_new( NULL, NULL ));
819 gtk_box_pack_start( GTK_BOX(hbox) , GTK_WIDGET(ldd->repGncFreq), TRUE, FALSE, 0 );
820 g_signal_connect (ldd->repGncFreq, "changed",
821 G_CALLBACK (loan_rep_page_valid_cb), ldd);
822 }
823
824 ldd->payGncFreq = GNC_FREQUENCY(gnc_frequency_new( NULL, NULL ));
825 gtk_container_add( GTK_CONTAINER(ldd->payFreqHBox), GTK_WIDGET(ldd->payGncFreq) );
826
827 g_signal_connect (ldd->payGncFreq, "changed",
828 G_CALLBACK (loan_pay_page_valid_cb), ldd);
829
830 butt = GTK_WIDGET(gtk_builder_get_object(builder, "pay_back_button"));
831 g_signal_connect (butt, "clicked",
832 G_CALLBACK (loan_pay_back_button_cb), ldd);
833
834 butt = GTK_WIDGET(gtk_builder_get_object(builder, "pay_next_button"));
835 g_signal_connect (butt, "clicked",
836 G_CALLBACK (loan_pay_next_button_cb), ldd);
837
838 }
839
840 /* Info page Call Back */
841 {
842 g_signal_connect (ldd->prmAccountGAS, "account_sel_changed",
843 G_CALLBACK (loan_info_page_valid_cb), ldd);
844 g_signal_connect( ldd->prmIrateType, "changed",
845 G_CALLBACK( loan_info_page_valid_cb ), ldd );
846 }
847 /* Opts page Call Back */
848 {
849 g_signal_connect (ldd->optEscrowGAS, "account_sel_changed",
850 G_CALLBACK (loan_opt_page_valid_cb), ldd);
851 }
852 /* Rep page Call Back */
853 {
854 g_signal_connect (ldd->repAssetsFromGAS, "account_sel_changed",
855 G_CALLBACK (loan_rep_page_valid_cb), ldd);
856 g_signal_connect (ldd->repIntToGAS, "account_sel_changed",
857 G_CALLBACK (loan_rep_page_valid_cb), ldd);
858 g_signal_connect (ldd->repPrincToGAS, "account_sel_changed",
859 G_CALLBACK (loan_rep_page_valid_cb), ldd);
860 }
861 /* Pay page Call Back */
862 {
863 g_signal_connect (ldd->payAcctFromGAS, "account_sel_changed",
864 G_CALLBACK (loan_pay_page_valid_cb), ldd);
865 g_signal_connect (ldd->payAcctToGAS, "account_sel_changed",
866 G_CALLBACK (loan_pay_page_valid_cb), ldd);
867 g_signal_connect (ldd->payAcctEscFromGAS, "account_sel_changed",
868 G_CALLBACK (loan_pay_page_valid_cb), ldd);
869 g_signal_connect (ldd->payAcctEscToGAS, "account_sel_changed",
870 G_CALLBACK (loan_pay_page_valid_cb), ldd);
871 }
872 /* Review page Call Back */
873 {
874 gtk_combo_box_set_active( ldd->revRangeOpt, 0 );
875 g_signal_connect( ldd->revRangeOpt, "changed",
876 G_CALLBACK( loan_rev_range_opt_changed_cb ),
877 ldd );
878 g_signal_connect( ldd->revStartDate, "date-changed",
879 G_CALLBACK( loan_rev_range_changed_cb ),
880 ldd );
881 g_signal_connect( ldd->revEndDate, "date-changed",
882 G_CALLBACK( loan_rev_range_changed_cb ),
883 ldd );
884 }
885
886 g_signal_connect( ldd->window, "destroy",
887 G_CALLBACK(loan_assistant_window_destroy_cb),
888 ldd );
889
890 gtk_builder_connect_signals(builder, ldd);
891 g_object_unref(G_OBJECT(builder));
892
893 gtk_widget_show_all( ldd->window );
894 return window;
895}
896
897
898static
899void
900loan_assistant_data_init( LoanAssistantData *ldd )
901{
902 int i, optCount;
903 RepayOptData *optData;
904
905 /* get the count of repayment defaults. */
906 for ( optCount = 0; REPAY_DEFAULTS[optCount].name != NULL; optCount++ )
907 ;
908
909 ldd->currentIdx = -1;
910
911 ldd->ld.principal = gnc_numeric_zero();
912 ldd->ld.startDate = g_date_new();
913 ldd->ld.varStartDate = g_date_new();
914 gnc_gdate_set_time64( ldd->ld.startDate, gnc_time (NULL) );
915 ldd->ld.loan_schedule = NULL;
916 ldd->ld.repayment_schedule = NULL;
917 {
918 Recurrence *r = g_new0(Recurrence, 1);
919 recurrenceSet(r, 1, PERIOD_MONTH, ldd->ld.startDate, WEEKEND_ADJ_NONE);
920 ldd->ld.repayment_schedule = g_list_append(ldd->ld.repayment_schedule, r);
921 }
922
923 ldd->ld.repMemo = g_strdup( _("Loan") );
924 ldd->ld.repStartDate = g_date_new();
925 ldd->ld.repayOptCount = optCount;
926 ldd->ld.repayOpts = g_new0( RepayOptData*, optCount );
927 /* copy all the default lines into the LDD */
928 ldd->repayOptsUI = g_new0( RepayOptUIData*, optCount );
929 for ( i = 0; i < optCount; i++ )
930 {
931 g_assert( REPAY_DEFAULTS[i].name != NULL );
932
933 ldd->repayOptsUI[i] = g_new0( RepayOptUIData, 1 );
934 ldd->repayOptsUI[i]->ldd = ldd;
935
936 optData = ldd->ld.repayOpts[i]
937 = ldd->repayOptsUI[i]->optData
938 = g_new0( RepayOptData, 1 );
939
940 optData->enabled = FALSE;
941 optData->optValid = FALSE;
942 optData->FreqUniq = FALSE;
943 optData->name = g_strdup( _(REPAY_DEFAULTS[i].name) );
944 optData->txnMemo = g_strdup( _(REPAY_DEFAULTS[i].
945 defaultTxnMemo) );
946 optData->amount = 0.0;
947 optData->throughEscrowP = REPAY_DEFAULTS[i].escrowDefault;
948 optData->specSrcAcctP = REPAY_DEFAULTS[i].specSrcAcctDefault;
949 optData->schedule = NULL;
950 optData->startDate = NULL;
951 }
952}
953
954/************************ Page functions in order ***********************/
955
956static
957void
958loan_info_prep( GtkAssistant *assistant, gpointer user_data )
959{
960 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
961
962 gnc_amount_edit_set_amount( ldd->prmOrigPrincGAE, ldd->ld.principal );
963 gtk_spin_button_set_value( ldd->prmIrateSpin, ldd->ld.interestRate );
964 gtk_combo_box_set_active( ldd->prmIrateType, ldd->ld.rateType );
965 gtk_combo_box_set_active( ldd->prmType, ldd->ld.type );
966 if ( ldd->ld.type != GNC_FIXED )
967 {
968 g_signal_handlers_block_by_func( GNC_FREQUENCY( ldd->prmVarGncFreq),
969 (gpointer) loan_info_page_valid_cb, ldd );
970 gnc_frequency_setup_recurrence(ldd->prmVarGncFreq, ldd->ld.loan_schedule, ldd->ld.varStartDate);
971 g_signal_handlers_unblock_by_func( GNC_FREQUENCY( ldd->prmVarGncFreq),
972 (gpointer) loan_info_page_valid_cb, ldd );
973 }
974
975 /* start date */
976 {
977 struct tm *tmpTm;
978
979 tmpTm = g_new0( struct tm, 1 );
980
981 g_date_to_struct_tm (ldd->ld.startDate, tmpTm);
982 gnc_date_edit_set_time (ldd->prmStartDateGDE,
983 gnc_mktime (tmpTm));
984 g_free (tmpTm);
985 }
986
987 /* length: total and remaining */
988 {
989 gtk_spin_button_set_value( ldd->prmLengthSpin, ldd->ld.numPer );
990 gtk_combo_box_set_active( ldd->prmLengthType, ldd->ld.perSize );
991 gtk_spin_button_set_value( ldd->prmRemainSpin, ldd->ld.numMonRemain );
992 }
993}
994
995
996static
997void
998loan_info_prm_type_cb( GtkWidget *w, gpointer user_data )
999{
1000 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1001 gint index;
1002
1003 index = gtk_combo_box_get_active( ldd->prmType );
1004 gtk_widget_set_sensitive( GTK_WIDGET(ldd->prmVarFrame),
1005 index != GNC_FIXED );
1006}
1007
1008
1009static
1010void
1011loan_info_calc_update_cb( GtkWidget *w, gpointer user_data )
1012{
1013 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1014 GDate start, now;
1015 int i, totalVal, total, remain;
1016
1017 g_date_clear( &start, 1 );
1018 g_date_clear( &now, 1 );
1019 gnc_gdate_set_time64( &start, gnc_date_edit_get_date( ldd->prmStartDateGDE ) );
1020 gnc_gdate_set_time64( &now, gnc_time (NULL) );
1021 for ( i = 0; g_date_compare( &start, &now ) < 0; i++ )
1022 {
1023 g_date_add_months( &start, 1 );
1024 }
1025
1026 /* Get the correct, current value of the length spin. */
1027 totalVal = gtk_spin_button_get_value_as_int (ldd->prmLengthSpin);
1028 total = totalVal
1029 * ( gtk_combo_box_get_active( ldd->prmLengthType )
1030 == 1 ? 12 : 1 );
1031 remain = total - i;
1032 gtk_spin_button_set_value( ldd->prmRemainSpin, remain );
1033 gtk_widget_show( GTK_WIDGET(ldd->prmRemainSpin) );
1034}
1035
1036
1037void
1038loan_info_page_valid_cb (GtkWidget *widget, gpointer user_data )
1039{
1040 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1041 GtkAssistant *assistant = GTK_ASSISTANT(ldd->window);
1042 gint num = gtk_assistant_get_current_page (assistant);
1043 GtkWidget *page = gtk_assistant_get_nth_page (assistant, num);
1044
1045 gtk_assistant_set_page_complete (assistant, page,
1046 loan_info_page_complete (assistant, ldd));
1047}
1048
1049
1050static
1051gboolean
1052loan_info_page_complete( GtkAssistant *assistant, gpointer user_data )
1053{
1054 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1055 GNCPrintAmountInfo print_info;
1056 gnc_commodity *currency;
1057 gint result;
1058 gnc_numeric value;
1059
1060 ldd->ld.primaryAcct = gnc_account_sel_get_account( ldd->prmAccountGAS );
1061 /* Test for valid Account */
1062 if ( ldd->ld.primaryAcct == NULL )
1063 return FALSE;
1064
1065 /* Test for loan amount */
1066 currency = xaccAccountGetCommodity (ldd->ld.primaryAcct);
1067 print_info = gnc_commodity_print_info (currency, FALSE);
1068 gnc_amount_edit_set_print_info (GNC_AMOUNT_EDIT(ldd->prmOrigPrincGAE), print_info);
1069 gnc_amount_edit_set_fraction (GNC_AMOUNT_EDIT(ldd->prmOrigPrincGAE),
1070 gnc_commodity_get_fraction (currency));
1071
1072 result = gnc_amount_edit_expr_is_valid (GNC_AMOUNT_EDIT(ldd->prmOrigPrincGAE),
1073 &value, FALSE, nullptr);
1074 if (result == 1)
1075 return FALSE;
1076
1077 return TRUE;
1078}
1079
1080
1081static
1082void
1083loan_info_page_save( GtkAssistant *assistant, gpointer user_data )
1084{
1085 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1086
1087 ldd->ld.primaryAcct = gnc_account_sel_get_account( ldd->prmAccountGAS );
1088
1089 if ( ! ldd->ld.repPriAcct )
1090 {
1091 ldd->ld.repPriAcct = ldd->ld.primaryAcct;
1092 }
1093 ldd->ld.principal = gnc_amount_edit_get_amount( ldd->prmOrigPrincGAE );
1094 ldd->ld.interestRate = gtk_spin_button_get_value( ldd->prmIrateSpin );
1095 ldd->ld.rateType = static_cast<IRateType>( gtk_combo_box_get_active (ldd->prmIrateType ));
1096 ldd->ld.type = static_cast<LoanType>( gtk_combo_box_get_active( ldd->prmType ));
1097
1098 if ( ldd->ld.type != GNC_FIXED )
1099 {
1100 recurrenceListFree(&ldd->ld.loan_schedule);
1101 gnc_frequency_save_to_recurrence(ldd->prmVarGncFreq,
1102 &ldd->ld.loan_schedule,
1103 ldd->ld.varStartDate);
1104 }
1105
1106 /* start date */
1107 {
1108 time64 tmpTT;
1109 struct tm *tmpTm;
1110
1111 tmpTT = gnc_date_edit_get_date( ldd->prmStartDateGDE );
1112 tmpTm = gnc_localtime ( &tmpTT );
1113 if (tmpTm)
1114 {
1115 g_date_set_dmy( ldd->ld.startDate,
1116 tmpTm->tm_mday,
1117 static_cast<GDateMonth>(tmpTm->tm_mon + 1),
1118 (1900 + tmpTm->tm_year) );
1119 gnc_tm_free (tmpTm);
1120 }
1121 }
1122
1123 /* len / periods */
1124 {
1125 ldd->ld.perSize =
1126 (gtk_combo_box_get_active( ldd->prmLengthType )
1127 == GNC_MONTHS) ? GNC_MONTHS : GNC_YEARS;
1128 ldd->ld.numPer =
1129 gtk_spin_button_get_value_as_int( ldd->prmLengthSpin );
1130 ldd->ld.numMonRemain =
1131 gtk_spin_button_get_value_as_int( ldd->prmRemainSpin );
1132 }
1133}
1134
1135/************************************************************************/
1136
1137static
1138void
1139loan_opt_prep( GtkAssistant *assistant, gpointer user_data )
1140{
1141 int i;
1142 RepayOptUIData *rouid;
1143 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1144
1145 /* Save Previous Page ( Information ) */
1146 loan_info_page_save(assistant, ldd);
1147
1148 if ( ldd->ld.escrowAcct )
1149 {
1150 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(ldd->optEscrowCb), TRUE );
1151 gnc_account_sel_set_account( ldd->optEscrowGAS, ldd->ld.escrowAcct, FALSE );
1152 }
1153 for ( i = 0; i < ldd->ld.repayOptCount; i++ )
1154 {
1155 rouid = ldd->repayOptsUI[i];
1156 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(rouid->optCb),
1157 rouid->optData->enabled );
1158 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(rouid->escrowCb),
1159 rouid->optData->throughEscrowP
1160 && rouid->optData->enabled
1161 && ldd->ld.escrowAcct );
1162 gtk_widget_set_sensitive( GTK_WIDGET(rouid->escrowCb),
1163 rouid->optData->enabled
1164 && ldd->ld.escrowAcct );
1165 }
1166}
1167
1168
1169static
1170void
1171loan_opt_toggled_cb( GtkToggleButton *tb, gpointer user_data )
1172{
1173 RepayOptUIData *rouid;
1174
1175 rouid = (RepayOptUIData*)user_data;
1176 rouid->optData->enabled = gtk_toggle_button_get_active(tb);
1177}
1178
1179
1180static
1181void
1182loan_opt_consistency_cb( GtkToggleButton *tb, gpointer user_data )
1183{
1184 GtkToggleButton *escrowCb;
1185 RepayOptUIData *rouid;
1186
1187 rouid = (RepayOptUIData*)user_data;
1188 escrowCb = GTK_TOGGLE_BUTTON(rouid->escrowCb);
1189 /* make sure the escrow option is only selected if we're active. */
1190 gtk_toggle_button_set_active( escrowCb,
1191 gtk_toggle_button_get_active(
1192 GTK_TOGGLE_BUTTON(
1193 rouid->ldd->optEscrowCb) )
1194 && rouid->optData->throughEscrowP
1195 && gtk_toggle_button_get_active(tb) );
1196 /* make sure the escrow option is only sensitive if we're active, and
1197 * the escrow account is enabled */
1198 gtk_widget_set_sensitive( GTK_WIDGET(escrowCb),
1199 gtk_toggle_button_get_active(tb)
1200 && gtk_toggle_button_get_active(
1201 GTK_TOGGLE_BUTTON(rouid->ldd->optEscrowCb)) );
1202}
1203
1204
1205static
1206void
1207loan_opt_escrow_toggle_cb( GtkToggleButton *tb, gpointer user_data )
1208{
1209 int i;
1210 gboolean newState;
1211 RepayOptUIData *rouid;
1212 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1213 GtkAssistant *assistant = GTK_ASSISTANT(ldd->window);
1214 gint num = gtk_assistant_get_current_page (assistant);
1215 GtkWidget *page = gtk_assistant_get_nth_page (assistant, num);
1216
1217 newState = gtk_toggle_button_get_active(tb);
1218 gtk_widget_set_sensitive( GTK_WIDGET(ldd->optEscrowHBox), newState );
1219 /* Check for Valid Account if enabled */
1220 if (newState)
1221 {
1222 if (GNC_ACCOUNT_SEL( ldd->ld.escrowAcct) == NULL)
1223 gtk_assistant_set_page_complete (assistant, page, FALSE);
1224 }
1225 else
1226 {
1227 ldd->ld.escrowAcct = NULL;
1228 gnc_account_sel_set_account( GNC_ACCOUNT_SEL( ldd->optEscrowGAS), NULL , FALSE );
1229 gtk_assistant_set_page_complete (assistant, page, TRUE);
1230 }
1231
1232
1233 /* deal with escrow options. */
1234 for ( i = 0; i < ldd->ld.repayOptCount; i++ )
1235 {
1236 rouid = ldd->repayOptsUI[i];
1237 /* If we're going off, then uncheck and desensitize all escrow opts. */
1238 /* If we're going on, then sensitize all escrow opts. */
1239
1240 /* prevent the toggle handler from running and "trashing" the
1241 * state of the throughEscrowP selection */
1242 g_signal_handlers_block_by_func( rouid->escrowCb,
1243 (gpointer) loan_opt_escrow_toggled_cb,
1244 rouid );
1245 gtk_toggle_button_set_active(
1246 GTK_TOGGLE_BUTTON(rouid->escrowCb),
1247 ( newState
1248 && gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(rouid->optCb) )
1249 && rouid->optData->throughEscrowP ) );
1250 gtk_widget_set_sensitive(
1251 GTK_WIDGET(rouid->escrowCb),
1252 newState
1253 && gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(rouid->optCb) ) );
1254 g_signal_handlers_unblock_by_func( rouid->escrowCb,
1255 (gpointer) loan_opt_escrow_toggled_cb,
1256 rouid );
1257 if ( newState )
1258 {
1259 rouid->optData->from = ldd->ld.escrowAcct;
1260 }
1261 else
1262 {
1263 rouid->optData->from = NULL;
1264 }
1265 }
1266}
1267
1268
1269static
1270void
1271loan_opt_escrow_toggled_cb( GtkToggleButton *tb, gpointer user_data )
1272{
1273 RepayOptUIData *rouid;
1274
1275 rouid = (RepayOptUIData*)user_data;
1276 rouid->optData->throughEscrowP = gtk_toggle_button_get_active( tb );
1277}
1278
1279
1280void
1281loan_opt_page_valid_cb (GtkWidget *widget, gpointer user_data )
1282{
1283 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1284
1285 GtkAssistant *assistant = GTK_ASSISTANT(ldd->window);
1286 gint num = gtk_assistant_get_current_page (assistant);
1287 GtkWidget *page = gtk_assistant_get_nth_page (assistant, num);
1288
1289 gtk_assistant_set_page_complete (assistant, page,
1290 loan_opt_page_complete (assistant, ldd));
1291}
1292
1293
1294static
1295gboolean
1296loan_opt_page_complete( GtkAssistant *assistant, gpointer user_data )
1297{
1298 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1299
1300 if ( gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(ldd->optEscrowCb) ) )
1301 {
1302 ldd->ld.escrowAcct =
1303 gnc_account_sel_get_account( ldd->optEscrowGAS );
1304 /* Test for valid Account */
1305 if ( ldd->ld.escrowAcct == NULL )
1306 return FALSE;
1307 }
1308 else
1309 {
1310 ldd->ld.escrowAcct = NULL;
1311 }
1312 return TRUE;
1313}
1314
1315/************************************************************************/
1316
1317static void
1318update_repayment_formula_cb(GtkWidget *widget, gpointer user_data)
1319{
1320 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1321
1322 recurrenceListFree(&ldd->ld.repayment_schedule);
1323 gnc_frequency_save_to_recurrence(ldd->repGncFreq,
1324 &ldd->ld.repayment_schedule,
1325 ldd->ld.repStartDate);
1326
1327 ldd->ld.repAmount = loan_get_pmt_formula(ldd);
1328 if (!ldd->ld.repAmount.empty() )
1329 gtk_entry_set_text(ldd->repAmtEntry, ldd->ld.repAmount.c_str());
1330
1331}
1332
1333static void
1334loan_rep_prep( GtkAssistant *assistant, gpointer user_data )
1335{
1336 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1337 static gulong date_changed_handler_id = 0;
1338
1339 if (date_changed_handler_id)
1340 {
1341 g_signal_handler_disconnect(ldd->repGncFreq, date_changed_handler_id);
1342 date_changed_handler_id = 0;
1343 }
1344
1345 ldd->ld.repAmount = loan_get_pmt_formula(ldd);
1346
1347 if ( ldd->ld.repMemo )
1348 gtk_entry_set_text( ldd->repTxnName, ldd->ld.repMemo );
1349
1350 if (!ldd->ld.repAmount.empty() )
1351 gtk_entry_set_text(ldd->repAmtEntry, ldd->ld.repAmount.c_str());
1352
1353 gnc_account_sel_set_account( ldd->repAssetsFromGAS, ldd->ld.repFromAcct, FALSE );
1354 gnc_account_sel_set_account( ldd->repPrincToGAS, ldd->ld.repPriAcct, FALSE );
1355 gnc_account_sel_set_account( ldd->repIntToGAS, ldd->ld.repIntAcct, FALSE );
1356
1357 g_signal_handlers_block_by_func( ldd->repGncFreq,
1358 (gpointer) loan_rep_page_valid_cb, ldd );
1359 gnc_frequency_setup_recurrence(ldd->repGncFreq, ldd->ld.repayment_schedule,
1360 ldd->ld.repStartDate);
1361 date_changed_handler_id = g_signal_connect (ldd->repGncFreq, "changed",
1362 G_CALLBACK (update_repayment_formula_cb), ldd);
1363 g_signal_handlers_unblock_by_func( ldd->repGncFreq,
1364 (gpointer) loan_rep_page_valid_cb, ldd );
1365
1366 /* Find the first enabled option */
1367 {
1368 int i;
1369 for ( i = 0; // we can always start at 0, here.
1370 (i < ldd->ld.repayOptCount)
1371 && !ldd->ld.repayOpts[i]->enabled;
1372 i++ )
1373 ;
1374 if ( i < ldd->ld.repayOptCount )
1375 ldd->currentIdx = i;
1376 else
1377 ldd->currentIdx = -1;
1378 }
1379}
1380
1381
1382void
1383loan_rep_page_valid_cb (GtkWidget *widget, gpointer user_data )
1384{
1385 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1386 GtkAssistant *assistant = GTK_ASSISTANT(ldd->window);
1387 gint num = gtk_assistant_get_current_page (assistant);
1388 GtkWidget *page = gtk_assistant_get_nth_page (assistant, num);
1389
1390 gtk_assistant_set_page_complete (assistant, page,
1391 loan_rep_page_complete (assistant, ldd));
1392}
1393
1394
1395static
1396gboolean
1397loan_rep_page_complete( GtkAssistant *assistant, gpointer user_data )
1398{
1399 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1400
1401 ldd->ld.repFromAcct =
1402 gnc_account_sel_get_account( ldd->repAssetsFromGAS );
1403 /* Test for valid Account */
1404 if ( ldd->ld.repFromAcct == NULL )
1405 return FALSE;
1406
1407 ldd->ld.repPriAcct =
1408 gnc_account_sel_get_account( ldd->repPrincToGAS );
1409 /* Test for valid Account */
1410 if ( ldd->ld.repPriAcct == NULL )
1411 return FALSE;
1412
1413 ldd->ld.repIntAcct =
1414 gnc_account_sel_get_account( ldd->repIntToGAS );
1415 /* Test for valid Account */
1416 if ( ldd->ld.repIntAcct == NULL )
1417 return FALSE;
1418
1419 return TRUE;
1420}
1421
1422
1423static
1424void
1425loan_rep_page_save( GtkAssistant *assistant, gpointer user_data )
1426{
1427 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1428
1429 if ( ldd->ld.repMemo )
1430 g_free( ldd->ld.repMemo );
1431 ldd->ld.repMemo =
1432 gtk_editable_get_chars( GTK_EDITABLE(ldd->repTxnName), 0, -1 );
1433
1434
1435 ldd->ld.repAmount = gtk_entry_get_text(ldd->repAmtEntry);
1436
1437 ldd->ld.repFromAcct =
1438 gnc_account_sel_get_account( ldd->repAssetsFromGAS );
1439
1440 ldd->ld.repPriAcct =
1441 gnc_account_sel_get_account( ldd->repPrincToGAS );
1442
1443 ldd->ld.repIntAcct =
1444 gnc_account_sel_get_account( ldd->repIntToGAS );
1445}
1446
1447/************************************************************************/
1448
1449static
1450void
1451loan_pay_prep( GtkAssistant *assistant, gpointer user_data )
1452{
1453 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1454 RepayOptData *rod;
1455 GString *str;
1456
1457
1458 gint num = gtk_assistant_get_current_page (assistant);
1459 GtkWidget *page = gtk_assistant_get_nth_page (assistant, num);
1460
1461 /* Save Previous Page (Repayment) */
1462 loan_rep_page_save(assistant, ldd);
1463
1464 /* Step over page if no options enabled */
1465 if (ldd->currentIdx == -1 )
1466 {
1467 gtk_assistant_set_current_page (assistant, num + 1);
1468 }
1469 else
1470 {
1471 g_assert( ldd->currentIdx >= 0 );
1472 g_assert( ldd->currentIdx <= ldd->ld.repayOptCount );
1473
1474 rod = ldd->ld.repayOpts[ldd->currentIdx];
1475 str = g_string_sized_new( 32 );
1476 /* Translators: %s is "Taxes", or "Insurance", or similar */
1477 g_string_printf( str, _("Loan Repayment Option: \"%s\""), rod->name );
1478 gtk_assistant_set_page_title (assistant, page, str->str );
1479
1480 /* copy in the relevant data from the currently-indexed option. */
1481 gtk_entry_set_text( ldd->payTxnName, rod->txnMemo );
1482 g_string_printf( str, "%0.2f", rod->amount );
1483 gtk_entry_set_text( ldd->payAmtEntry, str->str );
1484
1485 gtk_widget_set_sensitive( GTK_WIDGET(ldd->payUseEscrow),
1486 (ldd->ld.escrowAcct != NULL) );
1487
1488 {
1489 g_signal_handlers_block_by_func( ldd->payUseEscrow,
1490 (gpointer) loan_pay_use_esc_toggle_cb,
1491 ldd );
1492
1493 loan_pay_use_esc_setup( ldd,
1494 (ldd->ld.escrowAcct != NULL)
1495 && rod->throughEscrowP );
1496 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(ldd->payUseEscrow),
1497 (rod->throughEscrowP
1498 && ldd->ld.escrowAcct != NULL) );
1499
1500 g_signal_handlers_unblock_by_func( ldd->payUseEscrow,
1501 (gpointer) loan_pay_use_esc_toggle_cb,
1502 ldd );
1503 }
1504
1505 {
1506 g_signal_handlers_block_by_func( ldd->paySpecSrcAcct,
1507 (gpointer) loan_pay_spec_src_toggle_cb,
1508 ldd );
1509 loan_pay_spec_src_setup( ldd, rod->specSrcAcctP );
1510 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(ldd->paySpecSrcAcct),
1511 rod->specSrcAcctP );
1512
1513 g_signal_handlers_unblock_by_func( ldd->paySpecSrcAcct,
1514 (gpointer) loan_pay_spec_src_toggle_cb,
1515 ldd );
1516 }
1517
1518 g_signal_handlers_block_by_func(ldd->payAcctToGAS,
1519 (gpointer) loan_pay_page_valid_cb, ldd );
1520 gnc_account_sel_set_account( ldd->payAcctToGAS, rod->to, FALSE );
1521 g_signal_handlers_unblock_by_func(ldd->payAcctToGAS,
1522 (gpointer) loan_pay_page_valid_cb, ldd );
1523
1524
1525 g_signal_handlers_block_by_func(ldd->payTxnFreqUniqRb,
1526 (gpointer) loan_pay_freq_toggle_cb, ldd );
1527 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(ldd->payTxnFreqPartRb), !rod->FreqUniq );
1528 gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(ldd->payTxnFreqUniqRb), rod->FreqUniq );
1529 g_signal_handlers_unblock_by_func(ldd->payTxnFreqUniqRb,
1530 (gpointer) loan_pay_freq_toggle_cb, ldd );
1531
1532 gtk_widget_set_sensitive( GTK_WIDGET(ldd->payFreqHBox), rod->FreqUniq );
1533
1534 if ( rod->FreqUniq )
1535 {
1536 g_signal_handlers_disconnect_by_func( ldd->payGncFreq,
1537 (gpointer) loan_pay_page_valid_cb, ldd );
1538 gtk_container_remove( GTK_CONTAINER(ldd->payFreqHBox), GTK_WIDGET(ldd->payGncFreq) );
1539 ldd->payGncFreq = NULL;
1540 ldd->payGncFreq = GNC_FREQUENCY(gnc_frequency_new_from_recurrence( rod->schedule, rod->startDate ));
1541 gtk_container_add( GTK_CONTAINER(ldd->payFreqHBox), GTK_WIDGET(ldd->payGncFreq) );
1542 g_signal_connect (ldd->payGncFreq, "changed",
1543 G_CALLBACK (loan_pay_page_valid_cb), ldd);
1544 }
1545 g_string_free( str, TRUE );
1546 loan_pay_page_valid_cb(GTK_WIDGET(ldd->window), ldd);
1547 }
1548}
1549
1550
1551void
1552loan_pay_page_valid_cb (GtkWidget *widget, gpointer user_data )
1553{
1554 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1555 GtkAssistant *assistant = GTK_ASSISTANT(ldd->window);
1556 gint num = gtk_assistant_get_current_page (assistant);
1557 GtkWidget *page = gtk_assistant_get_nth_page (assistant, num);
1558
1559 gtk_assistant_set_page_complete (assistant, page,
1560 ( loan_pay_complete (assistant, ldd) &&
1561 loan_pay_all_opt_valid (assistant, ldd )));
1562}
1563
1564
1565static
1566void
1567loan_pay_use_esc_setup( LoanAssistantData *ldd, gboolean newState )
1568{
1569 gtk_widget_set_sensitive( GTK_WIDGET(ldd->payEscToLabel), newState );
1570 gtk_widget_set_sensitive( GTK_WIDGET(ldd->payEscFromLabel), newState );
1571 if ( newState )
1572 {
1573 g_signal_handlers_block_by_func( ldd->payAcctEscToGAS,
1574 (gpointer) loan_pay_page_valid_cb, ldd );
1575 g_signal_handlers_block_by_func( ldd->payAcctEscFromGAS,
1576 (gpointer) loan_pay_page_valid_cb, ldd );
1577 gnc_account_sel_set_account( ldd->payAcctEscToGAS, ldd->ld.escrowAcct, FALSE );
1578 gnc_account_sel_set_account( ldd->payAcctEscFromGAS, ldd->ld.escrowAcct, FALSE );
1579 g_signal_handlers_unblock_by_func( ldd->payAcctEscToGAS,
1580 (gpointer) loan_pay_page_valid_cb, ldd );
1581 g_signal_handlers_unblock_by_func( ldd->payAcctEscFromGAS,
1582 (gpointer) loan_pay_page_valid_cb, ldd );
1583 }
1584}
1585
1586
1587static
1588void
1589loan_pay_use_esc_toggle_cb( GtkToggleButton *tb, gpointer user_data )
1590{
1591 gboolean newState;
1592 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1593
1594 newState = gtk_toggle_button_get_active( tb );
1595 loan_pay_use_esc_setup( ldd, newState );
1596}
1597
1598
1599static
1600void
1601loan_pay_spec_src_setup( LoanAssistantData *ldd, gboolean newState )
1602{
1603 gtk_widget_set_sensitive( GTK_WIDGET(ldd->payAcctFromLabel), newState );
1604 gtk_widget_set_sensitive( GTK_WIDGET(ldd->payAcctFromGAS), newState );
1605 if ( newState )
1606 {
1607 g_signal_handlers_block_by_func( ldd->payAcctFromGAS,
1608 (gpointer) loan_pay_page_valid_cb, ldd );
1609 gnc_account_sel_set_account( ldd->payAcctFromGAS, ldd->ld.repayOpts[ldd->currentIdx]->from, FALSE );
1610 g_signal_handlers_unblock_by_func( ldd->payAcctFromGAS,
1611 (gpointer) loan_pay_page_valid_cb, ldd );
1612 }
1613 else
1614 {
1615 g_signal_handlers_block_by_func( ldd->payAcctFromGAS,
1616 (gpointer) loan_pay_page_valid_cb, ldd );
1617 gnc_account_sel_set_account( ldd->payAcctFromGAS, NULL, FALSE );
1618 ldd->ld.repayOpts[ldd->currentIdx]->from = NULL;
1619 g_signal_handlers_unblock_by_func( ldd->payAcctFromGAS,
1620 (gpointer) loan_pay_page_valid_cb, ldd );
1621 }
1622}
1623
1624
1625static
1626void
1627loan_pay_spec_src_toggle_cb( GtkToggleButton *tb, gpointer user_data )
1628{
1629 gboolean newState;
1630 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1631
1632 newState = gtk_toggle_button_get_active( tb );
1633 loan_pay_spec_src_setup( ldd, newState );
1634}
1635
1636
1637static
1638void
1639loan_pay_freq_toggle_cb( GtkToggleButton *tb, gpointer user_data )
1640{
1641 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1642 RepayOptData *rod;
1643
1644 g_assert( ldd->currentIdx >= 0 );
1645 g_assert( ldd->currentIdx <= ldd->ld.repayOptCount );
1646
1647 rod = ldd->ld.repayOpts[ldd->currentIdx];
1648
1649 rod->FreqUniq = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(ldd->payTxnFreqUniqRb) );
1650 gtk_widget_set_sensitive( GTK_WIDGET(ldd->payFreqHBox), rod->FreqUniq );
1651
1652 if ( rod->FreqUniq )
1653 {
1654 if ( rod->schedule == NULL )
1655 {
1656 Recurrence *r = g_new0(Recurrence, 1);
1657
1658 recurrenceSet(r, 1, PERIOD_MONTH, ldd->ld.startDate, WEEKEND_ADJ_NONE);
1659 rod->schedule = g_list_append(rod->schedule, r);
1660 }
1661 if ( rod->startDate == NULL )
1662 {
1663 rod->startDate = g_date_new();
1664 *rod->startDate = *ldd->ld.startDate;
1665 }
1666 g_signal_handlers_block_by_func( ldd->payGncFreq,
1667 (gpointer) loan_pay_page_valid_cb, ldd );
1668 gnc_frequency_setup_recurrence(ldd->payGncFreq, rod->schedule, rod->startDate);
1669 g_signal_handlers_unblock_by_func( ldd->payGncFreq,
1670 (gpointer) loan_pay_page_valid_cb, ldd );
1671 }
1672 else
1673 {
1674 if (rod->schedule)
1675 {
1676 recurrenceListFree(&rod->schedule);
1677 }
1678 if ( rod->startDate )
1679 {
1680 g_date_free( rod->startDate );
1681 rod->startDate = NULL;
1682 }
1683 }
1684}
1685
1686
1687static
1688void
1689loan_pay_next_button_cb( GtkButton *button, gpointer user_data )
1690{
1691 int i;
1692 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1693
1694 /* save current data */
1695 if ( loan_pay_complete ( GTK_ASSISTANT(ldd->window), user_data ) != FALSE )
1696 {
1697 /* Go through opts list and select next enabled option. */
1698 for ( i = ldd->currentIdx + 1;
1699 (i < ldd->ld.repayOptCount)
1700 && !ldd->ld.repayOpts[i]->enabled; i++ )
1701 ;
1702 if ( i < ldd->ld.repayOptCount )
1703 {
1704 ldd->currentIdx = i;
1705 loan_pay_prep( GTK_ASSISTANT(ldd->window), user_data );
1706 }
1707 }
1708}
1709
1710
1711static
1712void
1713loan_pay_back_button_cb( GtkButton *button, gpointer user_data )
1714{
1715 int i;
1716 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1717
1718 /* save current data */
1719 if ( loan_pay_complete ( GTK_ASSISTANT(ldd->window), user_data ) != FALSE)
1720 {
1721 /* go back through opts list and select next enabled options. */
1722 for ( i = ldd->currentIdx - 1;
1723 (i > -1) && !ldd->ld.repayOpts[i]->enabled;
1724 i-- )
1725 ;
1726 if ( i >= 0 )
1727 {
1728 ldd->currentIdx = i;
1729 loan_pay_prep( GTK_ASSISTANT(ldd->window), user_data );
1730 }
1731 }
1732}
1733
1734
1735static
1736gboolean
1737loan_pay_all_opt_valid ( GtkAssistant *assistant, gpointer user_data )
1738{
1739 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1740 int i;
1741 gboolean all_valid;
1742 all_valid = FALSE;
1743
1744 /* Go through all option pages checking for valid enabled pages */
1745 for ( i = 0; (i < ldd->ld.repayOptCount); i++ )
1746 {
1747 if (ldd->ld.repayOpts[i]->enabled)
1748 {
1749 if (ldd->ld.repayOpts[i]->optValid)
1750 all_valid = TRUE;
1751 else
1752 all_valid = FALSE;
1753 }
1754 }
1755 return all_valid;
1756}
1757
1758
1759static
1760gboolean
1761loan_pay_complete( GtkAssistant *assistant, gpointer user_data )
1762{
1763 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1764 RepayOptData *rod;
1765
1766 g_assert( ldd->currentIdx >= 0 );
1767 g_assert( ldd->currentIdx <= ldd->ld.repayOptCount );
1768 rod = ldd->ld.repayOpts[ ldd->currentIdx ];
1769
1770 if ( rod->txnMemo != NULL )
1771 {
1772 g_free( rod->txnMemo );
1773 }
1774 rod->txnMemo = g_strdup (gtk_entry_get_text (GTK_ENTRY(ldd->payTxnName)));
1775 rod->amount = (float)strtod (gtk_entry_get_text (GTK_ENTRY(ldd->payAmtEntry)), NULL);
1776
1777 rod->specSrcAcctP =
1778 gtk_toggle_button_get_active(
1779 GTK_TOGGLE_BUTTON(ldd->paySpecSrcAcct) );
1780
1781 /* Test for Valid From Account */
1782 if ( rod->specSrcAcctP )
1783 {
1784 rod->from = gnc_account_sel_get_account( ldd->payAcctFromGAS );
1785 if ( rod->from == NULL )
1786 return FALSE;
1787 }
1788
1789 /* Test for Valid To Account */
1790 rod->to = gnc_account_sel_get_account( ldd->payAcctToGAS );
1791 if ( rod->to == NULL )
1792 return FALSE;
1793
1794 /* Set Page Valid */
1795 rod->optValid = TRUE;
1796
1797 /* If Uniq Freq, then save to recurrence */
1798 if ( rod->FreqUniq )
1799 {
1800 if ( rod->startDate == NULL )
1801 {
1802 rod->startDate = g_date_new();
1803 }
1804 recurrenceListFree(&rod->schedule);
1805 gnc_frequency_save_to_recurrence(ldd->payGncFreq, &rod->schedule, rod->startDate);
1806
1807 if (! rod->schedule )
1808 {
1809 return FALSE;
1810 }
1811 }
1812 return TRUE;
1813}
1814
1815/************************************************************************/
1816
1817static
1818void
1819loan_rev_prep( GtkAssistant *assistant, gpointer user_data )
1820{
1821 /* 3, here, does not include the Date column. */
1822 static const int BASE_COLS = 3;
1823 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1824 GtkListStore *store;
1825 GtkCellRenderer *renderer;
1826 GtkTreeViewColumn *column;
1827 GType *types;
1828 int i;
1829 int col = 1;
1830
1831 /* Make sure we saved last Payment Option */
1832 if (ldd->currentIdx != -1)
1833 loan_pay_complete(assistant, ldd);
1834
1835 /* Cleanup old view */
1836 if ( ldd->revView != NULL )
1837 {
1838 gtk_widget_destroy( GTK_WIDGET(ldd->revView) );
1839 ldd->revView = NULL;
1840 }
1841
1842 ldd->ld.revNumPmts = BASE_COLS;
1843 /* Get the correct number of repayment columns. */
1844 for ( i = 0; i < ldd->ld.repayOptCount; i++ )
1845 {
1846 ldd->ld.revRepayOptToColMap[i] = -1;
1847 if ( ! ldd->ld.repayOpts[i]->enabled )
1848 {
1849 continue;
1850 }
1851 /* not '+1' = there is no date column to be accounted for in
1852 * the mapping. */
1853 ldd->ld.revRepayOptToColMap[i] = ldd->ld.revNumPmts;
1854 ldd->ld.revNumPmts += 1;
1855 }
1856 /* '+1' for leading date col */
1857 types = g_new( GType, ldd->ld.revNumPmts + 1 );
1858 for ( i = 0; i < ldd->ld.revNumPmts + 1; i++ )
1859 types[i] = G_TYPE_STRING;
1860 store = gtk_list_store_newv(ldd->ld.revNumPmts + 1, types);
1861 g_free(types);
1862
1863 ldd->revView = GTK_TREE_VIEW(
1864 gtk_tree_view_new_with_model( GTK_TREE_MODEL(store) ));
1865 g_object_unref(store);
1866
1867 // Set grid lines option to preference
1868 gtk_tree_view_set_grid_lines (GTK_TREE_VIEW(ldd->revView), gnc_tree_view_get_grid_lines_pref ());
1869
1870 renderer = gtk_cell_renderer_text_new();
1871 column = gtk_tree_view_column_new_with_attributes(_("Date"), renderer,
1872 "text", LOAN_COL_DATE,
1873 nullptr);
1874 gtk_tree_view_append_column(ldd->revView, column);
1875
1876 renderer = gtk_cell_renderer_text_new();
1877 column = gtk_tree_view_column_new_with_attributes(_("Payment"), renderer,
1878 "text", LOAN_COL_PAYMENT,
1879 nullptr);
1880 gtk_tree_view_append_column(ldd->revView, column);
1881
1882 renderer = gtk_cell_renderer_text_new();
1883 column = gtk_tree_view_column_new_with_attributes(_("Principal"), renderer,
1884 "text", LOAN_COL_PRINCIPAL,
1885 nullptr);
1886 gtk_tree_view_append_column(ldd->revView, column);
1887
1888 renderer = gtk_cell_renderer_text_new();
1889 column = gtk_tree_view_column_new_with_attributes(_("Interest"), renderer,
1890 "text", LOAN_COL_INTEREST,
1891 nullptr);
1892 gtk_tree_view_append_column(ldd->revView, column);
1893
1894 /* move the appropriate names over into the title array */
1895 {
1896 for ( i = 0; i < ldd->ld.repayOptCount; i++ )
1897 {
1898 if ( ldd->ld.revRepayOptToColMap[i] == -1 )
1899 {
1900 continue;
1901 }
1902 renderer = gtk_cell_renderer_text_new();
1903 column = gtk_tree_view_column_new_with_attributes
1904 (ldd->ld.repayOpts[i]->name, renderer,
1905 "text", LOAN_COL_INTEREST + col,
1906 nullptr);
1907 gtk_tree_view_append_column(ldd->revView, column);
1908 col++;
1909 }
1910 }
1911
1912 gtk_container_add( GTK_CONTAINER(ldd->revScrollWin),
1913 GTK_WIDGET(ldd->revView) );
1914 gtk_widget_show( GTK_WIDGET(ldd->revView) );
1915
1916 loan_rev_recalc_schedule( ldd );
1917
1918 {
1919 GDate start, end;
1920 g_date_clear( &start, 1 );
1921 g_date_clear( &end, 1 );
1922 loan_rev_get_dates( ldd, &start, &end );
1923 loan_rev_update_view( ldd, &start, &end );
1924 }
1925}
1926
1927
1928static
1929void
1930loan_rev_range_opt_changed_cb( GtkComboBox *combo, gpointer user_data )
1931{
1932 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1933 int opt;
1934
1935 opt = gtk_combo_box_get_active( ldd->revRangeOpt );
1936 gtk_widget_set_sensitive( GTK_WIDGET(ldd->revDateFrame),
1937 (opt == CUSTOM) );
1938 {
1939 GDate start, end;
1940 g_date_clear( &start, 1 );
1941 g_date_clear( &end, 1 );
1942 loan_rev_get_dates( ldd, &start, &end );
1943 loan_rev_update_view( ldd, &start, &end );
1944 }
1945}
1946
1947
1948static
1949void
1950loan_rev_range_changed_cb( GNCDateEdit *gde, gpointer user_data )
1951{
1952 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
1953 {
1954 GDate start, end;
1955 g_date_clear( &start, 1 );
1956 g_date_clear( &end, 1 );
1957 loan_rev_get_dates( ldd, &start, &end );
1958 loan_rev_update_view( ldd, &start, &end );
1959 }
1960}
1961
1962
1963static
1964void
1965loan_rev_get_loan_range( LoanAssistantData *ldd, GDate *start, GDate *end )
1966{
1967 int monthsTotal;
1968 struct tm *endDateMath;
1969
1970 *start = *ldd->ld.startDate;
1971
1972 endDateMath = g_new0( struct tm, 1 );
1973 g_date_to_struct_tm (ldd->ld.startDate, endDateMath);
1974 monthsTotal = ( ldd->ld.numPer
1975 * ( ldd->ld.perSize == GNC_MONTHS ? 1 : 12 ) );
1976 endDateMath->tm_mon += monthsTotal;
1977 gnc_gdate_set_time64 (end, gnc_mktime (endDateMath));
1978 g_date_subtract_days( end, 1 );
1979 g_free (endDateMath);
1980}
1981
1982
1983static
1984void
1985loan_rev_get_dates( LoanAssistantData *ldd, GDate *start, GDate *end )
1986{
1987 int range = gtk_combo_box_get_active( ldd->revRangeOpt );
1988 switch ( range )
1989 {
1990 case CURRENT_YEAR:
1991 gnc_gdate_set_time64( start, gnc_time (NULL) );
1992 g_date_set_dmy( start, 1, G_DATE_JANUARY, g_date_get_year( start ) );
1993 g_date_set_dmy( end, 31, G_DATE_DECEMBER, g_date_get_year( start ) );
1994 break;
1995 case NOW_PLUS_ONE:
1996 gnc_gdate_set_time64( start, gnc_time (NULL) );
1997 *end = *start;
1998 g_date_add_years( end, 1 );
1999 break;
2000 case WHOLE_LOAN:
2001 loan_rev_get_loan_range( ldd, start, end );
2002 break;
2003 case CUSTOM:
2004 gnc_gdate_set_time64( start,
2005 gnc_date_edit_get_date( ldd->revStartDate ) );
2007 gnc_date_edit_get_date( ldd->revEndDate ) );
2008 break;
2009 default:
2010 PERR( "Unknown review date range option %d", range );
2011 break;
2012 }
2013
2014}
2015
2016
2017static
2018void
2019loan_rev_sched_list_free( gpointer data, gpointer user_data )
2020{
2021 RevRepaymentRow *rrr = (RevRepaymentRow*)data;
2022 g_free( rrr->numCells );
2023 g_free( rrr );
2024}
2025
2026
2027static
2028void
2029loan_rev_hash_to_list( gpointer key, gpointer val, gpointer user_data )
2030{
2031 GList **l = (GList**)user_data;
2032 RevRepaymentRow *rrr;
2033 if ( !key || !val )
2034 {
2035 DEBUG( "%.8x, %.8x",
2036 GPOINTER_TO_UINT(key),
2037 GPOINTER_TO_UINT(val));
2038 return;
2039 }
2040 rrr = g_new0( RevRepaymentRow, 1 );
2041 rrr->date = *(GDate*)key;
2042 rrr->numCells = (gnc_numeric*)val;
2043 *l = g_list_append( *l, (gpointer)rrr );
2044}
2045
2046
2047static
2048void
2049loan_rev_hash_free_date_keys( gpointer key, gpointer val, gpointer user_data )
2050{
2051 g_free( (GDate*)key );
2052}
2053
2054
2055static
2056void
2057loan_rev_recalc_schedule( LoanAssistantData *ldd )
2058{
2059 GDate start, end;
2060 gnc_numeric *rowNumData;
2061 GHashTable *repayment_schedule;
2062
2063 g_date_clear( &start, 1 );
2064 g_date_clear( &end, 1 );
2065 loan_rev_get_loan_range( ldd, &start, &end );
2066
2067 /* The repayment_schedule is a hash of GDates to
2068 * row-of-gnc_numeric[N] data, where N is the number of columns as
2069 * determined by the _prep function, and stored in
2070 * LoanData::revNumPmts. */
2071 repayment_schedule = g_hash_table_new(gnc_gdate_hash, gnc_gdate_equal);
2072
2073 /* Do the master repayment */
2074 {
2075 GDate curDate, nextDate;
2076 int i;
2077 GHashTable *ivar;
2078
2079 auto pmtFormula = loan_get_pmt_formula(ldd);
2080 auto ppmtFormula = loan_get_ppmt_formula(ldd);
2081 auto ipmtFormula = loan_get_ipmt_formula(ldd);
2082
2083 ivar = g_hash_table_new( g_str_hash, g_str_equal );
2084 g_date_clear( &curDate, 1 );
2085 curDate = start;
2086 g_date_subtract_days( &curDate, 1 );
2087 g_date_clear(&nextDate, 1);
2088 recurrenceListNextInstance(ldd->ld.repayment_schedule, &curDate, &nextDate);
2089 for ( i = 1;
2090 g_date_valid( &nextDate )
2091 && g_date_compare( &nextDate, &end ) <= 0 ;
2092 i++,
2093 curDate = nextDate,
2094 recurrenceListNextInstance(ldd->ld.repayment_schedule,
2095 &curDate, &nextDate))
2096 {
2097 gnc_numeric ival;
2098 gnc_numeric val;
2099 char *eloc;
2100 rowNumData =
2101 (gnc_numeric*)g_hash_table_lookup( repayment_schedule,
2102 &nextDate );
2103 if ( rowNumData == NULL)
2104 {
2105 int j;
2106 GDate *dateKeyCopy = g_date_new();
2107
2108 *dateKeyCopy = nextDate;
2109 rowNumData = g_new0( gnc_numeric, ldd->ld.revNumPmts );
2110 g_assert( rowNumData != NULL );
2111 for ( j = 0; j < ldd->ld.revNumPmts; j++ )
2112 {
2113 rowNumData[j] = gnc_numeric_error( GNC_ERROR_ARG );
2114 }
2115 g_hash_table_insert( repayment_schedule,
2116 (gpointer)dateKeyCopy,
2117 (gpointer)rowNumData );
2118 }
2119
2120 /* evaluate the expressions given the correct
2121 * sequence number i */
2122 ival = gnc_numeric_create( i, 1 );
2123 g_hash_table_insert( ivar, (gpointer) "i", &ival );
2124
2125 if ( ! gnc_exp_parser_parse_separate_vars(
2126 pmtFormula.c_str(), &val, &eloc, ivar ) )
2127 {
2128 PERR( "pmt Parsing error at %s", eloc );
2129 continue;
2130 }
2131 val = gnc_numeric_convert( val, 100, GNC_HOW_RND_ROUND_HALF_UP );
2132 rowNumData[0] = val;
2133
2134 if ( ! gnc_exp_parser_parse_separate_vars(
2135 ppmtFormula.c_str(), &val, &eloc, ivar ) )
2136 {
2137 PERR( "ppmt Parsing error at %s", eloc );
2138 continue;
2139 }
2140 val = gnc_numeric_convert( val, 100, GNC_HOW_RND_ROUND_HALF_UP );
2141 rowNumData[1] = val;
2142
2143 if ( ! gnc_exp_parser_parse_separate_vars(
2144 ipmtFormula.c_str(), &val, &eloc, ivar ) )
2145 {
2146 PERR( "ipmt Parsing error at %s", eloc );
2147 continue;
2148 }
2149 val = gnc_numeric_convert( val, 100, GNC_HOW_RND_ROUND_HALF_UP );
2150 rowNumData[2] = val;
2151 }
2152
2153 g_hash_table_destroy( ivar );
2154 }
2155
2156 /* Process any other enabled payments. */
2157 {
2158 int i;
2159 GDate curDate, nextDate;
2160 GList *schedule;
2161
2162 for ( i = 0; i < ldd->ld.repayOptCount; i++ )
2163 {
2164 if ( ! ldd->ld.repayOpts[i]->enabled )
2165 continue;
2166
2167 schedule
2168 = ( ldd->ld.repayOpts[i]->schedule != NULL
2169 ? ldd->ld.repayOpts[i]->schedule
2170 : ldd->ld.repayment_schedule );
2171
2172 g_date_clear( &curDate, 1 );
2173 curDate = start;
2174 g_date_subtract_days( &curDate, 1 );
2175 g_date_clear(&nextDate, 1);
2176 recurrenceListNextInstance(schedule, &curDate, &nextDate );
2177 for ( ; g_date_valid( &nextDate )
2178 && g_date_compare( &nextDate, &end ) <= 0;
2179 curDate = nextDate,
2180 recurrenceListNextInstance(
2181 schedule, &curDate, &nextDate ) )
2182 {
2183 gint gncn_how =
2186 gnc_numeric val;
2187 rowNumData = (gnc_numeric*)g_hash_table_lookup( repayment_schedule,
2188 &nextDate );
2189 if ( rowNumData == NULL )
2190 {
2191 int j;
2192 GDate *dateKeyCopy = g_date_new();
2193
2194 *dateKeyCopy = nextDate;
2195 rowNumData = g_new0( gnc_numeric, ldd->ld.revNumPmts );
2196 g_assert( rowNumData != NULL );
2197 for ( j = 0; j < ldd->ld.revNumPmts; j++ )
2198 {
2199 rowNumData[j] = gnc_numeric_error( GNC_ERROR_ARG );
2200 }
2201 g_hash_table_insert( repayment_schedule,
2202 (gpointer)dateKeyCopy,
2203 (gpointer)rowNumData );
2204 }
2205
2206 val = double_to_gnc_numeric( (double)ldd->ld
2207 .repayOpts[i]
2208 ->amount,
2209 100, gncn_how );
2210 rowNumData[ ldd->ld.revRepayOptToColMap[i] ]
2211 = val;
2212 }
2213 }
2214 }
2215
2216 /* Convert the GHashTable into a sorted GList in the LoanData */
2217 {
2218 if ( ldd->ld.revSchedule != NULL )
2219 {
2220 g_list_foreach( ldd->ld.revSchedule,
2221 loan_rev_sched_list_free,
2222 NULL );
2223 g_list_free( ldd->ld.revSchedule );
2224 ldd->ld.revSchedule = NULL;
2225 }
2226 g_hash_table_foreach( repayment_schedule, loan_rev_hash_to_list,
2227 &ldd->ld.revSchedule );
2228 g_hash_table_foreach( repayment_schedule, loan_rev_hash_free_date_keys,
2229 NULL );
2230 g_hash_table_destroy( repayment_schedule );
2231 ldd->ld.revSchedule =
2232 g_list_sort( ldd->ld.revSchedule, (GCompareFunc)g_date_compare );
2233 }
2234}
2235
2236
2237static
2238void
2239loan_rev_update_view( LoanAssistantData *ldd, GDate *start, GDate *end )
2240{
2241 static const gchar *NO_AMT_CELL_TEXT = " ";
2242 GList *l;
2244 GtkListStore *store;
2245 GtkTreeIter iter;
2246
2247 pai = gnc_default_price_print_info(NULL);
2248 pai.min_decimal_places = 2;
2249
2250 store = GTK_LIST_STORE(gtk_tree_view_get_model( ldd->revView ));
2251
2252 gtk_list_store_clear( store );
2253
2254 for ( l = ldd->ld.revSchedule; l != NULL; l = l->next )
2255 {
2256 int i;
2257 gchar tmpBuf[64];
2258 RevRepaymentRow *rrr = (RevRepaymentRow*)l->data;
2259
2260 if ( g_date_compare( &rrr->date, start ) < 0 )
2261 continue;
2262 if ( g_date_compare( &rrr->date, end ) > 0 )
2263 continue; /* though we can probably return, too. */
2264
2265 gtk_list_store_append(store, &iter);
2266
2267 qof_print_gdate( tmpBuf, MAX_DATE_LENGTH, &rrr->date );
2268 gtk_list_store_set( store, &iter, LOAN_COL_DATE, tmpBuf, -1 );
2269
2270 for ( i = 0; i < ldd->ld.revNumPmts; i++ )
2271 {
2272 int numPrinted;
2273 if ( gnc_numeric_check( rrr->numCells[i] )
2274 == GNC_ERROR_ARG )
2275 {
2276 /* '+1' for the date cell */
2277 gtk_list_store_set( store, &iter,
2278 i + 1, NO_AMT_CELL_TEXT,
2279 -1);
2280 continue;
2281 }
2282
2283 numPrinted = xaccSPrintAmount( tmpBuf, rrr->numCells[i], pai );
2284 g_assert( numPrinted < 50 );
2285 /* '+1' for the date cell */
2286 gtk_list_store_set( store, &iter,
2287 i + 1, tmpBuf,
2288 -1);
2289 }
2290
2291 }
2292}
2293
2294/************************* Worker functions *****************************/
2295
2296/* From https://bugs.gnucash.org/show_bug.cgi?id=799582#c0:
2297 * INPUTS
2298 * n = number of payments per year (pmt_periods)
2299 * i = stated annual interest rate
2300 * m = number of compounding periods per year (comp_periods)
2301 *
2302 * OUTPUT
2303 * r = effective interest rate per payment
2304 * r = { [ 1 + (i / m) ] ^ (m / n) } - 1
2305 */
2306
2307gfloat loan_apr_to_simple_formula (double rate, double pmt_periods, double comp_periods)
2308{
2309 /* float percent_to_frac; - redundant */
2310 gfloat simple_rate;
2311 /* percent_to_frac= compounding_periods/100; - redundant */
2312 simple_rate = pow(1 + (rate / comp_periods), comp_periods / pmt_periods) - 1;
2313 return (simple_rate);
2314}
2315
2316using boost::locale::conv::utf_to_utf;
2317
2318// Define a monetary facet template for printing monetary values
2319// with a custom precision <prec>
2320// This will be used to generate the loan formulas, which use 5 decimal places
2321// for the loan percentage and 2 decimal places for all other numbers.
2322
2323// Note this facet is wchar_t based: some locales have non-ascii thousands
2324// separators and these can't be handled by a char based moneypunct facet.
2325
2326template<int prec>
2327struct cust_prec_punct : std::moneypunct_byname<wchar_t, false> {
2328 cust_prec_punct(const char* name) : moneypunct_byname(name) {}
2329 int do_frac_digits() const { return prec; }
2330};
2331
2332// Convert a double to a string hardcoding <prec> decimal places
2333template<int prec>
2334std::string to_str_with_prec (const gdouble val)
2335{
2336#ifdef __MINGW32__
2337 NUMBERFMTW numfmt;
2338 LCID lcid = GetThreadLocale();
2339 DWORD numval;
2340
2341 numfmt.NumDigits = prec;
2342 GetLocaleInfoW(lcid, LOCALE_RETURN_NUMBER|LOCALE_ILZERO, (LPWSTR)&numval,
2343 sizeof(numval)/sizeof(wchar_t));
2344 numfmt.LeadingZero = numval;
2345 wchar_t grouping[10];
2346 GetLocaleInfoW(lcid, LOCALE_SGROUPING, grouping,
2347 sizeof(grouping)/sizeof(wchar_t));
2348 auto semi = wcschr(grouping, ';');
2349 *semi = 0;
2350 numfmt.Grouping = _wtoi(grouping);
2351 wchar_t decsep[4];
2352 GetLocaleInfoW(lcid, LOCALE_SDECIMAL, decsep,
2353 sizeof(decsep)/sizeof(wchar_t) );
2354 numfmt.lpDecimalSep = decsep;
2355 wchar_t thousep[4];
2356 GetLocaleInfoW(lcid, LOCALE_STHOUSAND, thousep,
2357 sizeof(thousep)/sizeof(wchar_t));
2358 numfmt.lpThousandSep = thousep;
2359 GetLocaleInfoW(lcid, LOCALE_RETURN_NUMBER|LOCALE_INEGNUMBER,
2360 (LPWSTR)&numval, sizeof(numval)/sizeof(wchar_t));
2361 numfmt.NegativeOrder = numval;
2362//Can't use std::to_wstring, it localizes with a C function.
2363 std::wstringstream valstr;
2364 valstr << val;
2365 int size = GetNumberFormatW(lcid, 0, valstr.str().c_str(),
2366 &numfmt, nullptr, 0);
2367 wchar_t* buf = static_cast<wchar_t*>(malloc(sizeof(wchar_t) * size));
2368 GetNumberFormatW(lcid, 0, valstr.str().c_str(), &numfmt, buf, size);
2369#pragma GCC diagnostic push
2370#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
2371 std::wstring_convert<std::codecvt_utf8_utf16<wchar_t>, wchar_t> conv;
2372#pragma GCC diagnostic pop
2373 std::string result = conv.to_bytes(buf);
2374 free(buf);
2375 return result;
2376#else
2377 auto app_loc{gnc_get_locale()};
2378 if (std::use_facet<std::moneypunct<wchar_t>>(app_loc).curr_symbol().empty())
2379 {
2380 /* put_money in this locale will probably return an empty
2381 * string. Use floating point manipulators instead.
2382 */
2383 std::stringstream valstr;
2384 valstr << std::fixed << std::showpoint << std::setprecision(prec)
2385 << val;
2386 return valstr.str();
2387 }
2388
2389 auto loc = std::locale(app_loc, new cust_prec_punct<prec>(""));
2390 std::wstringstream valstr;
2391 valstr.imbue(loc);
2392 valstr << std::put_money(val * pow(10, prec));
2393 return utf_to_utf<char>(valstr.str());
2394#endif
2395}
2396
2397static std::tuple<double, double>
2398periods_per_year( LoanAssistantData *ldd)
2399{
2400 double ppy = 0.0, periods = 1.0;
2401 auto recurrences = ldd->ld.repayment_schedule;
2402 for (auto node = recurrences; node; node = g_list_next(node))
2403 {
2404 auto recurrence = static_cast<Recurrence*>(node->data);
2405 auto period_type = recurrenceGetPeriodType(recurrence);
2406 auto multiplier = recurrenceGetMultiplier(recurrence);
2407 if (multiplier < 1)
2408 multiplier = 1;
2409
2410 switch(period_type)
2411 {
2412 case PERIOD_YEAR:
2413 ppy += 1.0 / multiplier;
2414 break;
2415 case PERIOD_DAY:
2416 ppy = 365 / multiplier;
2417 break;
2418 case PERIOD_WEEK:
2419 ppy += 52.0 / multiplier;
2420 break;
2421 case PERIOD_MONTH:
2422 case PERIOD_END_OF_MONTH:
2423 case PERIOD_NTH_WEEKDAY:
2424 case PERIOD_LAST_WEEKDAY:
2425 ppy += 12.0 / multiplier;
2426 break;
2427 case PERIOD_ONCE:
2428 default:
2429 break;
2430 }
2431 }
2432
2433 auto months = (ldd->ld.numPer * (ldd->ld.perSize == GNC_MONTHS ? 1 : 12));
2434 if ((int)(months * ppy / 12) > 1)
2435 periods = (int)(months * ppy / 12) * 1.0;
2436
2437 return {ppy, periods};
2438}
2439
2440static std::string
2441loan_get_formula_internal( LoanAssistantData *ldd, const gchar *tpl )
2442{
2443 g_assert( ldd != NULL );
2444
2445 auto [ppy, periods] = periods_per_year(ldd);
2446 gdouble pass_thru_rate = ldd->ld.interestRate / 100.0;
2447 auto principal = gnc_numeric_to_double(ldd->ld.principal);
2448
2449 gdouble period_rate;
2450 auto rate_case = ldd->ld.rateType;
2451 switch (rate_case)
2452 {
2453 case GNC_IRATE_SIMPLE:
2454 period_rate = pass_thru_rate;
2455 break;
2456 case GNC_IRATE_APR_DAILY:
2457 period_rate = loan_apr_to_simple_formula (pass_thru_rate, ppy, 365);
2458 break;
2459 case GNC_IRATE_APR_WEEKLY:
2460 period_rate = loan_apr_to_simple_formula (pass_thru_rate, ppy, 52);
2461 break;
2462 case GNC_IRATE_APR_MONTHLY:
2463 period_rate = loan_apr_to_simple_formula (pass_thru_rate, ppy, 12);
2464 break;
2465 case GNC_IRATE_APR_QUARTERLY:
2466 period_rate = loan_apr_to_simple_formula (pass_thru_rate, ppy, 4);
2467 break;
2468 case GNC_IRATE_APR_SEMIANNUALLY:
2469 period_rate = loan_apr_to_simple_formula (pass_thru_rate, ppy, 2);
2470 break;
2471 case GNC_IRATE_APR_ANNUALLY:
2472 period_rate = loan_apr_to_simple_formula (pass_thru_rate, ppy, 1);
2473 break;
2474 default:
2475 period_rate = ldd->ld.interestRate / 100;
2476 break;
2477 }
2478 auto period_rate_str = to_str_with_prec<5> (period_rate);
2479 auto period_base_str = to_str_with_prec<2> (ppy);
2480 auto periods_str = to_str_with_prec<2> (periods);
2481 auto principal_str = to_str_with_prec<2> (principal);
2482
2483 // Using boost::locale::format here to merge a template
2484 // with plain strings. We can't use bl::format directly on the double
2485 // values as it will use numeric punctuation instead of monetary punctuation.
2486 // This is different in several locales (like nl_BE and ru_RU)
2487 // and our parsing function does expect monetary punctuation to work properly.
2488 // So instead of bl::format we could also have used boost::format.
2489 // However at the time of this writing that sublibrary is not yet a requirement
2490 // for gnucash. So I stuck with bl::format, which is.
2491 std::string formula;
2492 if (rate_case == GNC_IRATE_SIMPLE)
2493 formula = (bl::format (tpl) % period_rate_str %
2494 period_base_str % periods_str % principal_str).str();
2495 else
2496 formula = (bl::format (tpl) % period_rate_str % periods_str % principal_str).str();
2497 return formula;
2498}
2499
2500static std::string
2501loan_get_pmt_formula(LoanAssistantData *ldd)
2502{
2503 if (ldd->ld.rateType == GNC_IRATE_SIMPLE)
2504 return loan_get_formula_internal (ldd, "pmt( {1} / {2} : {3} : {4} : 0 : 0 )");
2505 else
2506 return loan_get_formula_internal (ldd, "pmt( {1} : {2} : {3} : 0 : 0 )");
2507}
2508
2509
2510static std::string
2511loan_get_ppmt_formula(LoanAssistantData *ldd)
2512{
2513 if (ldd->ld.rateType == GNC_IRATE_SIMPLE)
2514 return loan_get_formula_internal (ldd, "ppmt( {1} / {2} : i : {3} : {4} : 0 : 0 )");
2515 else
2516 return loan_get_formula_internal (ldd, "ppmt( {1} : i : {2} : {3} : 0 : 0 )");
2517}
2518
2519
2520static std::string
2521loan_get_ipmt_formula(LoanAssistantData *ldd)
2522{
2523 if (ldd->ld.rateType == GNC_IRATE_SIMPLE)
2524 return loan_get_formula_internal (ldd, "ipmt( {1} / {2} : i : {3} : {4} : 0 : 0 )");
2525 else
2526 return loan_get_formula_internal (ldd, "ipmt( {1} : i : {2} : {3} : 0 : 0 )");
2527}
2528
2529/******************* Scheduled Transaction Functions ********************/
2530
2531static int
2532ld_calc_sx_instance_num(GDate *start_date, GList *schedule)
2533{
2534 int instance_count;
2535 GDate next_date, today;
2536
2537 g_date_clear(&next_date, 1);
2538 g_date_clear(&today, 1);
2539 gnc_gdate_set_time64 (&today, gnc_time (NULL));
2540
2541 if (g_date_compare(start_date, &today) > 0)
2542 return 0;
2543
2544 instance_count = -1;
2545 do
2546 {
2547 instance_count++;
2548 recurrenceListNextInstance(schedule, start_date, &next_date);
2549 }
2550 while (g_date_compare(&next_date, &today) < 0);
2551
2552 return instance_count;
2553}
2554
2555
2556static
2557void
2558loan_tcSX_free( gpointer data, gpointer user_data )
2559{
2560 toCreateSX *tcSX = (toCreateSX*)data;
2561 g_free( tcSX->name );
2562 tcSX->mainTxn.~TTInfoPtr();
2563 tcSX->escrowTxn.~TTInfoPtr();
2564 g_free( tcSX );
2565}
2566
2567
2571static
2572void
2573loan_create_sx_from_tcSX( LoanAssistantData *ldd, toCreateSX *tcSX )
2574{
2575 SchedXaction *sx;
2576 SchedXactions *sxes;
2577 TTInfoVec ttxn_vec;
2578
2579 sx = xaccSchedXactionMalloc( gnc_get_current_book() );
2580 xaccSchedXactionSetName( sx, tcSX->name );
2581 gnc_sx_set_schedule(sx, tcSX->schedule);
2582 xaccSchedXactionSetStartDate( sx, &tcSX->start );
2583 xaccSchedXactionSetLastOccurDate( sx, &tcSX->last );
2584 xaccSchedXactionSetEndDate( sx, &tcSX->end );
2586
2587 if ( tcSX->mainTxn )
2588 ttxn_vec.push_back (tcSX->mainTxn);
2589 if ( tcSX->escrowTxn )
2590 ttxn_vec.push_back (tcSX->escrowTxn);
2591
2592 g_assert (!ttxn_vec.empty());
2593
2594 xaccSchedXactionSetTemplateTrans (sx, ttxn_vec, gnc_get_current_book());
2595
2596 sxes = gnc_book_get_schedxactions(gnc_get_current_book());
2597 gnc_sxes_add_sx(sxes, sx);
2598}
2599
2600
2601static TTSplitInfoPtr
2602find_account_from_template_splits (const TTInfoPtr& txn, const Account* account)
2603{
2604 auto& splits{txn->get_template_splits ()};
2605 auto has_acct = [account](auto ttinfo){ return ttinfo->get_account() == account; };
2606 auto it = std::find_if (splits.begin(), splits.end(), has_acct);
2607 return it == splits.end() ? nullptr : *it;
2608}
2609
2615static
2616void
2617ld_setup_repayment_sx( LoanAssistantData *ldd,
2618 RepayOptData *rod,
2619 toCreateSX *paymentSX,
2620 toCreateSX *tcSX )
2621{
2622 /* In DoubleEntryAccounting-ease, this is what we're going to do,
2623 * below...
2624 *
2625 * if ( rep->escrow ) {
2626 * if ( rep->from ) {
2627 * a: paymentSX.main.splits += split( rep->fromAcct, repAmt )
2628 * b: paymentSX.main.split( ldd->ld.escrowAcct ).debCred += repAmt
2629 * tcSX.escrow.split( rep->escrow ).debCred += repAmt
2630 * c1: tcSX.escrow.splits += split( rep->toAcct, +repAmt )
2631 * } else {
2632 * d: paymentSX.main.split( ldd->ld.repFromAcct ).debcred += -repAmt
2633 * b: paymentSX.main.split( ldd->ld.escrowAcct ).debCred += repAmt
2634 * tcSX.escrow.splits += split( rep->escrow, -repAmt )
2635 * c1: tcSX.escrow.splits += split( rep->toAcct, +repAmt )
2636 * }
2637 * } else {
2638 * if ( rep->from ) {
2639 * a: paymentSX.main.splits += split( rep->fromAcct, -repAmt )
2640 * c2: paymentSX.main.splits += split( rep->toAcct, +repAmt )
2641 * } else {
2642 * d: paymentSX.main.split( ldd->ld.payFromAcct ).debcred += -repAmt
2643 * c2: paymentSX.main.splits += split( rep->toAcct, +repAmt )
2644 * }
2645 * }
2646 */
2647
2648 /* Now, we refactor the common operations from the above out...
2649 *
2650 * fromSplit = NULL;
2651 * if ( rep->escrow ) {
2652 * b: paymentSX.main.split( ldd->ld.escrowAcct ).debCred += repAmt
2653 * c1: ( toTTI = tcSX.escrow )
2654 * if ( rep->from ) {
2655 * a1: (fromSplit = NULL) paymentSX.main.splits += split( rep->fromAcct, repAmt )
2656 * b:
2657 * tcSX.escrow.split( rep->escrow ).debCred += repAmt
2658 * c1:
2659 * } else {
2660 * a2: (fromSplit = paymentSX.main.split( ldd->ld.repFromAcct )) .debcred += -repAmt
2661 * b:
2662 * tcSX.escrow.splits += split( rep->escrow, -repAmt )
2663 * c1:
2664 * }
2665 * } else {
2666 * c2: ( toTTI = paymentSX.main )
2667 * if ( rep->from ) {
2668 * a1: (fromSplit = NULL) paymentSX.main.splits += split( rep->fromAcct, -repAmt )
2669 * c2:
2670 * } else {
2671 * a2: (fromSplit = paymentSX.main.split( ldd->ld.payFromAcct )).debcred += -repAmt
2672 * c2:
2673 * }
2674 * }
2675 * if ( fromSplit ) {
2676 * fromSplit.debCred += (-repAmt);
2677 * } else {
2678 * fromSplit = split( rep->fromAcct, -repAmt )
2679 * paymentSX.main.splits += fromSplit
2680 * }
2681 * toTTI.splits += split( rep->toAcct, +repAmt );
2682 */
2683
2686 GString *gstr;
2687 TTSplitInfoPtr fromSplit;
2688 TTSplitInfoPtr ttsi;
2689 TTInfoPtr toTxn;
2690 GNCPrintAmountInfo pricePAI = gnc_default_price_print_info(NULL);
2691#define AMTBUF_LEN 64
2692 gchar amtBuf[AMTBUF_LEN];
2693 gint GNCN_HOW = (GNC_HOW_DENOM_SIGFIGS(2) | GNC_HOW_RND_ROUND_HALF_UP);
2694
2695 /* We're going to use this a lot, below, so just create it once. */
2696 xaccSPrintAmount( amtBuf,
2697 double_to_gnc_numeric( rod->amount, 100,
2698 GNCN_HOW ),
2699 pricePAI );
2700
2701 if ( rod->throughEscrowP && ldd->ld.escrowAcct )
2702 {
2703 toTxn = tcSX->escrowTxn;
2704
2705 /* Add the repayment amount into the string of the existing
2706 * ttsplit. */
2707 {
2708 auto ttsi = find_account_from_template_splits (paymentSX->mainTxn, ldd->ld.escrowAcct);
2709 g_assert (ttsi);
2710 gstr = g_string_new (ttsi->get_debit_formula());
2711 g_string_append_printf( gstr, " + %s", amtBuf );
2712 ttsi->set_debit_formula (gstr->str);
2713 g_string_free( gstr, TRUE );
2714 gstr = NULL;
2715 ttsi = NULL;
2716 }
2717
2718 if ( rod->from != NULL )
2719 {
2720 gchar *str;
2721
2722 fromSplit = NULL;
2723
2724 /* tcSX.escrow.split( rep->escrow ).debCred += repAmt */
2725 auto ttsi = find_account_from_template_splits (tcSX->escrowTxn, ldd->ld.escrowAcct);
2726 if ( !ttsi )
2727 {
2728 /* create split */
2729 ttsi = std::make_shared<TTSplitInfo>();
2730 ttsi->set_memo (rod->txnMemo);
2731 ttsi->set_account (ldd->ld.escrowAcct);
2732 tcSX->escrowTxn->append_template_split (ttsi);
2733 }
2734 if ( (str = (gchar*)ttsi->get_credit_formula ())
2735 == NULL )
2736 {
2737 gstr = g_string_sized_new( 16 );
2738 }
2739 else
2740 {
2741 /* If we did get a split/didn't have to
2742 * create a split, then we need to add our
2743 * amount in rather than replace. */
2744 gstr = g_string_new( str );
2745 g_string_append_printf( gstr, " + " );
2746 }
2747 g_string_append_printf( gstr, "%s", amtBuf );
2748 ttsi->set_credit_formula (gstr->str);
2749 g_string_free( gstr, TRUE );
2750 gstr = NULL;
2751 ttsi = NULL;
2752 }
2753 else
2754 {
2755 /* (fromSplit = paymentSX.main.split( ldd->ld.repFromAcct )) */
2756 fromSplit = find_account_from_template_splits (paymentSX->mainTxn, ldd->ld.repFromAcct);
2757 g_assert (fromSplit);
2758
2759 /* tcSX.escrow.splits += split( rep->escrow, -repAmt ) */
2760 ttsi = std::make_shared<TTSplitInfo>();
2761 ttsi->set_memo (rod->txnMemo);
2762 ttsi->set_account (ldd->ld.escrowAcct);
2763 ttsi->set_credit_formula (amtBuf);
2764 tcSX->escrowTxn->append_template_split (ttsi);
2765 ttsi = NULL;
2766 }
2767 }
2768 else
2769 {
2770 toTxn = tcSX->mainTxn;
2771
2772 if ( rod->from != NULL )
2773 {
2774 fromSplit = NULL;
2775 }
2776 else
2777 {
2778 fromSplit = find_account_from_template_splits (tcSX->mainTxn, ldd->ld.repFromAcct);
2779 }
2780 }
2781
2782 if ( fromSplit != NULL )
2783 {
2784 /* Update the existing from-split. */
2785 gstr = g_string_new (fromSplit->get_credit_formula ());
2786 g_string_append_printf( gstr, " + %s", amtBuf );
2787 fromSplit->set_credit_formula (gstr->str);
2788 g_string_free( gstr, TRUE );
2789 gstr = NULL;
2790
2791 }
2792 else
2793 {
2794 TTInfoPtr tti;
2795 /* Create a new from-split. */
2796 ttsi = std::make_shared<TTSplitInfo>();
2797 ttsi->set_memo (rod->txnMemo);
2798 ttsi->set_account (rod->from ? rod->from : ldd->ld.repFromAcct);
2799 ttsi->set_credit_formula (amtBuf);
2800 tti = tcSX->mainTxn;
2801 if ( rod->throughEscrowP )
2802 {
2803 tti = paymentSX->mainTxn;
2804 }
2805 tti->append_template_split (ttsi);
2806 ttsi = NULL;
2807 tti = NULL;
2808 }
2809
2810 /* Add to-account split. */
2811 {
2812 ttsi = std::make_shared<TTSplitInfo>();
2813 ttsi->set_memo (rod->txnMemo);
2814 ttsi->set_account (rod->to);
2815 ttsi->set_debit_formula (amtBuf);
2816 toTxn->append_template_split (ttsi);
2817 ttsi = NULL;
2818 }
2819}
2820
2821
2836static
2837void
2838loan_create_sxes( LoanAssistantData *ldd )
2839{
2840 /* The main loan-payment SX.*/
2841 toCreateSX *paymentSX = NULL;
2842 /* A GList of any other repayment SXes with different schedule. */
2843 GList *repaySXes = NULL;
2844 /* The currently-being-referenced toCreateSX. */
2845 toCreateSX *tcSX;
2846 int i;
2847 TTInfoPtr ttxn;
2848 TTSplitInfoPtr ttsi;
2849
2850 paymentSX = g_new0( toCreateSX, 1 );
2851 paymentSX->name = g_strdup(ldd->ld.repMemo);
2852 paymentSX->start = *ldd->ld.startDate;
2853 paymentSX->last = *ldd->ld.repStartDate;
2854 g_date_subtract_months( &paymentSX->last, 1 );
2855 {
2856 paymentSX->end = *ldd->ld.repStartDate;
2857 g_date_add_months( &paymentSX->end, ldd->ld.numMonRemain - 1);
2858 }
2859
2860 paymentSX->schedule = ldd->ld.repayment_schedule;
2861 /* Figure out the correct current instance-count for the txns in the
2862 * SX. */
2863 paymentSX->instNum =
2864 (ldd->ld.numPer * ( ldd->ld.perSize == GNC_YEARS ? 12 : 1 ))
2865 - ldd->ld.numMonRemain + 1;
2866
2867 paymentSX->mainTxn = std::make_shared<TTInfo>();
2868 paymentSX->mainTxn->set_currency (gnc_default_currency());
2869
2870 {
2871 GString *payMainTxnDesc = g_string_sized_new( 32 );
2872 g_string_printf( payMainTxnDesc,
2873 "%s - %s",
2874 ldd->ld.repMemo,
2875 ( ldd->ld.escrowAcct == NULL
2876 ? _("Payment")
2877 : _("Escrow Payment") )
2878 );
2879
2880 paymentSX->mainTxn->set_description(payMainTxnDesc->str);
2881 g_string_free( payMainTxnDesc, TRUE );
2882 }
2883
2884 /* Create the basic txns and splits...
2885 *
2886 * ttxn <- mainTxn
2887 * srcAcct <- assets
2888 * if ( escrow ) {
2889 * realSrcAcct <- srcAcct
2890 * srcAcct <- escrow;
2891 * ttxn <- escrowTxn
2892 * main.splits += split( realSrcAcct, -pmt )
2893 * main.splits += split( escrow, pmt )
2894 * }
2895 * ttxn.splits += split( escrow, -pmt)
2896 * ttxn.splits += split( liability, ppmt )
2897 * ttxn.splits += split( expenses:interest, ipmt ) */
2898
2899 {
2900 Account *srcAcct;
2901
2902 ttxn = paymentSX->mainTxn;
2903 srcAcct = ldd->ld.repFromAcct;
2904 if ( ldd->ld.escrowAcct != NULL )
2905 {
2906 Account *realSrcAcct = srcAcct;
2907 srcAcct = ldd->ld.escrowAcct;
2908 auto formula = loan_get_pmt_formula(ldd);
2909 /* ttxn.splits += split( realSrcAcct, -pmt ); */
2910 {
2911 ttsi = std::make_shared<TTSplitInfo>();
2912 ttsi->set_memo (ldd->ld.repMemo);
2913 ttsi->set_account (realSrcAcct);
2914 ttsi->set_credit_formula (formula.c_str());
2915 ttxn->append_template_split (ttsi);
2916 ttsi = NULL;
2917 }
2918 /* ttxn.splits += split( escrowAcct, +pmt ); */
2919 {
2920 ttsi = std::make_shared<TTSplitInfo>();
2921 ttsi->set_memo (ldd->ld.repMemo);
2922 ttsi->set_account (ldd->ld.escrowAcct);
2923 ttsi->set_debit_formula (formula.c_str());
2924 ttxn->append_template_split (ttsi);
2925 ttsi = NULL;
2926 }
2927 paymentSX->escrowTxn = std::make_shared<TTInfo>();
2928 paymentSX->escrowTxn->set_currency (gnc_default_currency());
2929
2930 {
2931 GString *escrowTxnDesc;
2932 escrowTxnDesc = g_string_new( ldd->ld.repMemo );
2933 g_string_append_printf( escrowTxnDesc, " - %s", _("Payment") );
2934 paymentSX->escrowTxn->set_description (escrowTxnDesc->str);
2935 g_string_free( escrowTxnDesc, TRUE );
2936 }
2937 ttxn = paymentSX->escrowTxn;
2938 }
2939 /* ttxn.splits += split( srcAcct, -pmt ); */
2940 {
2941 ttsi = std::make_shared<TTSplitInfo>();
2942 {
2943 auto gstr = g_string_new( ldd->ld.repMemo );
2944 g_string_append_printf( gstr, " - %s",
2945 _("Payment") );
2946 ttsi->set_memo (gstr->str);
2947 g_string_free( gstr, TRUE );
2948 }
2949 ttsi->set_account (srcAcct);
2950 auto formula = loan_get_pmt_formula(ldd);
2951 ttsi->set_credit_formula (formula.c_str());
2952 ttxn->append_template_split (ttsi);
2953 ttsi = NULL;
2954 }
2955 /* ttxn.splits += split( ldd->ld.repPriAcct, +ppmt ); */
2956 {
2957 ttsi = std::make_shared<TTSplitInfo>();
2958 {
2959 auto gstr = g_string_new( ldd->ld.repMemo );
2960 g_string_append_printf( gstr, " - %s",
2961 _("Principal") );
2962 ttsi->set_memo (gstr->str);
2963 g_string_free( gstr, TRUE );
2964 }
2965 ttsi->set_account (ldd->ld.repPriAcct);
2966 auto formula = loan_get_ppmt_formula(ldd);
2967 ttsi->set_debit_formula (formula.c_str());
2968 ttxn->append_template_split (ttsi);
2969 ttsi = NULL;
2970 }
2971 /* ttxn.splits += split( ldd->ld.repIntAcct, +ipmt ); */
2972 {
2973 ttsi = std::make_shared<TTSplitInfo>();
2974 {
2975 auto gstr = g_string_new( ldd->ld.repMemo );
2976 g_string_append_printf( gstr, " - %s",
2977 _("Interest") );
2978 ttsi->set_memo (gstr->str);
2979 g_string_free( gstr, TRUE );
2980 }
2981 ttsi->set_account (ldd->ld.repIntAcct);
2982 auto formula = loan_get_ipmt_formula(ldd);
2983 ttsi->set_debit_formula (formula.c_str());
2984 ttxn->append_template_split (ttsi);
2985 ttsi = NULL;
2986 }
2987 }
2988 for ( i = 0; i < ldd->ld.repayOptCount; i++ )
2989 {
2990 RepayOptData *rod = ldd->ld.repayOpts[i];
2991 if ( ! rod->enabled )
2992 continue;
2993
2994 tcSX = paymentSX;
2995 if ( rod->schedule != NULL )
2996 {
2997 tcSX = g_new0( toCreateSX, 1 );
2998 auto gstr = g_string_new( ldd->ld.repMemo );
2999 g_string_append_printf( gstr, " - %s",
3000 rod->name );
3001 tcSX->name = g_strdup(gstr->str);
3002 tcSX->start = *ldd->ld.startDate;
3003 tcSX->last = *ldd->ld.repStartDate;
3004 {
3005 tcSX->end = tcSX->last;
3006 g_date_add_months( &tcSX->end, ldd->ld.numMonRemain );
3007 }
3008 tcSX->schedule = rod->schedule;
3009 /* So it won't get destroyed when the close the
3010 * Assistant. */
3011 tcSX->instNum =
3012 ld_calc_sx_instance_num(&tcSX->start, rod->schedule);
3013 rod->schedule = NULL;
3014 tcSX->mainTxn = std::make_shared<TTInfo>();
3015 tcSX->mainTxn->set_currency(gnc_default_currency());
3016 tcSX->mainTxn->set_description (gstr->str);
3017 tcSX->escrowTxn = std::make_shared<TTInfo>();
3018 tcSX->escrowTxn->set_currency (gnc_default_currency());
3019 tcSX->escrowTxn->set_description(gstr->str);
3020
3021 g_string_free( gstr, TRUE );
3022 repaySXes = g_list_prepend (repaySXes, tcSX);
3023
3024 }
3025
3026 /* repayment */
3027 ld_setup_repayment_sx( ldd, rod, paymentSX, tcSX );
3028 }
3029
3030 repaySXes = g_list_reverse (repaySXes);
3031 /* Create the SXes */
3032 {
3033 GList *l;
3034
3035 loan_create_sx_from_tcSX( ldd, paymentSX );
3036
3037 for ( l = repaySXes; l; l = l->next )
3038 {
3039 loan_create_sx_from_tcSX( ldd, (toCreateSX*)l->data );
3040 }
3041 }
3042 /* Clean up. */
3043 loan_tcSX_free( paymentSX, NULL );
3044 g_list_foreach( repaySXes, loan_tcSX_free, NULL );
3045 g_list_free( repaySXes );
3046}
3047
3048/************************ Assistant Functions ***************************/
3049
3050void
3051loan_assistant_finish ( GtkAssistant *gtkassistant, gpointer user_data )
3052{
3053 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
3054 loan_create_sxes( ldd );
3055
3056}
3057
3058
3059void
3060loan_assistant_cancel( GtkAssistant *gtkassistant, gpointer user_data )
3061{
3062 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
3063 gnc_close_gui_component_by_data( DIALOG_LOAN_ASSISTANT_CM_CLASS, ldd );
3064}
3065
3066
3067void
3068loan_assistant_close( GtkAssistant *gtkassistant, gpointer user_data )
3069{
3070 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
3071 gnc_close_gui_component_by_data( DIALOG_LOAN_ASSISTANT_CM_CLASS, ldd );
3072}
3073
3074
3075void
3076loan_assistant_prepare (GtkAssistant *assistant, GtkWidget *page,
3077 gpointer user_data)
3078{
3079 gint currentpage = gtk_assistant_get_current_page(assistant);
3080
3081 switch (currentpage)
3082 {
3083 case 1:
3084 /* Current page is info page */
3085 loan_info_prep (assistant, user_data);
3086 break;
3087 case 2:
3088 /* Current page is Options page */
3089 loan_opt_prep (assistant, user_data);
3090 break;
3091 case 3:
3092 /* Current page is Repayments page */
3093 loan_rep_prep (assistant, user_data);
3094 break;
3095 case 4:
3096 /* Current page is Repayments Options page */
3097 loan_pay_prep (assistant, user_data);
3098 break;
3099 case 5:
3100 /* Current page is Review page */
3101 loan_rev_prep (assistant, user_data);
3102 break;
3103 }
3104}
3105
3106
3107/********************************************************************\
3108 * gnc_ui_sx_loan_assistant_create *
3109 * opens up a window to start the loan Assistant *
3110 * *
3111\********************************************************************/
3112void
3113gnc_ui_sx_loan_assistant_create (void)
3114{
3115 LoanAssistantData *ldd;
3116 gint component_id;
3117
3118 ldd = g_new0 (LoanAssistantData, 1);
3119
3120 gnc_loan_assistant_create (ldd);
3121
3122 component_id = gnc_register_gui_component (DIALOG_LOAN_ASSISTANT_CM_CLASS,
3123 NULL, loan_assistant_close_handler,
3124 ldd);
3125
3126 gnc_gui_component_watch_entity_type (component_id,
3127 GNC_ID_ACCOUNT,
3128 QOF_EVENT_MODIFY | QOF_EVENT_DESTROY);
3129
3130 gtk_widget_show_all (ldd->window);
3131
3132 gnc_window_adjust_for_screen (GTK_WINDOW(ldd->window));
3133}
Account handling public routines.
Anchor Scheduled Transaction info in a book.
Scheduled Transactions public handling routines.
Date and Time handling routines.
All type declarations for the whole Gnucash engine.
utility functions for the GnuCash UI
GNCAccountType
The account types are used to determine how the transaction data in the account is displayed.
Definition Account.h:103
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account's commodity
Definition Account.cpp:3408
@ ACCT_TYPE_EXPENSE
Expense accounts are used to denote expenses.
Definition Account.h:143
@ ACCT_TYPE_BANK
The bank account type denotes a savings or checking account held at a bank.
Definition Account.h:107
@ ACCT_TYPE_ASSET
asset (and liability) accounts indicate generic, generalized accounts that are none of the above.
Definition Account.h:116
@ ACCT_TYPE_CASH
The cash account type is used to denote a shoe-box or pillowcase stuffed with * cash.
Definition Account.h:110
@ ACCT_TYPE_LIABILITY
liability (and asset) accounts indicate generic, generalized accounts that are none of the above.
Definition Account.h:119
@ ACCT_TYPE_CREDIT
The Credit card account is used to denote credit (e.g.
Definition Account.h:113
int gnc_commodity_get_fraction(const gnc_commodity *cm)
Retrieve the fraction for the specified commodity.
time64 gnc_mktime(struct tm *time)
calculate seconds from the epoch given a time struct
Definition gnc-date.cpp:219
gboolean gnc_gdate_equal(gconstpointer gda, gconstpointer gdb)
Compares two GDate*'s for equality; useful for using GDate*'s as GHashTable keys.
#define MAX_DATE_LENGTH
The maximum length of a string created by the date printers.
Definition gnc-date.h:108
void gnc_gdate_set_time64(GDate *gd, time64 time)
Set a GDate to a time64.
guint gnc_gdate_hash(gconstpointer gd)
Provides a "hash" of a GDate* value; useful for using GDate*'s as GHashTable keys.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition gnc-date.h:87
void gnc_tm_free(struct tm *time)
free a struct tm* created with gnc_localtime() or gnc_gmtime()
Definition gnc-date.cpp:97
time64 gnc_time(time64 *tbuf)
get the current time
Definition gnc-date.cpp:262
struct tm * gnc_localtime(const time64 *secs)
fill out a time struct from a 64-bit time value.
Definition gnc-date.cpp:103
size_t qof_print_gdate(char *buf, size_t len, const GDate *gd)
Convenience; calls through to qof_print_date_dmy_buff().
Definition gnc-date.cpp:598
int xaccSPrintAmount(char *bufp, gnc_numeric val, GNCPrintAmountInfo info)
Make a string representation of a gnc_numeric.
gnc_commodity * gnc_default_currency(void)
Return the default currency set by the user.
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define PERR(format, args...)
Log a serious error.
Definition qoflog.h:244
double gnc_numeric_to_double(gnc_numeric in)
Convert numeric to floating-point value.
gnc_numeric double_to_gnc_numeric(double in, gint64 denom, gint how)
Convert a floating-point number to a gnc_numeric.
gnc_numeric gnc_numeric_error(GNCNumericErrorCode error_code)
Create a gnc_numeric object that signals the error condition noted by error_code, rather than a numbe...
GNCNumericErrorCode gnc_numeric_check(gnc_numeric in)
Check for error signal in value.
#define GNC_HOW_DENOM_SIGFIGS(n)
Build a 'how' value that will generate a denominator that will keep at least n significant figures in...
@ GNC_ERROR_ARG
Argument is not a valid number.
@ GNC_HOW_RND_ROUND_HALF_UP
Round to the nearest integer, rounding away from zero when there are two equidistant nearest integers...
void gnc_sx_set_schedule(SchedXaction *sx, GList *schedule)
SchedXaction * xaccSchedXactionMalloc(QofBook *book)
Creates and initializes a scheduled transaction.
void xaccSchedXactionSetName(SchedXaction *sx, const gchar *newName)
A copy of the name is made.
void xaccSchedXactionSetEndDate(SchedXaction *sx, const GDate *newEnd)
Set to an invalid GDate to turn off 'end-date' definition.
void gnc_sx_set_instance_count(SchedXaction *sx, gint instance_num)
Sets the instance count to something other than the default.
STRUCTS.
Implementations.
The UI-side storage of the loan assistant data.
Data about a loan repayment.
The default repayment options data.
The data relating to a single "repayment option" – a potential [sub-]transaction in the repayment.
The UI-side storage of the repayment options.
A transient struct used to collate the GDate and the gnc_numeric row-data for the repayment review sc...
A transient structure to contain SX details during the creation process.
TTInfoPtr mainTxn
The main/source transaction being created.
gint instNum
The current 'instance-num' count.
TTInfoPtr escrowTxn
The optional escrow transaction being created.
GDate start
The start, last-occurred and end dates.
gchar * name
The name of the SX.
GList * schedule
The SX schedule.