GnuCash c935c2f+
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[50];
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 loc = std::locale(gnc_get_locale(), new cust_prec_punct<prec>(""));
2378 std::wstringstream valstr;
2379 valstr.imbue(loc);
2380 valstr << std::put_money(val * pow(10, prec));
2381 return utf_to_utf<char>(valstr.str());
2382#endif
2383}
2384
2385static std::tuple<double, double>
2386periods_per_year( LoanAssistantData *ldd)
2387{
2388 double ppy = 0.0, periods = 1.0;
2389 auto recurrences = ldd->ld.repayment_schedule;
2390 for (auto node = recurrences; node; node = g_list_next(node))
2391 {
2392 auto recurrence = static_cast<Recurrence*>(node->data);
2393 auto period_type = recurrenceGetPeriodType(recurrence);
2394 auto multiplier = recurrenceGetMultiplier(recurrence);
2395 if (multiplier < 1)
2396 multiplier = 1;
2397
2398 switch(period_type)
2399 {
2400 case PERIOD_YEAR:
2401 ppy += 1.0 / multiplier;
2402 break;
2403 case PERIOD_DAY:
2404 ppy = 365 / multiplier;
2405 break;
2406 case PERIOD_WEEK:
2407 ppy += 52.0 / multiplier;
2408 break;
2409 case PERIOD_MONTH:
2410 case PERIOD_END_OF_MONTH:
2411 case PERIOD_NTH_WEEKDAY:
2412 case PERIOD_LAST_WEEKDAY:
2413 ppy += 12.0 / multiplier;
2414 break;
2415 case PERIOD_ONCE:
2416 default:
2417 break;
2418 }
2419 }
2420
2421 auto months = (ldd->ld.numPer * (ldd->ld.perSize == GNC_MONTHS ? 1 : 12));
2422 if ((int)(months * ppy / 12) > 1)
2423 periods = (int)(months * ppy / 12) * 1.0;
2424
2425 return {ppy, periods};
2426}
2427
2428static std::string
2429loan_get_formula_internal( LoanAssistantData *ldd, const gchar *tpl )
2430{
2431 g_assert( ldd != NULL );
2432
2433 auto [ppy, periods] = periods_per_year(ldd);
2434 gdouble pass_thru_rate = ldd->ld.interestRate / 100.0;
2435 auto principal = gnc_numeric_to_double(ldd->ld.principal);
2436
2437 gdouble period_rate;
2438 auto rate_case = ldd->ld.rateType;
2439 switch (rate_case)
2440 {
2441 case GNC_IRATE_SIMPLE:
2442 period_rate = pass_thru_rate;
2443 break;
2444 case GNC_IRATE_APR_DAILY:
2445 period_rate = loan_apr_to_simple_formula (pass_thru_rate, ppy, 365);
2446 break;
2447 case GNC_IRATE_APR_WEEKLY:
2448 period_rate = loan_apr_to_simple_formula (pass_thru_rate, ppy, 52);
2449 break;
2450 case GNC_IRATE_APR_MONTHLY:
2451 period_rate = loan_apr_to_simple_formula (pass_thru_rate, ppy, 12);
2452 break;
2453 case GNC_IRATE_APR_QUARTERLY:
2454 period_rate = loan_apr_to_simple_formula (pass_thru_rate, ppy, 4);
2455 break;
2456 case GNC_IRATE_APR_SEMIANNUALLY:
2457 period_rate = loan_apr_to_simple_formula (pass_thru_rate, ppy, 2);
2458 break;
2459 case GNC_IRATE_APR_ANNUALLY:
2460 period_rate = loan_apr_to_simple_formula (pass_thru_rate, ppy, 1);
2461 break;
2462 default:
2463 period_rate = ldd->ld.interestRate / 100;
2464 break;
2465 }
2466 auto period_rate_str = to_str_with_prec<5> (period_rate);
2467 auto period_base_str = to_str_with_prec<2> (ppy);
2468 auto periods_str = to_str_with_prec<2> (periods);
2469 auto principal_str = to_str_with_prec<2> (principal);
2470
2471 // Using boost::locale::format here to merge a template
2472 // with plain strings. We can't use bl::format directly on the double
2473 // values as it will use numeric punctuation instead of monetary punctuation.
2474 // This is different in several locales (like nl_BE and ru_RU)
2475 // and our parsing function does expect monetary punctuation to work properly.
2476 // So instead of bl::format we could also have used boost::format.
2477 // However at the time of this writing that sublibrary is not yet a requirement
2478 // for gnucash. So I stuck with bl::format, which is.
2479 std::string formula;
2480 if (rate_case == GNC_IRATE_SIMPLE)
2481 formula = (bl::format (tpl) % period_rate_str %
2482 period_base_str % periods_str % principal_str).str();
2483 else
2484 formula = (bl::format (tpl) % period_rate_str % periods_str % principal_str).str();
2485 return formula;
2486}
2487
2488static std::string
2489loan_get_pmt_formula(LoanAssistantData *ldd)
2490{
2491 if (ldd->ld.rateType == GNC_IRATE_SIMPLE)
2492 return loan_get_formula_internal (ldd, "pmt( {1} / {2} : {3} : {4} : 0 : 0 )");
2493 else
2494 return loan_get_formula_internal (ldd, "pmt( {1} : {2} : {3} : 0 : 0 )");
2495}
2496
2497
2498static std::string
2499loan_get_ppmt_formula(LoanAssistantData *ldd)
2500{
2501 if (ldd->ld.rateType == GNC_IRATE_SIMPLE)
2502 return loan_get_formula_internal (ldd, "ppmt( {1} / {2} : i : {3} : {4} : 0 : 0 )");
2503 else
2504 return loan_get_formula_internal (ldd, "ppmt( {1} : i : {2} : {3} : 0 : 0 )");
2505}
2506
2507
2508static std::string
2509loan_get_ipmt_formula(LoanAssistantData *ldd)
2510{
2511 if (ldd->ld.rateType == GNC_IRATE_SIMPLE)
2512 return loan_get_formula_internal (ldd, "ipmt( {1} / {2} : i : {3} : {4} : 0 : 0 )");
2513 else
2514 return loan_get_formula_internal (ldd, "ipmt( {1} : i : {2} : {3} : 0 : 0 )");
2515}
2516
2517/******************* Scheduled Transaction Functions ********************/
2518
2519static int
2520ld_calc_sx_instance_num(GDate *start_date, GList *schedule)
2521{
2522 int instance_count;
2523 GDate next_date, today;
2524
2525 g_date_clear(&next_date, 1);
2526 g_date_clear(&today, 1);
2527 gnc_gdate_set_time64 (&today, gnc_time (NULL));
2528
2529 if (g_date_compare(start_date, &today) > 0)
2530 return 0;
2531
2532 instance_count = -1;
2533 do
2534 {
2535 instance_count++;
2536 recurrenceListNextInstance(schedule, start_date, &next_date);
2537 }
2538 while (g_date_compare(&next_date, &today) < 0);
2539
2540 return instance_count;
2541}
2542
2543
2544static
2545void
2546loan_tcSX_free( gpointer data, gpointer user_data )
2547{
2548 toCreateSX *tcSX = (toCreateSX*)data;
2549 g_free( tcSX->name );
2550 tcSX->mainTxn.~TTInfoPtr();
2551 tcSX->escrowTxn.~TTInfoPtr();
2552 g_free( tcSX );
2553}
2554
2555
2559static
2560void
2561loan_create_sx_from_tcSX( LoanAssistantData *ldd, toCreateSX *tcSX )
2562{
2563 SchedXaction *sx;
2564 SchedXactions *sxes;
2565 TTInfoVec ttxn_vec;
2566
2567 sx = xaccSchedXactionMalloc( gnc_get_current_book() );
2568 xaccSchedXactionSetName( sx, tcSX->name );
2569 gnc_sx_set_schedule(sx, tcSX->schedule);
2570 xaccSchedXactionSetStartDate( sx, &tcSX->start );
2571 xaccSchedXactionSetLastOccurDate( sx, &tcSX->last );
2572 xaccSchedXactionSetEndDate( sx, &tcSX->end );
2574
2575 if ( tcSX->mainTxn )
2576 ttxn_vec.push_back (tcSX->mainTxn);
2577 if ( tcSX->escrowTxn )
2578 ttxn_vec.push_back (tcSX->escrowTxn);
2579
2580 g_assert (!ttxn_vec.empty());
2581
2582 xaccSchedXactionSetTemplateTrans (sx, ttxn_vec, gnc_get_current_book());
2583
2584 sxes = gnc_book_get_schedxactions(gnc_get_current_book());
2585 gnc_sxes_add_sx(sxes, sx);
2586}
2587
2588
2589static TTSplitInfoPtr
2590find_account_from_template_splits (const TTInfoPtr& txn, const Account* account)
2591{
2592 auto& splits{txn->get_template_splits ()};
2593 auto has_acct = [account](auto ttinfo){ return ttinfo->get_account() == account; };
2594 auto it = std::find_if (splits.begin(), splits.end(), has_acct);
2595 return it == splits.end() ? nullptr : *it;
2596}
2597
2603static
2604void
2605ld_setup_repayment_sx( LoanAssistantData *ldd,
2606 RepayOptData *rod,
2607 toCreateSX *paymentSX,
2608 toCreateSX *tcSX )
2609{
2610 /* In DoubleEntryAccounting-ease, this is what we're going to do,
2611 * below...
2612 *
2613 * if ( rep->escrow ) {
2614 * if ( rep->from ) {
2615 * a: paymentSX.main.splits += split( rep->fromAcct, repAmt )
2616 * b: paymentSX.main.split( ldd->ld.escrowAcct ).debCred += repAmt
2617 * tcSX.escrow.split( rep->escrow ).debCred += repAmt
2618 * c1: tcSX.escrow.splits += split( rep->toAcct, +repAmt )
2619 * } else {
2620 * d: paymentSX.main.split( ldd->ld.repFromAcct ).debcred += -repAmt
2621 * b: paymentSX.main.split( ldd->ld.escrowAcct ).debCred += repAmt
2622 * tcSX.escrow.splits += split( rep->escrow, -repAmt )
2623 * c1: tcSX.escrow.splits += split( rep->toAcct, +repAmt )
2624 * }
2625 * } else {
2626 * if ( rep->from ) {
2627 * a: paymentSX.main.splits += split( rep->fromAcct, -repAmt )
2628 * c2: paymentSX.main.splits += split( rep->toAcct, +repAmt )
2629 * } else {
2630 * d: paymentSX.main.split( ldd->ld.payFromAcct ).debcred += -repAmt
2631 * c2: paymentSX.main.splits += split( rep->toAcct, +repAmt )
2632 * }
2633 * }
2634 */
2635
2636 /* Now, we refactor the common operations from the above out...
2637 *
2638 * fromSplit = NULL;
2639 * if ( rep->escrow ) {
2640 * b: paymentSX.main.split( ldd->ld.escrowAcct ).debCred += repAmt
2641 * c1: ( toTTI = tcSX.escrow )
2642 * if ( rep->from ) {
2643 * a1: (fromSplit = NULL) paymentSX.main.splits += split( rep->fromAcct, repAmt )
2644 * b:
2645 * tcSX.escrow.split( rep->escrow ).debCred += repAmt
2646 * c1:
2647 * } else {
2648 * a2: (fromSplit = paymentSX.main.split( ldd->ld.repFromAcct )) .debcred += -repAmt
2649 * b:
2650 * tcSX.escrow.splits += split( rep->escrow, -repAmt )
2651 * c1:
2652 * }
2653 * } else {
2654 * c2: ( toTTI = paymentSX.main )
2655 * if ( rep->from ) {
2656 * a1: (fromSplit = NULL) paymentSX.main.splits += split( rep->fromAcct, -repAmt )
2657 * c2:
2658 * } else {
2659 * a2: (fromSplit = paymentSX.main.split( ldd->ld.payFromAcct )).debcred += -repAmt
2660 * c2:
2661 * }
2662 * }
2663 * if ( fromSplit ) {
2664 * fromSplit.debCred += (-repAmt);
2665 * } else {
2666 * fromSplit = split( rep->fromAcct, -repAmt )
2667 * paymentSX.main.splits += fromSplit
2668 * }
2669 * toTTI.splits += split( rep->toAcct, +repAmt );
2670 */
2671
2674 GString *gstr;
2675 TTSplitInfoPtr fromSplit;
2676 TTSplitInfoPtr ttsi;
2677 TTInfoPtr toTxn;
2678 GNCPrintAmountInfo pricePAI = gnc_default_price_print_info(NULL);
2679#define AMTBUF_LEN 64
2680 gchar amtBuf[AMTBUF_LEN];
2681 gint GNCN_HOW = (GNC_HOW_DENOM_SIGFIGS(2) | GNC_HOW_RND_ROUND_HALF_UP);
2682
2683 /* We're going to use this a lot, below, so just create it once. */
2684 xaccSPrintAmount( amtBuf,
2685 double_to_gnc_numeric( rod->amount, 100,
2686 GNCN_HOW ),
2687 pricePAI );
2688
2689 if ( rod->throughEscrowP && ldd->ld.escrowAcct )
2690 {
2691 toTxn = tcSX->escrowTxn;
2692
2693 /* Add the repayment amount into the string of the existing
2694 * ttsplit. */
2695 {
2696 auto ttsi = find_account_from_template_splits (paymentSX->mainTxn, ldd->ld.escrowAcct);
2697 g_assert (ttsi);
2698 gstr = g_string_new (ttsi->get_debit_formula());
2699 g_string_append_printf( gstr, " + %s", amtBuf );
2700 ttsi->set_debit_formula (gstr->str);
2701 g_string_free( gstr, TRUE );
2702 gstr = NULL;
2703 ttsi = NULL;
2704 }
2705
2706 if ( rod->from != NULL )
2707 {
2708 gchar *str;
2709
2710 fromSplit = NULL;
2711
2712 /* tcSX.escrow.split( rep->escrow ).debCred += repAmt */
2713 auto ttsi = find_account_from_template_splits (tcSX->escrowTxn, ldd->ld.escrowAcct);
2714 if ( !ttsi )
2715 {
2716 /* create split */
2717 ttsi = std::make_shared<TTSplitInfo>();
2718 ttsi->set_memo (rod->txnMemo);
2719 ttsi->set_account (ldd->ld.escrowAcct);
2720 tcSX->escrowTxn->append_template_split (ttsi);
2721 }
2722 if ( (str = (gchar*)ttsi->get_credit_formula ())
2723 == NULL )
2724 {
2725 gstr = g_string_sized_new( 16 );
2726 }
2727 else
2728 {
2729 /* If we did get a split/didn't have to
2730 * create a split, then we need to add our
2731 * amount in rather than replace. */
2732 gstr = g_string_new( str );
2733 g_string_append_printf( gstr, " + " );
2734 }
2735 g_string_append_printf( gstr, "%s", amtBuf );
2736 ttsi->set_credit_formula (gstr->str);
2737 g_string_free( gstr, TRUE );
2738 gstr = NULL;
2739 ttsi = NULL;
2740 }
2741 else
2742 {
2743 /* (fromSplit = paymentSX.main.split( ldd->ld.repFromAcct )) */
2744 fromSplit = find_account_from_template_splits (paymentSX->mainTxn, ldd->ld.repFromAcct);
2745 g_assert (fromSplit);
2746
2747 /* tcSX.escrow.splits += split( rep->escrow, -repAmt ) */
2748 ttsi = std::make_shared<TTSplitInfo>();
2749 ttsi->set_memo (rod->txnMemo);
2750 ttsi->set_account (ldd->ld.escrowAcct);
2751 ttsi->set_credit_formula (amtBuf);
2752 tcSX->escrowTxn->append_template_split (ttsi);
2753 ttsi = NULL;
2754 }
2755 }
2756 else
2757 {
2758 toTxn = tcSX->mainTxn;
2759
2760 if ( rod->from != NULL )
2761 {
2762 fromSplit = NULL;
2763 }
2764 else
2765 {
2766 fromSplit = find_account_from_template_splits (tcSX->mainTxn, ldd->ld.repFromAcct);
2767 }
2768 }
2769
2770 if ( fromSplit != NULL )
2771 {
2772 /* Update the existing from-split. */
2773 gstr = g_string_new (fromSplit->get_credit_formula ());
2774 g_string_append_printf( gstr, " + %s", amtBuf );
2775 fromSplit->set_credit_formula (gstr->str);
2776 g_string_free( gstr, TRUE );
2777 gstr = NULL;
2778
2779 }
2780 else
2781 {
2782 TTInfoPtr tti;
2783 /* Create a new from-split. */
2784 ttsi = std::make_shared<TTSplitInfo>();
2785 ttsi->set_memo (rod->txnMemo);
2786 ttsi->set_account (rod->from ? rod->from : ldd->ld.repFromAcct);
2787 ttsi->set_credit_formula (amtBuf);
2788 tti = tcSX->mainTxn;
2789 if ( rod->throughEscrowP )
2790 {
2791 tti = paymentSX->mainTxn;
2792 }
2793 tti->append_template_split (ttsi);
2794 ttsi = NULL;
2795 tti = NULL;
2796 }
2797
2798 /* Add to-account split. */
2799 {
2800 ttsi = std::make_shared<TTSplitInfo>();
2801 ttsi->set_memo (rod->txnMemo);
2802 ttsi->set_account (rod->to);
2803 ttsi->set_debit_formula (amtBuf);
2804 toTxn->append_template_split (ttsi);
2805 ttsi = NULL;
2806 }
2807}
2808
2809
2824static
2825void
2826loan_create_sxes( LoanAssistantData *ldd )
2827{
2828 /* The main loan-payment SX.*/
2829 toCreateSX *paymentSX = NULL;
2830 /* A GList of any other repayment SXes with different schedule. */
2831 GList *repaySXes = NULL;
2832 /* The currently-being-referenced toCreateSX. */
2833 toCreateSX *tcSX;
2834 int i;
2835 TTInfoPtr ttxn;
2836 TTSplitInfoPtr ttsi;
2837
2838 paymentSX = g_new0( toCreateSX, 1 );
2839 paymentSX->name = g_strdup(ldd->ld.repMemo);
2840 paymentSX->start = *ldd->ld.startDate;
2841 paymentSX->last = *ldd->ld.repStartDate;
2842 g_date_subtract_months( &paymentSX->last, 1 );
2843 {
2844 paymentSX->end = *ldd->ld.repStartDate;
2845 g_date_add_months( &paymentSX->end, ldd->ld.numMonRemain - 1);
2846 }
2847
2848 paymentSX->schedule = ldd->ld.repayment_schedule;
2849 /* Figure out the correct current instance-count for the txns in the
2850 * SX. */
2851 paymentSX->instNum =
2852 (ldd->ld.numPer * ( ldd->ld.perSize == GNC_YEARS ? 12 : 1 ))
2853 - ldd->ld.numMonRemain + 1;
2854
2855 paymentSX->mainTxn = std::make_shared<TTInfo>();
2856 paymentSX->mainTxn->set_currency (gnc_default_currency());
2857
2858 {
2859 GString *payMainTxnDesc = g_string_sized_new( 32 );
2860 g_string_printf( payMainTxnDesc,
2861 "%s - %s",
2862 ldd->ld.repMemo,
2863 ( ldd->ld.escrowAcct == NULL
2864 ? _("Payment")
2865 : _("Escrow Payment") )
2866 );
2867
2868 paymentSX->mainTxn->set_description(payMainTxnDesc->str);
2869 g_string_free( payMainTxnDesc, TRUE );
2870 }
2871
2872 /* Create the basic txns and splits...
2873 *
2874 * ttxn <- mainTxn
2875 * srcAcct <- assets
2876 * if ( escrow ) {
2877 * realSrcAcct <- srcAcct
2878 * srcAcct <- escrow;
2879 * ttxn <- escrowTxn
2880 * main.splits += split( realSrcAcct, -pmt )
2881 * main.splits += split( escrow, pmt )
2882 * }
2883 * ttxn.splits += split( escrow, -pmt)
2884 * ttxn.splits += split( liability, ppmt )
2885 * ttxn.splits += split( expenses:interest, ipmt ) */
2886
2887 {
2888 Account *srcAcct;
2889
2890 ttxn = paymentSX->mainTxn;
2891 srcAcct = ldd->ld.repFromAcct;
2892 if ( ldd->ld.escrowAcct != NULL )
2893 {
2894 Account *realSrcAcct = srcAcct;
2895 srcAcct = ldd->ld.escrowAcct;
2896 auto formula = loan_get_pmt_formula(ldd);
2897 /* ttxn.splits += split( realSrcAcct, -pmt ); */
2898 {
2899 ttsi = std::make_shared<TTSplitInfo>();
2900 ttsi->set_memo (ldd->ld.repMemo);
2901 ttsi->set_account (realSrcAcct);
2902 ttsi->set_credit_formula (formula.c_str());
2903 ttxn->append_template_split (ttsi);
2904 ttsi = NULL;
2905 }
2906 /* ttxn.splits += split( escrowAcct, +pmt ); */
2907 {
2908 ttsi = std::make_shared<TTSplitInfo>();
2909 ttsi->set_memo (ldd->ld.repMemo);
2910 ttsi->set_account (ldd->ld.escrowAcct);
2911 ttsi->set_debit_formula (formula.c_str());
2912 ttxn->append_template_split (ttsi);
2913 ttsi = NULL;
2914 }
2915 paymentSX->escrowTxn = std::make_shared<TTInfo>();
2916 paymentSX->escrowTxn->set_currency (gnc_default_currency());
2917
2918 {
2919 GString *escrowTxnDesc;
2920 escrowTxnDesc = g_string_new( ldd->ld.repMemo );
2921 g_string_append_printf( escrowTxnDesc, " - %s", _("Payment") );
2922 paymentSX->escrowTxn->set_description (escrowTxnDesc->str);
2923 g_string_free( escrowTxnDesc, TRUE );
2924 }
2925 ttxn = paymentSX->escrowTxn;
2926 }
2927 /* ttxn.splits += split( srcAcct, -pmt ); */
2928 {
2929 ttsi = std::make_shared<TTSplitInfo>();
2930 {
2931 auto gstr = g_string_new( ldd->ld.repMemo );
2932 g_string_append_printf( gstr, " - %s",
2933 _("Payment") );
2934 ttsi->set_memo (gstr->str);
2935 g_string_free( gstr, TRUE );
2936 }
2937 ttsi->set_account (srcAcct);
2938 auto formula = loan_get_pmt_formula(ldd);
2939 ttsi->set_credit_formula (formula.c_str());
2940 ttxn->append_template_split (ttsi);
2941 ttsi = NULL;
2942 }
2943 /* ttxn.splits += split( ldd->ld.repPriAcct, +ppmt ); */
2944 {
2945 ttsi = std::make_shared<TTSplitInfo>();
2946 {
2947 auto gstr = g_string_new( ldd->ld.repMemo );
2948 g_string_append_printf( gstr, " - %s",
2949 _("Principal") );
2950 ttsi->set_memo (gstr->str);
2951 g_string_free( gstr, TRUE );
2952 }
2953 ttsi->set_account (ldd->ld.repPriAcct);
2954 auto formula = loan_get_ppmt_formula(ldd);
2955 ttsi->set_debit_formula (formula.c_str());
2956 ttxn->append_template_split (ttsi);
2957 ttsi = NULL;
2958 }
2959 /* ttxn.splits += split( ldd->ld.repIntAcct, +ipmt ); */
2960 {
2961 ttsi = std::make_shared<TTSplitInfo>();
2962 {
2963 auto gstr = g_string_new( ldd->ld.repMemo );
2964 g_string_append_printf( gstr, " - %s",
2965 _("Interest") );
2966 ttsi->set_memo (gstr->str);
2967 g_string_free( gstr, TRUE );
2968 }
2969 ttsi->set_account (ldd->ld.repIntAcct);
2970 auto formula = loan_get_ipmt_formula(ldd);
2971 ttsi->set_debit_formula (formula.c_str());
2972 ttxn->append_template_split (ttsi);
2973 ttsi = NULL;
2974 }
2975 }
2976 for ( i = 0; i < ldd->ld.repayOptCount; i++ )
2977 {
2978 RepayOptData *rod = ldd->ld.repayOpts[i];
2979 if ( ! rod->enabled )
2980 continue;
2981
2982 tcSX = paymentSX;
2983 if ( rod->schedule != NULL )
2984 {
2985 tcSX = g_new0( toCreateSX, 1 );
2986 auto gstr = g_string_new( ldd->ld.repMemo );
2987 g_string_append_printf( gstr, " - %s",
2988 rod->name );
2989 tcSX->name = g_strdup(gstr->str);
2990 tcSX->start = *ldd->ld.startDate;
2991 tcSX->last = *ldd->ld.repStartDate;
2992 {
2993 tcSX->end = tcSX->last;
2994 g_date_add_months( &tcSX->end, ldd->ld.numMonRemain );
2995 }
2996 tcSX->schedule = rod->schedule;
2997 /* So it won't get destroyed when the close the
2998 * Assistant. */
2999 tcSX->instNum =
3000 ld_calc_sx_instance_num(&tcSX->start, rod->schedule);
3001 rod->schedule = NULL;
3002 tcSX->mainTxn = std::make_shared<TTInfo>();
3003 tcSX->mainTxn->set_currency(gnc_default_currency());
3004 tcSX->mainTxn->set_description (gstr->str);
3005 tcSX->escrowTxn = std::make_shared<TTInfo>();
3006 tcSX->escrowTxn->set_currency (gnc_default_currency());
3007 tcSX->escrowTxn->set_description(gstr->str);
3008
3009 g_string_free( gstr, TRUE );
3010 repaySXes = g_list_prepend (repaySXes, tcSX);
3011
3012 }
3013
3014 /* repayment */
3015 ld_setup_repayment_sx( ldd, rod, paymentSX, tcSX );
3016 }
3017
3018 repaySXes = g_list_reverse (repaySXes);
3019 /* Create the SXes */
3020 {
3021 GList *l;
3022
3023 loan_create_sx_from_tcSX( ldd, paymentSX );
3024
3025 for ( l = repaySXes; l; l = l->next )
3026 {
3027 loan_create_sx_from_tcSX( ldd, (toCreateSX*)l->data );
3028 }
3029 }
3030 /* Clean up. */
3031 loan_tcSX_free( paymentSX, NULL );
3032 g_list_foreach( repaySXes, loan_tcSX_free, NULL );
3033 g_list_free( repaySXes );
3034}
3035
3036/************************ Assistant Functions ***************************/
3037
3038void
3039loan_assistant_finish ( GtkAssistant *gtkassistant, gpointer user_data )
3040{
3041 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
3042 loan_create_sxes( ldd );
3043
3044}
3045
3046
3047void
3048loan_assistant_cancel( GtkAssistant *gtkassistant, gpointer user_data )
3049{
3050 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
3051 gnc_close_gui_component_by_data( DIALOG_LOAN_ASSISTANT_CM_CLASS, ldd );
3052}
3053
3054
3055void
3056loan_assistant_close( GtkAssistant *gtkassistant, gpointer user_data )
3057{
3058 LoanAssistantData *ldd = static_cast<LoanAssistantData*> (user_data);
3059 gnc_close_gui_component_by_data( DIALOG_LOAN_ASSISTANT_CM_CLASS, ldd );
3060}
3061
3062
3063void
3064loan_assistant_prepare (GtkAssistant *assistant, GtkWidget *page,
3065 gpointer user_data)
3066{
3067 gint currentpage = gtk_assistant_get_current_page(assistant);
3068
3069 switch (currentpage)
3070 {
3071 case 1:
3072 /* Current page is info page */
3073 loan_info_prep (assistant, user_data);
3074 break;
3075 case 2:
3076 /* Current page is Options page */
3077 loan_opt_prep (assistant, user_data);
3078 break;
3079 case 3:
3080 /* Current page is Repayments page */
3081 loan_rep_prep (assistant, user_data);
3082 break;
3083 case 4:
3084 /* Current page is Repayments Options page */
3085 loan_pay_prep (assistant, user_data);
3086 break;
3087 case 5:
3088 /* Current page is Review page */
3089 loan_rev_prep (assistant, user_data);
3090 break;
3091 }
3092}
3093
3094
3095/********************************************************************\
3096 * gnc_ui_sx_loan_assistant_create *
3097 * opens up a window to start the loan Assistant *
3098 * *
3099\********************************************************************/
3100void
3101gnc_ui_sx_loan_assistant_create (void)
3102{
3103 LoanAssistantData *ldd;
3104 gint component_id;
3105
3106 ldd = g_new0 (LoanAssistantData, 1);
3107
3108 gnc_loan_assistant_create (ldd);
3109
3110 component_id = gnc_register_gui_component (DIALOG_LOAN_ASSISTANT_CM_CLASS,
3111 NULL, loan_assistant_close_handler,
3112 ldd);
3113
3114 gnc_gui_component_watch_entity_type (component_id,
3115 GNC_ID_ACCOUNT,
3116 QOF_EVENT_MODIFY | QOF_EVENT_DESTROY);
3117
3118 gtk_widget_show_all (ldd->window);
3119
3120 gnc_window_adjust_for_screen (GTK_WINDOW(ldd->window));
3121}
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.