mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
2002-07-10 Joshua Sled <jsled@asynchronous.org>
* src/gnome/druid-loan.[ch]: Added; Initial implementation of Gnome Druid for setting up loan-repayment Scheduled Transactions. * src/doc/loans.txt: Added; notes about how loans will be dealt with in GnuCash. * src/gnome/glade/sched-xact.glade: Added loan-druid. * src/gnome/window-main.c (gnc_main_window_create_menus): Added 'Mortgage/Loan Repayment Setup' Druid invocation menu item. * src/scm/fin.scm: Added. Implementations of 'ipmt', 'ppmt', 'pmt' and supporting code. * src/app-utils/test/test-exp-parser.c (test_parser): Added tests for functions-in-expressions. Added [passed] test for Conrad's bug. * src/app-utils/gnc-exp-parser.c (func_op): Added. gnc-side callback for dealing with a function in an expression. * src/calculation/expression_parser.c: Added initial, undocumented support for functions in expressions. git-svn-id: svn+ssh://svn.gnucash.org/repo/gnucash/trunk@7115 57a11ea4-9604-0410-9ed3-97b8803252fd
This commit is contained in:
parent
5165819059
commit
0ebf4e00d1
26
ChangeLog
26
ChangeLog
@ -1,3 +1,29 @@
|
||||
2002-07-10 Joshua Sled <jsled@asynchronous.org>
|
||||
|
||||
* src/gnome/druid-loan.[ch]: Added; Initial implementation of
|
||||
Gnome Druid for setting up loan-repayment Scheduled Transactions.
|
||||
|
||||
* src/doc/loans.txt: Added; notes about how loans will be dealt
|
||||
with in GnuCash.
|
||||
|
||||
* src/gnome/glade/sched-xact.glade: Added loan-druid.
|
||||
|
||||
* src/gnome/window-main.c (gnc_main_window_create_menus): Added
|
||||
'Mortgage/Loan Repayment Setup' Druid invocation menu item.
|
||||
|
||||
* src/scm/fin.scm: Added. Implementations of 'ipmt', 'ppmt', 'pmt'
|
||||
and supporting code.
|
||||
|
||||
* src/app-utils/test/test-exp-parser.c (test_parser): Added tests
|
||||
for functions-in-expressions. Added [passed] test for Conrad's
|
||||
bug.
|
||||
|
||||
* src/app-utils/gnc-exp-parser.c (func_op): Added. gnc-side
|
||||
callback for dealing with a function in an expression.
|
||||
|
||||
* src/calculation/expression_parser.c: Added initial, undocumented
|
||||
support for functions in expressions.
|
||||
|
||||
2002-07-10 Derek Atkins <derek@ihtfp.com>
|
||||
|
||||
* add Billable flag and Bill-To (owner) to line-item entries.
|
||||
|
@ -22,6 +22,8 @@
|
||||
|
||||
#include <ctype.h>
|
||||
#include <locale.h>
|
||||
#include <string.h>
|
||||
|
||||
#include <glib.h>
|
||||
#include <guile/gh.h>
|
||||
|
||||
@ -341,6 +343,40 @@ update_variables (var_store_ptr vars)
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void*
|
||||
func_op( const char *fname,
|
||||
int argc, void **argv )
|
||||
{
|
||||
SCM scmFn, scmArgs, scmTmp;
|
||||
int i;
|
||||
gnc_numeric n, *result;
|
||||
GString *realFnName;
|
||||
|
||||
realFnName = g_string_sized_new( strlen(fname) + 5 );
|
||||
g_string_sprintf( realFnName, "gnc:%s", fname );
|
||||
scmFn = gh_eval_str_with_standard_handler( realFnName->str );
|
||||
g_string_free( realFnName, TRUE );
|
||||
if ( ! gh_procedure_p( scmFn ) ) {
|
||||
/* FIXME: handle errors correctly. */
|
||||
printf( "gnc:\"%s\" is not a scm procedure\n", fname );
|
||||
return NULL;
|
||||
}
|
||||
scmArgs = gh_list( SCM_UNDEFINED );
|
||||
for ( i=0; i<argc; i++ ) {
|
||||
/* cons together back-to-front. */
|
||||
n = *(gnc_numeric*)argv[argc - i - 1];
|
||||
scmTmp = gh_double2scm( gnc_numeric_to_double( n ) );
|
||||
scmArgs = gh_cons( scmTmp, scmArgs );
|
||||
}
|
||||
scmTmp = gh_apply( scmFn, scmArgs );
|
||||
|
||||
result = g_new0( gnc_numeric, 1 );
|
||||
*result = double_to_gnc_numeric( gh_scm2double(scmTmp), 1,
|
||||
GNC_DENOM_SIGFIG | (6<<8) );
|
||||
return (void*)result;
|
||||
}
|
||||
|
||||
static void *
|
||||
trans_numeric(const char *digit_str,
|
||||
char radix_point,
|
||||
@ -455,7 +491,8 @@ gnc_exp_parser_parse_separate_vars (const char * expression,
|
||||
lc = gnc_localeconv ();
|
||||
|
||||
pe = init_parser (vars, *lc->mon_decimal_point, *lc->mon_thousands_sep,
|
||||
trans_numeric, numeric_ops, negate_numeric, g_free);
|
||||
trans_numeric, numeric_ops, negate_numeric, g_free,
|
||||
func_op);
|
||||
|
||||
error_loc = parse_string (&result, expression, pe);
|
||||
|
||||
@ -545,6 +582,8 @@ gnc_exp_parser_error_string (void)
|
||||
return _("Undefined character");
|
||||
case NOT_A_VARIABLE:
|
||||
return _("Not a variable");
|
||||
case NOT_A_FUNC:
|
||||
return _("Not a defined function");
|
||||
case PARSER_OUT_OF_MEMORY:
|
||||
return _("Out of memory");
|
||||
case NUMERIC_ERROR:
|
||||
|
@ -1,5 +1,8 @@
|
||||
#include <glib.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#include "guile/gh.h"
|
||||
|
||||
#include "gnc-exp-parser.h"
|
||||
#include "gnc-numeric.h"
|
||||
@ -54,7 +57,19 @@ run_parser_test (TestNode *node)
|
||||
gnc_numeric result;
|
||||
char *error_loc;
|
||||
|
||||
result = gnc_numeric_error( -1 );
|
||||
printf( "Running test \"%s\" = ", node->test_name );
|
||||
succeeded = gnc_exp_parser_parse (node->exp, &result, &error_loc);
|
||||
{
|
||||
int pass;
|
||||
pass = succeeded;
|
||||
pass ^= node->should_succeed;
|
||||
pass = !pass;
|
||||
printf( "%0.2f [%s]\n",
|
||||
( ( pass && node->should_succeed ) ?
|
||||
gnc_numeric_to_double( result ) : 0.0 ),
|
||||
(pass ? "PASS" : "FAIL" ) );
|
||||
}
|
||||
|
||||
if (succeeded != node->should_succeed)
|
||||
{
|
||||
@ -116,6 +131,31 @@ test_parser (void)
|
||||
add_pass_test ("5 * 6", NULL, gnc_numeric_create (30, 1));
|
||||
add_pass_test (" 34 / (22) ", NULL, gnc_numeric_create (34, 22));
|
||||
add_pass_test (" (4 + 5 * 2) - 7 / 3", NULL, gnc_numeric_create (35, 3));
|
||||
add_pass_test ("1 + 2 * 3 + 4 + 5 * 6 * 7", NULL, gnc_numeric_create(221, 1) );
|
||||
add_pass_test( "1 - 2 * 3 + 4 - 5 * 6 * 7", NULL,
|
||||
gnc_numeric_create(-211, 1) );
|
||||
add_pass_test( "Conrad's bug",
|
||||
"22.32 * 2 + 16.8 + 34.2 * 2 + 18.81 + 85.44"
|
||||
"- 42.72 + 13.32 + 15.48 + 23.4 + 115.4",
|
||||
gnc_numeric_create(35897, 100) );
|
||||
|
||||
gh_eval_str( "(define (plus a b) (+ a b))" );
|
||||
add_pass_test( "plus( 1 : 2 ) + 3", NULL, gnc_numeric_create( 6, 1 ) );
|
||||
add_pass_test( "plus( 1 : 2 ) * 3", NULL, gnc_numeric_create( 9, 1 ) );
|
||||
add_pass_test( "plus( 1 + 2 : 3 ) * 5", NULL, gnc_numeric_create( 30, 1 ) );
|
||||
add_pass_test( "plus( ( 1 + 2 ) * 3 : 4 ) + 5", NULL, gnc_numeric_create( 18, 1) );
|
||||
add_pass_test( "5 + plus( ( 1 + 2 ) * 3 : 4 )", NULL, gnc_numeric_create( 18, 1) );
|
||||
add_pass_test( "plus( plus( 1 : 2 ) : 3 )", NULL, gnc_numeric_create( 6, 1 ) );
|
||||
add_pass_test( "plus( 4 : plus( plus( 1 : 2 ) : 3))", NULL, gnc_numeric_create( 10, 1 ) );
|
||||
|
||||
gh_eval_str( "(define (foo a b) (+ a b))" );
|
||||
add_pass_test( "foo( 1 : 2 ) + 4", NULL, gnc_numeric_create( 7, 1 ) );
|
||||
add_pass_test( "foo( (1 + 2 * 3) : 4 ) + 5",
|
||||
NULL, gnc_numeric_create( 16, 1 ) );
|
||||
add_pass_test( "foo( 1 : 2 ) + foo( 3 : 4 ) + 5",
|
||||
NULL, gnc_numeric_create( 15, 1 ) );
|
||||
add_pass_test( "foo( a = 42 : foo( foo( 1 : 2 ) : 6 * 7 )) + a",
|
||||
NULL, gnc_numeric_create( 129, 1 ) );
|
||||
|
||||
run_parser_tests ();
|
||||
|
||||
@ -124,10 +164,17 @@ test_parser (void)
|
||||
}
|
||||
|
||||
int
|
||||
main (int argc, char **argv)
|
||||
real_main (int argc, char **argv)
|
||||
{
|
||||
/* set_should_print_success (TRUE); */
|
||||
test_parser();
|
||||
print_test_results();
|
||||
exit(get_rv());
|
||||
}
|
||||
|
||||
int main( int argc, char **argv )
|
||||
{
|
||||
/* do things this way so we can test scheme function calls from expressions */
|
||||
gh_enter( argc, argv, real_main );
|
||||
|
||||
}
|
||||
|
@ -365,9 +365,15 @@
|
||||
|
||||
#include <glib.h>
|
||||
|
||||
#include <guile/gh.h>
|
||||
|
||||
#include "../engine/gnc-numeric.h"
|
||||
|
||||
#define EXPRESSION_PARSER_STATICS
|
||||
#include "finvar.h"
|
||||
|
||||
#define MAX_FUNC_ARG_LEN 255
|
||||
|
||||
/* structure to hold parser environment - environment particular to
|
||||
* each caller */
|
||||
typedef struct parser_env
|
||||
@ -375,11 +381,8 @@ typedef struct parser_env
|
||||
unsigned stack_cnt;
|
||||
unsigned stack_size;
|
||||
var_store_ptr *stack;
|
||||
|
||||
var_store_ptr predefined_vars;
|
||||
|
||||
var_store_ptr named_vars;
|
||||
|
||||
var_store_ptr unnamed_vars;
|
||||
|
||||
const char *parse_str;
|
||||
@ -402,6 +405,7 @@ typedef struct parser_env
|
||||
void *(*numeric_ops) (char op_sym, void *left_value, void *right_value);
|
||||
void *(*negate_numeric) (void *value);
|
||||
void (*free_numeric) (void *numeric_value);
|
||||
void *(*func_op)( const char *fname, int argc, void **argv );
|
||||
}
|
||||
parser_env;
|
||||
|
||||
@ -409,6 +413,8 @@ parser_env;
|
||||
#include "fin_static_proto.h"
|
||||
#include "fin_spl_protos.h"
|
||||
|
||||
#define FN_TOKEN 'F'
|
||||
#define ARG_TOKEN ':'
|
||||
#define VAR_TOKEN 'V'
|
||||
#define NUM_TOKEN 'I'
|
||||
|
||||
@ -418,7 +424,7 @@ parser_env;
|
||||
|
||||
#define NAMED_INCR 5
|
||||
|
||||
static char allowed_operators[] = "+-*/()=";
|
||||
static char allowed_operators[] = "+-*/()=:";
|
||||
|
||||
parser_env_ptr
|
||||
init_parser (var_store_ptr predefined_vars,
|
||||
@ -432,7 +438,9 @@ init_parser (var_store_ptr predefined_vars,
|
||||
void *left_value,
|
||||
void *right_value),
|
||||
void *negate_numeric (void *value),
|
||||
void free_numeric (void *numeric_value))
|
||||
void free_numeric (void *numeric_value),
|
||||
void *func_op( const char *fname,
|
||||
int argc, void **argv ))
|
||||
{
|
||||
parser_env_ptr pe = g_new0 (parser_env, 1);
|
||||
|
||||
@ -450,6 +458,7 @@ init_parser (var_store_ptr predefined_vars,
|
||||
pe->numeric_ops = numeric_ops;
|
||||
pe->negate_numeric = negate_numeric;
|
||||
pe->free_numeric = free_numeric;
|
||||
pe->func_op = func_op;
|
||||
|
||||
return pe;
|
||||
} /* init_parser */
|
||||
@ -741,27 +750,14 @@ next_token (parser_env_ptr pe)
|
||||
{
|
||||
add_token (pe, EOS);
|
||||
}
|
||||
/* test for name */
|
||||
else if (isalpha (*str_parse) || (*str_parse == '_'))
|
||||
{
|
||||
add_token (pe, VAR_TOKEN);
|
||||
nstr = pe->name;
|
||||
do
|
||||
{
|
||||
*nstr++ = *str_parse++;
|
||||
}
|
||||
while ((*str_parse == '_') ||
|
||||
isalpha (*str_parse) ||
|
||||
isdigit (*str_parse));
|
||||
|
||||
*nstr = EOS;
|
||||
}
|
||||
/* test for possible operator */
|
||||
else if (strchr (allowed_operators, *str_parse))
|
||||
{
|
||||
add_token (pe, *str_parse++);
|
||||
if (*str_parse == ASN_OP)
|
||||
{
|
||||
/* BUG/FIXME: this allows '(=' and ')=' [?], neither of which make
|
||||
* sense. */
|
||||
if (pe->Token != ASN_OP)
|
||||
{
|
||||
str_parse++;
|
||||
@ -772,6 +768,38 @@ next_token (parser_env_ptr pe)
|
||||
pe->error_code = UNDEFINED_CHARACTER;
|
||||
} /* endif */
|
||||
}
|
||||
/* test for name */
|
||||
else if (isalpha (*str_parse)
|
||||
|| (*str_parse == '_'))
|
||||
{
|
||||
int funcFlag = 0;
|
||||
|
||||
/* Check for variable or function */
|
||||
/* If variable: add token. */
|
||||
/* If function: parse args, build struct, add token. */
|
||||
nstr = pe->name;
|
||||
do
|
||||
{
|
||||
if ( *str_parse == '(' ) {
|
||||
funcFlag = 1;
|
||||
str_parse++;
|
||||
break;
|
||||
}
|
||||
*nstr++ = *str_parse++;
|
||||
}
|
||||
while ((*str_parse == '_')
|
||||
|| (*str_parse == '(')
|
||||
|| isalpha (*str_parse)
|
||||
|| isdigit (*str_parse));
|
||||
|
||||
*nstr = EOS;
|
||||
if ( funcFlag ) {
|
||||
add_token( pe, FN_TOKEN );
|
||||
} else {
|
||||
add_token (pe, VAR_TOKEN);
|
||||
}
|
||||
|
||||
}
|
||||
/* test for numeric token */
|
||||
else if ((number = pe->trans_numeric (str_parse, pe->radix_point,
|
||||
pe->group_char, &nstr)))
|
||||
@ -790,6 +818,15 @@ next_token (parser_env_ptr pe)
|
||||
pe->parse_str = str_parse;
|
||||
} /* next_token */
|
||||
|
||||
/* evaluate function operators
|
||||
* <name>( arg0, arg1, ... )
|
||||
*/
|
||||
static void
|
||||
function_op( parser_env_ptr pe )
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/* evaluate assignment operators,
|
||||
* =
|
||||
* +=
|
||||
@ -857,8 +894,9 @@ assignment_op (parser_env_ptr pe)
|
||||
vl->value = vr->value;
|
||||
vr->value = NULL;
|
||||
}
|
||||
else
|
||||
else {
|
||||
pe->numeric_ops (ASN_OP, vl->value, vr->value);
|
||||
}
|
||||
|
||||
free_var (vr, pe);
|
||||
} /* endif */
|
||||
@ -997,11 +1035,14 @@ multiply_divide_op (parser_env_ptr pe)
|
||||
* named variables
|
||||
* numerics
|
||||
* grouped expressions, "()"
|
||||
* functions [ <name>( [exp, exp, ..., exp] ) ]
|
||||
*/
|
||||
static void
|
||||
primary_exp (parser_env_ptr pe)
|
||||
{
|
||||
var_store_ptr rslt = NULL;
|
||||
char *fnIdent;
|
||||
int funcArgCount;
|
||||
char LToken = pe->Token;
|
||||
|
||||
next_token (pe);
|
||||
@ -1011,7 +1052,6 @@ primary_exp (parser_env_ptr pe)
|
||||
switch (LToken)
|
||||
{
|
||||
case '(':
|
||||
/*add_sub_op(pe); */
|
||||
assignment_op (pe);
|
||||
if (pe->error_code)
|
||||
return;
|
||||
@ -1058,6 +1098,51 @@ primary_exp (parser_env_ptr pe)
|
||||
pe->numeric_value = NULL;
|
||||
break;
|
||||
|
||||
case FN_TOKEN:
|
||||
fnIdent = pe->name;
|
||||
funcArgCount = 0;
|
||||
|
||||
do {
|
||||
assignment_op(pe);
|
||||
if ( pe->error_code )
|
||||
return;
|
||||
|
||||
funcArgCount++;
|
||||
if ( pe->Token == ')' ) {
|
||||
break;
|
||||
}
|
||||
next_token(pe);
|
||||
} while ( pe->Token != ARG_TOKEN );
|
||||
|
||||
if ( pe->Token != ')' ) {
|
||||
add_token( pe, EOS );
|
||||
pe->error_code = UNBALANCED_PARENS;
|
||||
}
|
||||
|
||||
{
|
||||
int i;
|
||||
var_store_ptr val;
|
||||
void **argv;
|
||||
|
||||
argv = g_new0( void*, funcArgCount );
|
||||
for ( i=0; i<funcArgCount; i++ ) {
|
||||
/* fill back-to-front */
|
||||
val = pop(pe);
|
||||
argv[funcArgCount - i - 1] = val->value;
|
||||
}
|
||||
rslt = get_unnamed_var(pe);
|
||||
rslt->value = (*pe->func_op)( fnIdent, funcArgCount, argv );
|
||||
g_free( argv );
|
||||
if ( rslt->value == NULL ) {
|
||||
pe->error_code = NOT_A_FUNC;
|
||||
add_token( pe, EOS );
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
next_token(pe);
|
||||
break;
|
||||
|
||||
case VAR_TOKEN:
|
||||
rslt = get_named_var (pe);
|
||||
break;
|
||||
@ -1065,4 +1150,5 @@ primary_exp (parser_env_ptr pe)
|
||||
|
||||
if (rslt != NULL)
|
||||
push (rslt, pe);
|
||||
|
||||
} /* primary_exp */
|
||||
|
@ -220,6 +220,10 @@ void parse_error(unsigned error_code,
|
||||
case NOT_A_VARIABLE:
|
||||
err_str = "Need a Variable on Left side of assignment operator, '='\n";
|
||||
break;
|
||||
case NOT_A_FUNC:
|
||||
err_str = "Need a valid Function name.\n";
|
||||
break;
|
||||
|
||||
} /* endswitch */
|
||||
printf(err_str);
|
||||
printf("%s\n",buf_start);
|
||||
|
@ -37,6 +37,8 @@ parser_env_ptr init_parser(
|
||||
void *left_value,
|
||||
void *right_value),
|
||||
void *negate_numeric(void *value),
|
||||
void free_numeric(void *numeric_value));
|
||||
void free_numeric(void *numeric_value),
|
||||
void *func_op( const char *fname,
|
||||
int argc, void **argv ) );
|
||||
|
||||
#endif
|
||||
|
@ -45,6 +45,7 @@ typedef enum
|
||||
STACK_UNDERFLOW,
|
||||
UNDEFINED_CHARACTER,
|
||||
NOT_A_VARIABLE,
|
||||
NOT_A_FUNC,
|
||||
PARSER_OUT_OF_MEMORY,
|
||||
NUMERIC_ERROR,
|
||||
PARSER_NUM_ERRORS
|
||||
|
475
src/doc/loans.txt
Normal file
475
src/doc/loans.txt
Normal file
@ -0,0 +1,475 @@
|
||||
Handling loan repayment in GnuCash::Scheduled Transactions
|
||||
------------------------------------------------------------
|
||||
July, 2002 - jsled@asychronous.org
|
||||
|
||||
Bugs 84892 and 84877 detail a request for a new Loan/Mortgage account type,
|
||||
and Scheduled Transaction support for loan repayment. While it's debatable
|
||||
that a new account type is required, there is definitely a need for Scheduled
|
||||
Transaction support for interest/payment computation for a parameterized
|
||||
"loan repayment SX".
|
||||
|
||||
The nature of this support will not create a new top-level account type, but
|
||||
instead will result in the following changes:
|
||||
a. Support in the SX credit/debit formulas for such calculations.
|
||||
b. A Druid to assist in the creation of such SXes.
|
||||
[c. budgeting/tool bench support in the future]
|
||||
|
||||
We define loan repayment values in the following terms:
|
||||
|
||||
Identifiers:
|
||||
P : the original principal. This is the overall principal afforded by the
|
||||
loan at the time of it's creation.
|
||||
P' : The beginning principal. This is the principal at the time of entry
|
||||
into GnuCash.
|
||||
I : The interest rate associated with the loan. Note that this may change
|
||||
over time [based on an addition to the Prime rate, for instance], at
|
||||
various frequencies [yearly, monthly, quarterly...]. Ideally, we can
|
||||
use the FreqSpec mechanism to facilitate the interest rate adjustment.
|
||||
N : The length of the loan in periods.
|
||||
m : The minimum periodic payment.
|
||||
n : The current period of the repayment.
|
||||
|
||||
Functions:
|
||||
PMT : Total equal periodic payment, as per Gnumeric/Excel's definitions
|
||||
[see end for more detail].
|
||||
IPMT : Monthly payment interest portion, ""
|
||||
PPMT : Monthly payment principal portion, ""
|
||||
|
||||
[ NOTE: 'PMT(I,N,P) = IPMT(I, n, N, P) + PPMT(I, n, N, P)' for 0 <= n < N ]
|
||||
|
||||
|
||||
The formula entered into the SX template for a loan may then look like:
|
||||
|
||||
Example 1:
|
||||
Desc/Memo | Account | Credit | Debit
|
||||
----------+-----------------------------+----------------+-------------------
|
||||
Repayment | Assets:Bank:Checking | | =PMT(I,n,N,P)
|
||||
| | | + fixed_amt
|
||||
Interest | Expenses:Loan_Name:Interest | =IPMT(I,n,N,P) |
|
||||
PMI | Expenses:Loan_Name:Misc | fixed_amt |
|
||||
Principal | Liabilities:Loan_Name | =PPMT(I,n,N,P) |
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
Or, in the case where an escrow account is involved [with thanks to warlord
|
||||
for the review and fixes]:
|
||||
|
||||
Example 2:
|
||||
Desc/Memo | Account | Credit | Debit
|
||||
---------------+-----------------------------+----------------+--------------
|
||||
Repayment | Assets:Bank:Checking | | =PMT(I,n,N,P)
|
||||
| | | + escrow_amt
|
||||
| | | + fixed_amt
|
||||
| | | + pre_payment
|
||||
Escrow | Assets:Loan_Escrow_acct | escrow_amt |
|
||||
Interest | Expenses:Loan_Name:Interest | =IPMT(I,n,N,P) |
|
||||
PMI | Expenses:Loan_Name:Misc | fixed_amt |
|
||||
Principal | Liabilities:Loan_Name | =PPMT(I,n,N,P) |
|
||||
| | + pre_payment |
|
||||
FreqSpec = 1 month
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
Desc/Memo | Account | Credit | Debit
|
||||
---------------+-----------------------------+----------------+--------------
|
||||
Insurance | Assets:Loan_Escrow_acct | | insurance_amt
|
||||
Insurance | Expenses:Home_Insurance | insurance_amt |
|
||||
FreqSpec = 1 year
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
Desc/Memo | Account | Credit | Debit
|
||||
---------------+-----------------------------+----------------+--------------
|
||||
Taxes | Assets:Loan_Escrow_acct | | taxes_amt
|
||||
Taxes | Expenses:Property_Taxes | taxes_amt |
|
||||
FreqSpec = Quarterly
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
|
||||
-------------------------
|
||||
|
||||
Practical questions regarding the implementation of this facility are:
|
||||
|
||||
-----
|
||||
| 1. The transactions as in Example 2 are not going to be scheduled for the
|
||||
| same day; are their values linked at all / do they need to share the
|
||||
| same var bindings?
|
||||
|
||||
Yes, they would want to be linked. More precisely, the insurance/tax amounts
|
||||
are very likely linked to the escrow_amt in Ex.2. Unfortunately, these are
|
||||
very likely separate SXes...
|
||||
|
||||
-----
|
||||
| 2. How does this effect the SX implementation of variables?
|
||||
|
||||
Vastly.
|
||||
|
||||
It becomes clear that multiple SXes will be related. While they'll have
|
||||
separate FreqSpecs and template transactions, they'll share some state. For
|
||||
both visualization [i.e., the SX list] and processing [credit/debit cell
|
||||
value computation] we'll want some manner of dealing with this.
|
||||
|
||||
It becomes clear as well that the nature of variables and functions needs to
|
||||
be more clearly defined with respect to these issues. We probably want to
|
||||
institute a clear policy for the scoping of variables. As well, since the
|
||||
SXes will have different instantiation dates, we'll need a method and
|
||||
implementation for the relation of SXes to each other.
|
||||
|
||||
A substantial hurdle is that if a set of SXes are [strongly] related, there
|
||||
is no-longer a single instantiation date for a set of related SXes. In fact,
|
||||
there may be different frequencies of recurrence.
|
||||
|
||||
One option -- on the surface -- to relate them would be to maintain an
|
||||
instance variable-binding frame cache, which would store user-entered and
|
||||
computed variable bindings. The first instantiated SX of the set would create
|
||||
the frame, and the "last" instance would clean it up. First "last" instance
|
||||
is defined by the last-occurring SX in a related set, in a given time range.
|
||||
|
||||
For example: a loan SX-set is defined by two monthly SXes ["repayment" and
|
||||
"insurance"], and a quarterly "tax" SX. The first monthly SX would create a
|
||||
frame, which would be passed two the second monthly SX. This would occur for
|
||||
the 3 months of interest. The Quarterly SX would get all 3 frames for it's
|
||||
creation, and use them in an /appropriate/ [read: to be defined through a lot
|
||||
of pain] way. As the time-based dependency relationship between the frames
|
||||
plays out, the frame can be removed from the system.
|
||||
|
||||
Another option is to toss this idea entirely and instead let the user DTRT
|
||||
manually.
|
||||
|
||||
A related option is to add the necessary grouping mechanism to the SX
|
||||
storage/data structure: immediately allowing visual grouping of related SXes,
|
||||
and potentially allowing a storage place for such frame data in the future
|
||||
with less file-versioning headache. This is the option that will be pursued.
|
||||
|
||||
|
||||
Another element implicit in the original requirements to support
|
||||
loans/repayment calculations is implicit variables. These are symbolic names
|
||||
which can be used and are automagically bound to values. The known implicit
|
||||
variables to support loan/repayment are:
|
||||
|
||||
P [loan principal amount], N [loan repayment periods], I [interest], m
|
||||
[minimum payment] and n [current period]. Some of these [P, N, I, m] are
|
||||
fixed over many instances; some [n] are rebound specific to the instance.
|
||||
See the 'variable-scope-frame' below for a method of handling these
|
||||
variables.
|
||||
|
||||
And yet-another element implicit in the original requirement is support for
|
||||
detecting and computing the result of functions in the template transaction's
|
||||
credit/debit cells. Changes to the src/app-utils/gnc-exp-parser.[hc] and
|
||||
src/calculation/expression_parser.[ch] to support functions would be
|
||||
necessitated. It is conceivable that after parsing, the parsed expression
|
||||
could be passed to scheme for evaluation. Hopefully this would make it
|
||||
easier to add support for new functions to the SX code via Scheme.
|
||||
|
||||
|
||||
-----
|
||||
| 3. How do we deal with periodic [yearly, semi-yearly] updating of various
|
||||
| "fixed" variables?
|
||||
|
||||
Another change in the way variables are used is that some SXes -- especially
|
||||
loan-repayment -- may involve variables which are not tied to the instance of
|
||||
the SX, but rather to variables which:
|
||||
. are also involved in another SX
|
||||
. change with a frequency different than the SX
|
||||
. are represented by a relationship to the outside world ["prime + 1.7"]
|
||||
|
||||
A partial fix for this problem is to provide multiple levels of scope for
|
||||
variable bindings, and expose this to the user by a method of assigning
|
||||
[perhaps time-dependent] values to these variables. Variables bound in this
|
||||
manner would absolve the user of the need to bind them at SX-creation time.
|
||||
|
||||
An added benefit of this would be to allow some users [see Bug#85707] have
|
||||
"fixed variable" values for a group of SXes.
|
||||
|
||||
In combination with the SX Grouping, this would provide most of a fix for the
|
||||
problem described in #2, above. The variable_frame could be used to provide
|
||||
the shared-state between related SXes, without imposing quite the same
|
||||
burden. This approach is slightly less flexible, but that allows it to be
|
||||
implemented more readily, and understood more easily.
|
||||
|
||||
A question which comes up when thinking about yearly-changing values such as
|
||||
interest rates is if the historical information needs to be versioned. For
|
||||
now, we punt on this issue, but hopefully will provide enough of a framework
|
||||
for this to be reasonably added in the future.
|
||||
|
||||
|
||||
We define four types of variables supported by this scheme:
|
||||
|
||||
implicit : provided only by the system
|
||||
e.g.: 'n', the current index of the repayment
|
||||
|
||||
transient : have user-defined values, bound at instantiation time.
|
||||
e.g.: existing ad-hoc variables in SXes.
|
||||
|
||||
static : have a user-defined values, and are not expected to change with
|
||||
any measurable frequency. The user may change these at their
|
||||
leisure, but no facility to assist or encourage this is
|
||||
provided.
|
||||
e.g.: paycheck amount, loan principal amount
|
||||
|
||||
periodic : have user-defined values which change at specific points in
|
||||
time [July 1, yearly]. After the expiration of a variable value,
|
||||
it's re-binding will prevent any dependent SXes from being
|
||||
created.
|
||||
e.g.: loan tax amount, loan interest rate
|
||||
|
||||
-----
|
||||
| 4. From where do we get the dollar amount against which to do the [PI]PMT
|
||||
| calculation?
|
||||
|
||||
The user will specify the parameters of the Loan via some UI... then where
|
||||
does the data go?
|
||||
|
||||
. KVP data for that account?
|
||||
. KVP data for the SX?
|
||||
. Do we have a different top-level "Loan" object?
|
||||
. Present only in the SX template transactions/variable-frames?
|
||||
|
||||
|
||||
I believe that the only location of the data after Druid creation is in the
|
||||
variable-binding frames and the formulae in the template transactions. The
|
||||
Druid would thus simply assist the user in creating the following SX-related
|
||||
structures:
|
||||
|
||||
. SXGroup: Loan Repayment
|
||||
. variable_frame
|
||||
. P [static]
|
||||
. N [static]
|
||||
. n [implicit]
|
||||
. I [periodic]
|
||||
. pmi_amount [periodic]
|
||||
. tax_amount [periodic]
|
||||
. pre_payment [periodic]
|
||||
. insurance_amount [periodic]
|
||||
. SX: Payment
|
||||
. Bank -> { Escrow,
|
||||
Liability:Loan:Principal,
|
||||
Expense:Loan:Interest,
|
||||
Expense:Loan:Insurance }
|
||||
. SX: Tax
|
||||
. Escrow -> Expense:Tax
|
||||
. SX: Insurance
|
||||
. Escrow -> Expense:Insurance
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
Questions
|
||||
|
||||
1/ UI - visible should all this machination be to the user? Should they even
|
||||
see them as such. The current SX since-last-run UI makes them pretty
|
||||
visible, and in my estimation it actually helps to make them a bit more
|
||||
formal and visible. At the same time, it may be overwhelming for the user
|
||||
to have to create formal variables with weird types like "implicit",
|
||||
"transient", "static", and "periodic".
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
Priorities, Plan
|
||||
|
||||
The above represents an "ideal" set of extensions to the SX framework to
|
||||
enable multiple "enhancement"-level functionalities. Therefore, the
|
||||
following is the prioritized schedule, with annotations:
|
||||
|
||||
1. Functions [PMT, [IP]PMT] in exp_parser; implicit variables [n].
|
||||
2. [Visual-only] SX grouping
|
||||
3. Loan-repayment creation Druid
|
||||
4. SX-only static vars
|
||||
5. SX-only periodic vars
|
||||
6. SX-group vars, var_frames
|
||||
|
||||
After the completion of item 4, the feature can safely be called "finished".
|
||||
Items 5 and 6 only serve to increase the robustness of the facility and make
|
||||
the user's life slightly easier, at the cost of making _my_ life harder. :)
|
||||
|
||||
--------------------------------------------------
|
||||
|
||||
Reference
|
||||
-------------------------
|
||||
|
||||
Other software:
|
||||
----------
|
||||
|
||||
Gnumeric supports the following functions WRT payment calculation:
|
||||
|
||||
* PMT( rate, nper, pv [, fv, type] )
|
||||
PMT returns the amount of payment for a loan based on a constant interest
|
||||
rate and constant payments (ea. payment equal).
|
||||
@rate : constant interest rate
|
||||
@nper : overall number of payments
|
||||
@pv : present value
|
||||
@fv : future value
|
||||
@type : payment type
|
||||
. 0 : end of period
|
||||
. 1 : beginning of period
|
||||
|
||||
* IPMT( rate, per, nper, pv, fv, type )
|
||||
IPMT calculates the amount of a payment of an annuity going towards
|
||||
interest. Formula for IPMT is:
|
||||
IPMT(per) = - principal(per-1) * interest_rate
|
||||
where:
|
||||
principal(per-1) = amount of the remaining principal from last period.
|
||||
|
||||
* ISPMT( rate, per, nper, pv )
|
||||
ISPMT returns the interest paid on a given period.
|
||||
If @per < 1 or @per > @nper, returns #NUM! err.
|
||||
|
||||
* PPMT(rate, per, nper, pv [, fv, type] )
|
||||
PPMT calculates the amount of a payment of an annuity going towards
|
||||
principal.
|
||||
PPMT(per) = PMT - IPMT(per)
|
||||
where: PMT is payment
|
||||
IPMT is interest for period per
|
||||
|
||||
* PV( rate, nper, pmt [, fv, type] )
|
||||
Calculates the present value of an investment
|
||||
@rate : periodic interest rate
|
||||
@nper : number of compounding periods
|
||||
@pmt : payment made each period
|
||||
@fv : future value
|
||||
|
||||
|
||||
--------------------------------------------------
|
||||
--------------------------------------------------
|
||||
|
||||
Day Two:
|
||||
|
||||
As per warlord's comments, the definition of IPMT needs to be updated to
|
||||
account for principal pre-payment. IPMT is actually defined by computation
|
||||
of the value of an account at a specified point in time. This is significant
|
||||
if the loan repayments involve interest.
|
||||
|
||||
In the face of creating multiple scheduled transactions for a time range, it
|
||||
may be the case that the relevant account balance is not actually posted to
|
||||
the account at the time of the variable binding. If we intend to show the
|
||||
user an estimation of the IPMT cell value during variable binding, then we
|
||||
would need to do something creative about this ... but as it stands, we'll
|
||||
leave this as an Excercise for the Reader. :)
|
||||
|
||||
|
||||
-----
|
||||
|
||||
Druid thoughts...
|
||||
|
||||
Page Order:
|
||||
Intro ->
|
||||
Params ->
|
||||
Opts ->
|
||||
Repayment ->
|
||||
[Insurance ->]
|
||||
[PMI ->]
|
||||
[Taxes ->]
|
||||
Review/Approval
|
||||
|
||||
|
||||
--------------------------------------------------
|
||||
| Intro
|
||||
--------------------------------------------------
|
||||
|
||||
"This is a step-by-step method of creating a loan
|
||||
repayment setup within GnuCash. In this Druid,
|
||||
you can input the parameters of your loan and
|
||||
it's repayment and give the details of it's
|
||||
payback. Using that information, the appropriate
|
||||
Scheduled Transactions will be created.
|
||||
|
||||
"If you make a mistake or want to make changes
|
||||
later, you can edit the created Scheduled
|
||||
Transactions directly."
|
||||
--------------------------------------------------
|
||||
|
||||
|
||||
--------------------------------------------------
|
||||
| Params
|
||||
--------------------------------------------------
|
||||
Principal : [amount entry]
|
||||
Actual Principal : [[optional] amount entry]
|
||||
Interest Rate : [numeric entry] %
|
||||
Type : [ ] Fixed
|
||||
[ ] Variable ---------+
|
||||
| Type : 10/1,7/1,...|
|
||||
| When : [freqspec?] |
|
||||
+---------------------+
|
||||
Start Date : [Gnome Date Entry]
|
||||
Length : [num entry] [years|v]
|
||||
Remaining : [num entry]
|
||||
--------------------------------------------------
|
||||
|
||||
|
||||
--------------------------------------------------
|
||||
| Options
|
||||
--------------------------------------------------
|
||||
Do you...
|
||||
[ ] ... utilize an escrow account?
|
||||
Account: [ acct select |v]
|
||||
[ ] ... pay PMI?
|
||||
[ ] Via the Escrow account?
|
||||
[ ] ... pay insurance?
|
||||
[ ] Via the Escrow account?
|
||||
[ ] ... pay taxes?
|
||||
[ ] Via the Escrow account?
|
||||
[ ] ... have some other expense not listed above?
|
||||
[ ] Via the Escrow account?
|
||||
--------------------------------------------------
|
||||
|
||||
|
||||
--------------------------------------------------
|
||||
| Repayment
|
||||
--------------------------------------------------
|
||||
Amount : [ amount entry ]
|
||||
Assets from : [ account sel |v]
|
||||
Princiapl to : [ account sel |v]
|
||||
Interest to : [ account sel |v]
|
||||
Escrow to : [ account sel |v]
|
||||
Remainder to : [{escrow,principal,interest}|v]
|
||||
Frequency : +- freqspec ----------------+
|
||||
| .... |
|
||||
+---------------------------+
|
||||
--------------------------------------------------
|
||||
|
||||
--------------------------------------------------
|
||||
| Insurance
|
||||
--------------------------------------------------
|
||||
Amount : [ amount entry ]
|
||||
Account : [ account sel |v]
|
||||
Frequency :
|
||||
[ ] Part of Repayment Transaction
|
||||
[ ] Other: +- freqspec ----------------+
|
||||
| .... |
|
||||
+---------------------------+
|
||||
--------------------------------------------------
|
||||
|
||||
|
||||
--------------------------------------------------
|
||||
| Taxes/PMI/Other
|
||||
--------------------------------------------------
|
||||
[ same as Insurance ]
|
||||
--------------------------------------------------
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
===========================================================================
|
||||
===========================================================================
|
||||
|
||||
Options in repayment:
|
||||
. loan freq != repayment freq
|
||||
. floated
|
||||
. not
|
||||
|
||||
. Where does over-payment go?
|
||||
. where
|
||||
. into the escrow account
|
||||
. directly applied
|
||||
. how
|
||||
. towards principal [interest is then re-calculated]
|
||||
. towards interest [principal is then re-calculated]
|
||||
|
||||
. still to do...
|
||||
. expression parser/gnc-exp-parser extensions to handle...
|
||||
. ...symbols [account names] into functions
|
||||
. ...errors better
|
||||
. ...iter/count/implicit vars
|
||||
. druid...
|
||||
. add ipmt', ppmt' calculations, using above
|
||||
. kvp storage of "real" data
|
||||
. sx grouping
|
||||
|
||||
http://www.interest.com/hugh/calc/mort_links.html
|
@ -41,6 +41,7 @@ libgncgnome_la_SOURCES = \
|
||||
dialog-userpass.c \
|
||||
dialog-scheduledxaction.c \
|
||||
druid-hierarchy.c \
|
||||
druid-loan.c \
|
||||
druid-stock-split.c \
|
||||
gnc-network.c \
|
||||
gnc-splash.c \
|
||||
@ -77,6 +78,7 @@ noinst_HEADERS = \
|
||||
dialog-transfer.h \
|
||||
dialog-scheduledxaction.h \
|
||||
druid-hierarchy.h \
|
||||
druid-loan.h \
|
||||
gnc-network.h \
|
||||
gnc-splash.h \
|
||||
gw-gnc.h \
|
||||
|
995
src/gnome/druid-loan.c
Normal file
995
src/gnome/druid-loan.c
Normal file
@ -0,0 +1,995 @@
|
||||
/********************************************************************\
|
||||
* druid-loan.c : A Gnome Druid for setting up loan-repayment *
|
||||
* scheduled transactions. *
|
||||
* Copyright (C) 2002 Joshua Sled <jsled@asynchronous.org> *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License as *
|
||||
* published by the Free Software Foundation; either version 2 of *
|
||||
* the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License*
|
||||
* along with this program; if not, contact: *
|
||||
* *
|
||||
* Free Software Foundation Voice: +1-617-542-5942 *
|
||||
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
|
||||
* Boston, MA 02111-1307, USA gnu@gnu.org *
|
||||
\********************************************************************/
|
||||
|
||||
#include "config.h"
|
||||
#include <string.h>
|
||||
#include <gtk/gtk.h>
|
||||
#include <glib.h>
|
||||
#include <glade/glade.h>
|
||||
#include <gnome.h>
|
||||
|
||||
#include "druid-loan.h"
|
||||
|
||||
#include "gnc-component-manager.h"
|
||||
#include "dialog-utils.h"
|
||||
#include "Account.h"
|
||||
#include "FreqSpec.h"
|
||||
#include "gnc-ui.h"
|
||||
#include "gnc-gui-query.h"
|
||||
#include "gnc-ui-util.h"
|
||||
#include "gnc-frequency.h"
|
||||
|
||||
#define DIALOG_LOAN_DRUID_CM_CLASS "druid-loan-setup"
|
||||
|
||||
#define SX_GLADE_FILE "sched-xact.glade"
|
||||
#define LOAN_DRUID_WIN_GLADE_NAME "loan_druid_win"
|
||||
#define LOAN_DRUID_GLADE_NAME "loan_druid"
|
||||
|
||||
#define PG_INTRO "loan_intro_pg"
|
||||
#define PG_INFO "loan_info_pg"
|
||||
# define PARAM_TABLE "param_table"
|
||||
# define ORIG_PRINC_GNE "orig_princ_gne"
|
||||
# define ORIG_PRINC_ENTRY "orig_princ_ent"
|
||||
# define CUR_PRINC_GNE "cur_princ_gne"
|
||||
# define CUR_PRINC_ENTRY "cur_princ_ent"
|
||||
# define IRATE_SPIN "irate_spin"
|
||||
# define TYPE_OPT "type_opt"
|
||||
# define VAR_CONTAINER "type_freq_frame"
|
||||
# define START_DATE "start_gde"
|
||||
# define LENGTH_SPIN "len_spin"
|
||||
# define LENGTH_OPT "len_opt"
|
||||
# define REMAIN_SPIN "rem_spin"
|
||||
#define PG_OPTS "loan_opts_pg"
|
||||
# define OPT_CONTAINER "opt_vbox"
|
||||
# define OPT_ESCROW "opt_escrow"
|
||||
# define OPT_ESCROW_CONTAINER "opt_escrow_hbox"
|
||||
#define PG_REPAYMENT "repayment_pg"
|
||||
# define TXN_NAME "txn_title"
|
||||
# define REPAY_TABLE "repay_table"
|
||||
# define AMOUNT_GNE "amount_gne"
|
||||
# define AMOUNT_ENTRY "amount_ent"
|
||||
# define REMAINDER_OPT "remain_opt"
|
||||
# define FREQ_CONTAINER "freq_frame"
|
||||
#define PG_PAYMENT "payment_pg"
|
||||
# define PAY_TXN_TITLE "pay_txn_title"
|
||||
# define PAY_AMT_GNE "pay_amt_gne"
|
||||
# define PAY_AMT_ENTRY "pay_amt_ent"
|
||||
# define PAY_TABLE "pay_table"
|
||||
# define PAY_TXN_PART_RB "pay_txn_part_rb"
|
||||
# define PAY_UNIQ_FREQ_RB "pay_uniq_freq_rb"
|
||||
# define PAY_FREQ_CONTAINER "pay_freq_align"
|
||||
#define PG_FINISH "finish_pg"
|
||||
|
||||
#define OPT_VBOX_SPACING 2
|
||||
|
||||
/**
|
||||
* TODO/fixme:
|
||||
* . param account selection should fill in orig/cur principal amounts from
|
||||
* the books.
|
||||
* . initialize type freq to monthly.
|
||||
* . if LoanType <- !FIXED
|
||||
* . Frequency <- sensitive
|
||||
**/
|
||||
|
||||
/**
|
||||
* The data relating to a single "repayment option" -- a potential
|
||||
* [sub-]transaction in the repayment.
|
||||
**/
|
||||
typedef struct RepayOptData_ {
|
||||
gboolean enabled;
|
||||
char *name; // { "insurance", "pmi", "taxes", ... }
|
||||
char *txnMemo;
|
||||
float amount;
|
||||
gboolean throughEscrowP;
|
||||
Account *to;
|
||||
Account *from; // If NULL { If throughEscrowP, then through escrowP;
|
||||
// else: undefined.
|
||||
FreqSpec *fs; // If NULL, part of repayment; otherwise: defined here.
|
||||
GDate *startDate;
|
||||
} RepayOptData;
|
||||
|
||||
/**
|
||||
* The default repayment options data.
|
||||
**/
|
||||
typedef struct RepayOptDataDefault_ {
|
||||
char *name;
|
||||
char *defaultTxnMemo;
|
||||
gboolean escrowDefault;
|
||||
} RepayOptDataDefault;
|
||||
|
||||
static RepayOptDataDefault REPAY_DEFAULTS[] = {
|
||||
/* { name, default txn memo, throughEscrowP } */
|
||||
{ "Taxes", "Tax Payment", FALSE },
|
||||
{ "Insurance", "Insurance Payment", FALSE },
|
||||
{ "PMI", "PMI Payment", FALSE },
|
||||
{ "Other Expense", "Miscellaneous Payment", FALSE },
|
||||
{ NULL }
|
||||
};
|
||||
|
||||
/**
|
||||
* The UI-side storage of the repayment options.
|
||||
**/
|
||||
typedef struct RepayOptUI_ {
|
||||
GtkCheckButton *optCb;
|
||||
GtkCheckButton *escrowCb;
|
||||
RepayOptData *optData;
|
||||
} RepayOptUIData;
|
||||
|
||||
typedef enum {
|
||||
FIXED = 0,
|
||||
VARIABLE,
|
||||
VARIABLE_5_1 = VARIABLE,
|
||||
VARIABLE_7_1,
|
||||
VARIABLE_10_1,
|
||||
/* ... FIXME */
|
||||
} LoanType;
|
||||
|
||||
typedef enum {
|
||||
MONTHS = 0,
|
||||
YEARS
|
||||
} PeriodSize;
|
||||
|
||||
/**
|
||||
* Data about a loan repayment.
|
||||
**/
|
||||
typedef struct LoanData_ {
|
||||
Account *primaryAcct;
|
||||
float principal;
|
||||
float principalLeft;
|
||||
float interestRate;
|
||||
LoanType type;
|
||||
FreqSpec *loanFreq;
|
||||
GDate *startDate;
|
||||
GDate *varStartDate;
|
||||
int numPer;
|
||||
int numPerRemain;
|
||||
PeriodSize perSize;
|
||||
|
||||
int repayOptCount;
|
||||
RepayOptData **repayOpts;
|
||||
} LoanData;
|
||||
|
||||
/**
|
||||
* The UI-side storage of the loan druid data.
|
||||
**/
|
||||
typedef struct LoanDruidData_ {
|
||||
GladeXML *gxml;
|
||||
GtkWidget *dialog;
|
||||
GnomeDruid *druid;
|
||||
|
||||
LoanData ld;
|
||||
/* The UI-side storage of repayment data; this is 1:1 with the array
|
||||
* in LoanData */
|
||||
RepayOptUIData **repayOptsUI;
|
||||
|
||||
/* Current index of the payment opt for multiplexing the 'payment'
|
||||
* page. */
|
||||
int currentIdx;
|
||||
|
||||
/* widgets */
|
||||
/* prm = params */
|
||||
GtkTable *prmTable;
|
||||
GnomeNumberEntry *prmOrigPrincGNE;
|
||||
GtkEntry *prmOrigPrincEntry;
|
||||
GnomeNumberEntry *prmCurPrincGNE;
|
||||
GtkEntry *prmCurPrincEntry;
|
||||
GtkSpinButton *prmIrateSpin;
|
||||
GtkOptionMenu *prmType;
|
||||
GtkFrame *prmVarFrame;
|
||||
GNCFrequency *prmVarGncFreq;
|
||||
GnomeDateEdit *prmStartDateGDE;
|
||||
GtkSpinButton *prmLengthSpin;
|
||||
GtkOptionMenu *prmLengthType;
|
||||
GtkSpinButton *prmRemainSpin;
|
||||
|
||||
/* opt = options */
|
||||
GtkVBox *optVBox;
|
||||
GtkCheckButton *optEscrowOpt;
|
||||
GtkHBox *optEscrowHBox;
|
||||
|
||||
/* rep = repayment */
|
||||
GtkEntry *repTxnName;
|
||||
GtkTable *repTable;
|
||||
GnomeNumberEntry *repAmtGNE;
|
||||
GtkEntry *repAmtEntry;
|
||||
GtkOptionMenu *repRemainderOpt;
|
||||
GtkFrame *repFreqFrame;
|
||||
GNCFrequency *repGncFreq;
|
||||
|
||||
/* pay = payment[s] */
|
||||
GtkEntry *payTxnName;
|
||||
GnomeNumberEntry *payAmtGNE;
|
||||
GtkEntry *payAmtEntry;
|
||||
GtkTable *payTable;
|
||||
GtkRadioButton *payTxnFreqPartRb;
|
||||
GtkRadioButton *payTxnFreqUniqRb;
|
||||
GtkAlignment *payFreqAlign;
|
||||
GNCFrequency *payGncFreq;
|
||||
} LoanDruidData;
|
||||
|
||||
static void gnc_loan_druid_data_init( LoanDruidData *ldd );
|
||||
static void gnc_loan_druid_get_widgets( LoanDruidData *ldd );
|
||||
|
||||
static void ld_close_handler( LoanDruidData *ldd );
|
||||
static void ld_destroy( GtkObject *o, gpointer ud );
|
||||
|
||||
static void ld_cancel_check( GnomeDruid *gd, LoanDruidData *ldd );
|
||||
|
||||
static void ld_opt_toggled( GtkToggleButton *tb, gpointer ud );
|
||||
static void ld_opt_consistency( GtkToggleButton *tb, gpointer ud );
|
||||
static void ld_escrow_tog( GtkToggleButton *tb, gpointer ud );
|
||||
|
||||
static void ld_pay_freq_toggle( GtkToggleButton *tb, gpointer ud );
|
||||
|
||||
static gboolean ld_info_save( GnomeDruidPage *gdp, gpointer arg1, gpointer ud );
|
||||
static void ld_info_prep( GnomeDruidPage *gdp, gpointer arg1, gpointer ud );
|
||||
static gboolean ld_opts_next( GnomeDruidPage *gdp, gpointer arg1, gpointer ud );
|
||||
static void ld_opts_prep( GnomeDruidPage *gdp, gpointer arg1, gpointer ud );
|
||||
static gboolean ld_rep_next ( GnomeDruidPage *gdp, gpointer arg1, gpointer ud );
|
||||
static void ld_rep_prep ( GnomeDruidPage *gdp, gpointer arg1, gpointer ud );
|
||||
static gboolean ld_pay_next ( GnomeDruidPage *gdp, gpointer arg1, gpointer ud );
|
||||
static void ld_pay_prep ( GnomeDruidPage *gdp, gpointer arg1, gpointer ud );
|
||||
static gboolean ld_pay_back ( GnomeDruidPage *gdp, gpointer arg1, gpointer ud );
|
||||
static gboolean ld_fin_back ( GnomeDruidPage *gdp, gpointer arg1, gpointer ud );
|
||||
|
||||
struct LoanDruidData_*
|
||||
gnc_ui_sx_loan_druid_create()
|
||||
{
|
||||
static struct {
|
||||
char *pageName;
|
||||
gboolean (*nextFn)();
|
||||
void (*prepFn)();
|
||||
gboolean (*backFn)();
|
||||
void (*finishFn)();
|
||||
/* cancel is handled by the druid itself. */
|
||||
} DRUID_HANDLERS[] = {
|
||||
{ PG_INFO, ld_info_save, ld_info_prep, ld_info_save, NULL },
|
||||
{ PG_OPTS, ld_opts_next, ld_opts_prep, NULL, NULL },
|
||||
{ PG_REPAYMENT, ld_rep_next, ld_rep_prep },
|
||||
{ PG_PAYMENT, ld_pay_next, ld_pay_prep, ld_pay_back, NULL },
|
||||
{ PG_FINISH, NULL, NULL, ld_fin_back, NULL },
|
||||
{ NULL }
|
||||
};
|
||||
int i;
|
||||
LoanDruidData *ldd;
|
||||
|
||||
ldd = g_new0( LoanDruidData, 1 );
|
||||
|
||||
gnc_loan_druid_data_init( ldd );
|
||||
|
||||
ldd->gxml = gnc_glade_xml_new( SX_GLADE_FILE, LOAN_DRUID_WIN_GLADE_NAME );
|
||||
ldd->dialog = glade_xml_get_widget( ldd->gxml, LOAN_DRUID_WIN_GLADE_NAME );
|
||||
ldd->druid = GNOME_DRUID(glade_xml_get_widget( ldd->gxml,
|
||||
LOAN_DRUID_GLADE_NAME ));
|
||||
|
||||
/* get pointers to the various widgets */
|
||||
gnc_loan_druid_get_widgets( ldd );
|
||||
|
||||
/* FIXME: non-gladeable widget setup */
|
||||
{
|
||||
{
|
||||
GtkAdjustment *a;
|
||||
|
||||
/* 8.0 [%], range of 0.05..100.0 with ticks at 0.05[%]. */
|
||||
a = GTK_ADJUSTMENT(gtk_adjustment_new( 8.0, 0.05,
|
||||
100.0, 0.05,
|
||||
1.0, 1.0 ));
|
||||
gtk_spin_button_set_adjustment( ldd->prmIrateSpin, a );
|
||||
gtk_spin_button_set_value( ldd->prmIrateSpin, 8.00 );
|
||||
gtk_spin_button_set_snap_to_ticks( ldd->prmIrateSpin,
|
||||
TRUE );
|
||||
|
||||
a = GTK_ADJUSTMENT(gtk_adjustment_new( 360, 1,
|
||||
9999, 1,
|
||||
12, 12 ));
|
||||
gtk_spin_button_set_adjustment( ldd->prmLengthSpin, a );
|
||||
|
||||
a = GTK_ADJUSTMENT(gtk_adjustment_new( 360, 1,
|
||||
9999, 1,
|
||||
12, 12 ));
|
||||
gtk_spin_button_set_adjustment( ldd->prmRemainSpin, a );
|
||||
}
|
||||
|
||||
gnc_option_menu_init( GTK_WIDGET(ldd->prmType) );
|
||||
gnc_option_menu_init( GTK_WIDGET(ldd->prmLengthType) );
|
||||
gnc_option_menu_init( GTK_WIDGET(ldd->repRemainderOpt) );
|
||||
|
||||
/* PARAM_TABLE(0,1) += account sel */
|
||||
/* OPT_ESCROW_CONTAINER += account sel */
|
||||
{
|
||||
}
|
||||
|
||||
/* OPT_CONTAINER += option options. */
|
||||
/* . Each RepayOpt gets an entry in the optContainer.
|
||||
* . Each "entry" is a 2-line vbox containing:
|
||||
* . The checkbox for the option itself
|
||||
* . an alignment-contained sub-checkbox for "through the
|
||||
* escrow account".
|
||||
* . Hook up each to bit-twiddling the appropriate line.
|
||||
*/
|
||||
|
||||
/* FIXME : too deep, factor out. */
|
||||
{
|
||||
RepayOptUIData *rouid;
|
||||
GtkVBox *vb;
|
||||
GtkAlignment *optAlign, *subOptAlign;
|
||||
GString *str;
|
||||
|
||||
str = g_string_sized_new( 32 );
|
||||
|
||||
for ( i=0; i<ldd->ld.repayOptCount; i++ ) {
|
||||
rouid = ldd->repayOptsUI[i];
|
||||
vb = GTK_VBOX(gtk_vbox_new( FALSE, OPT_VBOX_SPACING ));
|
||||
|
||||
/* Add payment checkbox. */
|
||||
g_string_sprintf( str, "... pay \"%s\"?", rouid->optData->name );
|
||||
rouid->optCb =
|
||||
GTK_CHECK_BUTTON(gtk_check_button_new_with_label( str->str ));
|
||||
gtk_box_pack_start( GTK_BOX(vb), GTK_WIDGET(rouid->optCb),
|
||||
FALSE, FALSE, 2 );
|
||||
rouid->escrowCb =
|
||||
GTK_CHECK_BUTTON(gtk_check_button_new_with_label( _("via Escrow account?") ));
|
||||
gtk_widget_set_sensitive( GTK_WIDGET(rouid->escrowCb), FALSE );
|
||||
subOptAlign = GTK_ALIGNMENT(gtk_alignment_new( 0.5, 0.5, 0.75, 1.0 ));
|
||||
gtk_container_add( GTK_CONTAINER(subOptAlign),
|
||||
GTK_WIDGET(rouid->escrowCb) );
|
||||
gtk_box_pack_start( GTK_BOX(vb), GTK_WIDGET(subOptAlign),
|
||||
FALSE, FALSE, 2 );
|
||||
|
||||
gtk_signal_connect( GTK_OBJECT( rouid->optCb ),
|
||||
"toggled",
|
||||
GTK_SIGNAL_FUNC(ld_opt_toggled),
|
||||
(gpointer)rouid );
|
||||
gtk_signal_connect( GTK_OBJECT( rouid->optCb ),
|
||||
"toggled",
|
||||
GTK_SIGNAL_FUNC(ld_opt_consistency),
|
||||
(gpointer)rouid->escrowCb );
|
||||
gtk_signal_connect( GTK_OBJECT( rouid->escrowCb ),
|
||||
"toggled",
|
||||
GTK_SIGNAL_FUNC(ld_escrow_tog),
|
||||
(gpointer)rouid );
|
||||
|
||||
optAlign = GTK_ALIGNMENT(gtk_alignment_new( 0.5, 0.5, 0.75, 1.0 ));
|
||||
gtk_container_add( GTK_CONTAINER(optAlign),
|
||||
GTK_WIDGET(vb) );
|
||||
gtk_box_pack_start( GTK_BOX(ldd->optVBox), GTK_WIDGET(optAlign),
|
||||
FALSE, FALSE, 2 );
|
||||
gtk_widget_show_all( GTK_WIDGET(optAlign) );
|
||||
}
|
||||
g_string_free( str, TRUE );
|
||||
}
|
||||
|
||||
gtk_signal_connect( GTK_OBJECT(ldd->payTxnFreqPartRb), "toggled",
|
||||
GTK_SIGNAL_FUNC(ld_pay_freq_toggle), (gpointer)ldd );
|
||||
gtk_signal_connect( GTK_OBJECT(ldd->payTxnFreqUniqRb), "toggled",
|
||||
GTK_SIGNAL_FUNC(ld_pay_freq_toggle), (gpointer)ldd );
|
||||
|
||||
ldd->prmVarGncFreq =
|
||||
GNC_FREQUENCY(gnc_frequency_new( NULL, NULL ));
|
||||
gtk_container_add( GTK_CONTAINER(ldd->prmVarFrame),
|
||||
GTK_WIDGET(ldd->prmVarGncFreq) );
|
||||
|
||||
ldd->repGncFreq =
|
||||
GNC_FREQUENCY(gnc_frequency_new( NULL, NULL ));
|
||||
gtk_container_add( GTK_CONTAINER(ldd->repFreqFrame),
|
||||
GTK_WIDGET(ldd->repGncFreq) );
|
||||
|
||||
ldd->payGncFreq =
|
||||
GNC_FREQUENCY(gnc_frequency_new( NULL, NULL ));
|
||||
gtk_container_add( GTK_CONTAINER(ldd->payFreqAlign),
|
||||
GTK_WIDGET(ldd->payGncFreq) );
|
||||
/* FIXME: Repayment/Payment[s] pages += account sel */
|
||||
}
|
||||
|
||||
gnc_register_gui_component( DIALOG_LOAN_DRUID_CM_CLASS,
|
||||
NULL, /* no refresh handler */
|
||||
ld_close_handler,
|
||||
ldd );
|
||||
gtk_signal_connect( GTK_OBJECT(ldd->dialog), "destroy",
|
||||
GTK_SIGNAL_FUNC(ld_destroy),
|
||||
ldd );
|
||||
|
||||
/* setup page-transition handlers */
|
||||
/* setup druid-global handler for cancel */
|
||||
gtk_signal_connect( GTK_OBJECT(ldd->druid), "cancel",
|
||||
GTK_SIGNAL_FUNC(ld_cancel_check),
|
||||
(gpointer)ldd );
|
||||
/* FIXME: this is substantially similar to the code in
|
||||
* dialog-sxsincelast.c ... it should probably be factored out
|
||||
* somewhere. */
|
||||
for ( i=0; DRUID_HANDLERS[i].pageName != NULL; i++ ) {
|
||||
GtkWidget *pg;
|
||||
|
||||
pg = glade_xml_get_widget( ldd->gxml,
|
||||
DRUID_HANDLERS[i].pageName );
|
||||
if ( DRUID_HANDLERS[i].prepFn ) {
|
||||
gtk_signal_connect( GTK_OBJECT(pg), "prepare",
|
||||
GTK_SIGNAL_FUNC(DRUID_HANDLERS[i].
|
||||
prepFn),
|
||||
ldd);
|
||||
}
|
||||
if ( DRUID_HANDLERS[i].backFn ) {
|
||||
gtk_signal_connect( GTK_OBJECT(pg), "back",
|
||||
GTK_SIGNAL_FUNC(DRUID_HANDLERS[i].
|
||||
backFn),
|
||||
ldd);
|
||||
}
|
||||
if ( DRUID_HANDLERS[i].nextFn ) {
|
||||
gtk_signal_connect( GTK_OBJECT(pg), "next",
|
||||
GTK_SIGNAL_FUNC(DRUID_HANDLERS[i].
|
||||
nextFn),
|
||||
ldd);
|
||||
}
|
||||
if ( DRUID_HANDLERS[i].finishFn ) {
|
||||
gtk_signal_connect( GTK_OBJECT(pg), "finish",
|
||||
GTK_SIGNAL_FUNC(DRUID_HANDLERS[i].
|
||||
finishFn),
|
||||
ldd);
|
||||
}
|
||||
}
|
||||
|
||||
gtk_widget_show_all( ldd->dialog );
|
||||
return ldd;
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
gnc_loan_druid_data_init( LoanDruidData *ldd )
|
||||
{
|
||||
int i, optCount;
|
||||
RepayOptData *optData;
|
||||
|
||||
/* get the count of repayment defaults. */
|
||||
for ( optCount=0; REPAY_DEFAULTS[optCount].name != NULL; optCount++ )
|
||||
;
|
||||
|
||||
ldd->currentIdx = -1;
|
||||
|
||||
ldd->ld.startDate = g_date_new();
|
||||
ldd->ld.varStartDate = g_date_new();
|
||||
g_date_set_time( ldd->ld.startDate, time(NULL) );
|
||||
ldd->ld.loanFreq = xaccFreqSpecMalloc( gnc_get_current_book() );
|
||||
ldd->ld.repayOptCount = optCount;
|
||||
ldd->ld.repayOpts = g_new0( RepayOptData*, optCount );
|
||||
/* copy all the default lines into the LDD */
|
||||
ldd->repayOptsUI = g_new0( RepayOptUIData*, optCount );
|
||||
for ( i=0; i < optCount; i++ ) {
|
||||
g_assert( REPAY_DEFAULTS[i].name != NULL );
|
||||
|
||||
ldd->repayOptsUI[i] = g_new0( RepayOptUIData, 1 );
|
||||
optData = ldd->ld.repayOpts[i]
|
||||
= ldd->repayOptsUI[i]->optData
|
||||
= g_new0( RepayOptData, 1 );
|
||||
|
||||
optData->enabled = FALSE;
|
||||
optData->name = g_strdup( REPAY_DEFAULTS[i].name );
|
||||
optData->txnMemo = g_strdup( REPAY_DEFAULTS[i].
|
||||
defaultTxnMemo );
|
||||
optData->amount = 0.0;
|
||||
optData->throughEscrowP = REPAY_DEFAULTS[i].escrowDefault;
|
||||
optData->fs = NULL;
|
||||
optData->startDate = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
gnc_loan_druid_get_widgets( LoanDruidData *ldd )
|
||||
{
|
||||
g_assert( ldd != NULL );
|
||||
g_assert( ldd->gxml != NULL );
|
||||
|
||||
/* Get all widgets */
|
||||
|
||||
#define GET_CASTED_WIDGET( cast, name ) \
|
||||
(cast( glade_xml_get_widget( ldd->gxml, name ) ))
|
||||
|
||||
/* prm = params */
|
||||
ldd->prmTable =
|
||||
GET_CASTED_WIDGET( GTK_TABLE, PARAM_TABLE );
|
||||
ldd->prmOrigPrincGNE =
|
||||
GET_CASTED_WIDGET( GNOME_NUMBER_ENTRY, ORIG_PRINC_GNE );
|
||||
ldd->prmOrigPrincEntry =
|
||||
GET_CASTED_WIDGET( GTK_ENTRY, ORIG_PRINC_ENTRY );
|
||||
ldd->prmCurPrincGNE =
|
||||
GET_CASTED_WIDGET( GNOME_NUMBER_ENTRY, CUR_PRINC_GNE );
|
||||
ldd->prmCurPrincEntry =
|
||||
GET_CASTED_WIDGET( GTK_ENTRY, CUR_PRINC_ENTRY );
|
||||
ldd->prmIrateSpin =
|
||||
GET_CASTED_WIDGET( GTK_SPIN_BUTTON, IRATE_SPIN );
|
||||
ldd->prmType =
|
||||
GET_CASTED_WIDGET( GTK_OPTION_MENU, TYPE_OPT );
|
||||
ldd->prmVarFrame =
|
||||
GET_CASTED_WIDGET( GTK_FRAME, VAR_CONTAINER );
|
||||
ldd->prmStartDateGDE =
|
||||
GET_CASTED_WIDGET( GNOME_DATE_EDIT, START_DATE );
|
||||
ldd->prmLengthSpin =
|
||||
GET_CASTED_WIDGET( GTK_SPIN_BUTTON, LENGTH_SPIN );
|
||||
ldd->prmLengthType =
|
||||
GET_CASTED_WIDGET( GTK_OPTION_MENU, LENGTH_OPT );
|
||||
ldd->prmRemainSpin =
|
||||
GET_CASTED_WIDGET( GTK_SPIN_BUTTON, REMAIN_SPIN );
|
||||
|
||||
/* opt = options */
|
||||
ldd->optVBox =
|
||||
GET_CASTED_WIDGET( GTK_VBOX, OPT_CONTAINER );
|
||||
ldd->optEscrowOpt =
|
||||
GET_CASTED_WIDGET( GTK_CHECK_BUTTON, OPT_ESCROW );
|
||||
ldd->optEscrowHBox =
|
||||
GET_CASTED_WIDGET( GTK_HBOX, OPT_ESCROW_CONTAINER );
|
||||
|
||||
/* rep = repayment */
|
||||
ldd->repTxnName =
|
||||
GET_CASTED_WIDGET( GTK_ENTRY, TXN_NAME );
|
||||
ldd->repTable =
|
||||
GET_CASTED_WIDGET( GTK_TABLE, REPAY_TABLE );
|
||||
ldd->repAmtGNE =
|
||||
GET_CASTED_WIDGET( GNOME_NUMBER_ENTRY, AMOUNT_GNE );
|
||||
ldd->repAmtEntry =
|
||||
GET_CASTED_WIDGET( GTK_ENTRY, AMOUNT_ENTRY );
|
||||
ldd->repRemainderOpt =
|
||||
GET_CASTED_WIDGET( GTK_OPTION_MENU, REMAINDER_OPT );
|
||||
ldd->repFreqFrame =
|
||||
GET_CASTED_WIDGET( GTK_FRAME, FREQ_CONTAINER );
|
||||
|
||||
/* pay = payment[s] */
|
||||
ldd->payTxnName =
|
||||
GET_CASTED_WIDGET( GTK_ENTRY, PAY_TXN_TITLE );
|
||||
ldd->payAmtGNE =
|
||||
GET_CASTED_WIDGET( GNOME_NUMBER_ENTRY, PAY_AMT_GNE );
|
||||
ldd->payAmtEntry =
|
||||
GET_CASTED_WIDGET( GTK_ENTRY, PAY_AMT_ENTRY );
|
||||
ldd->payTable =
|
||||
GET_CASTED_WIDGET( GTK_TABLE, PAY_TABLE );
|
||||
ldd->payTxnFreqPartRb =
|
||||
GET_CASTED_WIDGET( GTK_RADIO_BUTTON, PAY_TXN_PART_RB );
|
||||
ldd->payTxnFreqUniqRb =
|
||||
GET_CASTED_WIDGET( GTK_RADIO_BUTTON, PAY_UNIQ_FREQ_RB );
|
||||
ldd->payFreqAlign =
|
||||
GET_CASTED_WIDGET( GTK_ALIGNMENT, PAY_FREQ_CONTAINER );
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
ld_close_handler( LoanDruidData *ldd )
|
||||
{
|
||||
gtk_widget_hide( ldd->dialog );
|
||||
gtk_widget_destroy( ldd->dialog );
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
ld_destroy( GtkObject *o, gpointer ud )
|
||||
{
|
||||
LoanDruidData *ldd;
|
||||
|
||||
ldd = (LoanDruidData*)ud;
|
||||
|
||||
g_assert( ldd );
|
||||
|
||||
gnc_unregister_gui_component_by_data
|
||||
(DIALOG_LOAN_DRUID_CM_CLASS, ldd);
|
||||
|
||||
/* FIXME: free alloc'd mem; cleanup */
|
||||
|
||||
g_free( ldd );
|
||||
}
|
||||
|
||||
|
||||
static
|
||||
void
|
||||
ld_cancel_check( GnomeDruid *gd, LoanDruidData *ldd )
|
||||
{
|
||||
const char *cancelMsg = _( "Are you sure you want to close "
|
||||
"the Mortgage/Loan Setup Druid?" );
|
||||
if ( gnc_verify_dialog_parented( ldd->dialog, FALSE, cancelMsg ) ) {
|
||||
gnc_close_gui_component_by_data( DIALOG_LOAN_DRUID_CM_CLASS,
|
||||
ldd );
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
ld_opt_toggled( GtkToggleButton *tb, gpointer ud )
|
||||
{
|
||||
RepayOptUIData *rouid;
|
||||
|
||||
rouid = (RepayOptUIData*)ud;
|
||||
rouid->optData->enabled = gtk_toggle_button_get_active(tb);
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
ld_opt_consistency( GtkToggleButton *tb, gpointer ud )
|
||||
{
|
||||
GtkToggleButton *escrowCb;
|
||||
|
||||
escrowCb = GTK_TOGGLE_BUTTON(ud);
|
||||
/* make sure the escrow option is only selected if we're active. */
|
||||
gtk_toggle_button_set_state( escrowCb,
|
||||
gtk_toggle_button_get_active(escrowCb)
|
||||
& gtk_toggle_button_get_active(tb) );
|
||||
/* make sure the escrow option is only sensitive if we're active. */
|
||||
gtk_widget_set_sensitive( GTK_WIDGET(escrowCb),
|
||||
gtk_toggle_button_get_active(tb) );
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
ld_escrow_tog( GtkToggleButton *tb, gpointer ud )
|
||||
{
|
||||
RepayOptUIData *rouid;
|
||||
|
||||
rouid = (RepayOptUIData*)ud;
|
||||
rouid->optData->throughEscrowP = gtk_toggle_button_get_active( tb );
|
||||
}
|
||||
|
||||
static
|
||||
gboolean
|
||||
ld_info_save( GnomeDruidPage *gdp, gpointer arg1, gpointer ud )
|
||||
{
|
||||
LoanDruidData *ldd;
|
||||
float amt;
|
||||
gchar *txt;
|
||||
|
||||
ldd = (LoanDruidData*)ud;
|
||||
|
||||
/* FIXME: account */
|
||||
txt = gtk_editable_get_chars( GTK_EDITABLE(ldd->prmOrigPrincEntry),
|
||||
0, -1 );
|
||||
amt = -1.0;
|
||||
amt = (float)strtod( txt, NULL );
|
||||
if ( amt < 0 ) {
|
||||
gnc_error_dialog( _("The original principal must "
|
||||
"be a valid number.") );
|
||||
return TRUE;
|
||||
}
|
||||
ldd->ld.principal = amt;
|
||||
g_free( txt );
|
||||
txt = gtk_editable_get_chars( GTK_EDITABLE(ldd->prmCurPrincEntry),
|
||||
0, -1 );
|
||||
amt = -1.0;
|
||||
amt = (float)strtod( txt, NULL );
|
||||
if ( amt < 0 ) {
|
||||
gnc_error_dialog( _("The current principal must "
|
||||
"be a valid number.") );
|
||||
return TRUE;
|
||||
}
|
||||
ldd->ld.principalLeft = amt;
|
||||
ldd->ld.interestRate =
|
||||
gtk_spin_button_get_value_as_float( ldd->prmIrateSpin );
|
||||
ldd->ld.type = gnc_option_menu_get_active( GTK_WIDGET(ldd->prmType) );
|
||||
if ( ldd->ld.type != FIXED ) {
|
||||
gnc_frequency_save_state( ldd->prmVarGncFreq,
|
||||
ldd->ld.loanFreq,
|
||||
ldd->ld.varStartDate );
|
||||
}
|
||||
|
||||
/* start date */
|
||||
{
|
||||
time_t tmpTT;
|
||||
struct tm *tmpTm;
|
||||
|
||||
tmpTT = gnome_date_edit_get_date( ldd->prmStartDateGDE );
|
||||
tmpTm = localtime( &tmpTT );
|
||||
g_date_set_dmy( ldd->ld.startDate,
|
||||
1, (tmpTm->tm_mon+1), tmpTm->tm_mday );
|
||||
}
|
||||
|
||||
/* len / periods */
|
||||
{
|
||||
ldd->ld.perSize =
|
||||
(gnc_option_menu_get_active( GTK_WIDGET(ldd->prmLengthType) )
|
||||
== MONTHS) ? MONTHS : YEARS;
|
||||
ldd->ld.numPer =
|
||||
gtk_spin_button_get_value_as_int( ldd->prmLengthSpin );
|
||||
ldd->ld.numPerRemain =
|
||||
gtk_spin_button_get_value_as_int( ldd->prmRemainSpin );
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
ld_info_prep( GnomeDruidPage *gdp, gpointer arg1, gpointer ud )
|
||||
{
|
||||
LoanDruidData *ldd;
|
||||
GString *str;
|
||||
|
||||
ldd = (LoanDruidData*)ud;
|
||||
str = g_string_sized_new( 16 );
|
||||
/* FIXME: account. */
|
||||
g_string_sprintf( str, "%0.2f", ldd->ld.principal );
|
||||
gtk_entry_set_text( ldd->prmOrigPrincEntry, str->str );
|
||||
g_string_sprintf( str, "%0.2f", ldd->ld.principalLeft );
|
||||
gtk_entry_set_text( ldd->prmCurPrincEntry, str->str );
|
||||
gtk_spin_button_set_value( ldd->prmIrateSpin, ldd->ld.interestRate );
|
||||
gtk_option_menu_set_history( ldd->prmType, ldd->ld.type );
|
||||
if ( ldd->ld.type != FIXED ) {
|
||||
gnc_frequency_setup( ldd->prmVarGncFreq,
|
||||
ldd->ld.loanFreq,
|
||||
ldd->ld.varStartDate );
|
||||
}
|
||||
|
||||
/* start date */
|
||||
{
|
||||
struct tm *tmpTm;
|
||||
|
||||
tmpTm = g_new0( struct tm, 1 );
|
||||
|
||||
g_date_to_struct_tm( ldd->ld.startDate, tmpTm );
|
||||
gnome_date_edit_set_time( ldd->prmStartDateGDE,
|
||||
mktime(tmpTm) );
|
||||
g_free( tmpTm );
|
||||
}
|
||||
|
||||
/* length: total and remaining */
|
||||
{
|
||||
// prmLengthSpin
|
||||
gtk_spin_button_set_value( ldd->prmLengthSpin, ldd->ld.numPer );
|
||||
// prmLengthType
|
||||
gtk_option_menu_set_history( ldd->prmLengthType, ldd->ld.perSize );
|
||||
// prmRemainSpin
|
||||
gtk_spin_button_set_value( ldd->prmRemainSpin, ldd->ld.numPerRemain );
|
||||
}
|
||||
|
||||
g_string_free( str, TRUE );
|
||||
}
|
||||
|
||||
static
|
||||
gboolean
|
||||
ld_opts_next( GnomeDruidPage *gdp, gpointer arg1, gpointer ud )
|
||||
{
|
||||
/* FIXME: do anything here? */
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
ld_opts_prep( GnomeDruidPage *gdp, gpointer arg1, gpointer ud )
|
||||
{
|
||||
/* FIXME: setup the options */
|
||||
}
|
||||
|
||||
static
|
||||
gboolean
|
||||
ld_rep_next( GnomeDruidPage *gdp, gpointer arg1, gpointer ud )
|
||||
{
|
||||
LoanDruidData *ldd;
|
||||
|
||||
ldd = (LoanDruidData*)ud;
|
||||
if ( (ldd->currentIdx < 0)
|
||||
|| (ldd->currentIdx >= ldd->ld.repayOptCount)
|
||||
|| !ldd->ld.repayOpts[ldd->currentIdx]->enabled ) {
|
||||
int i;
|
||||
for ( i=0;
|
||||
(i < ldd->ld.repayOptCount)
|
||||
&& !ldd->ld.repayOpts[i]->enabled;
|
||||
i++ )
|
||||
;
|
||||
if ( i == ldd->ld.repayOptCount ) {
|
||||
/* transition to final page. */
|
||||
GtkWidget *pg;
|
||||
pg = glade_xml_get_widget( ldd->gxml, PG_FINISH );
|
||||
gnome_druid_set_page( ldd->druid, GNOME_DRUID_PAGE(pg) );
|
||||
return TRUE;
|
||||
}
|
||||
ldd->currentIdx = i;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
ld_rep_prep( GnomeDruidPage *gdp, gpointer arg1, gpointer ud )
|
||||
{
|
||||
/* do anything? */
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
ld_pay_prep( GnomeDruidPage *gdp, gpointer arg1, gpointer ud )
|
||||
{
|
||||
LoanDruidData *ldd;
|
||||
RepayOptData *rod;
|
||||
GString *str;
|
||||
gboolean uniq;
|
||||
|
||||
ldd = (LoanDruidData*)ud;
|
||||
g_assert( ldd->currentIdx >= 0 );
|
||||
g_assert( ldd->currentIdx <= ldd->ld.repayOptCount );
|
||||
|
||||
rod = ldd->ld.repayOpts[ldd->currentIdx];
|
||||
str = g_string_sized_new( 32 );
|
||||
g_string_sprintf( str, "Payment: \"%s\"", rod->name );
|
||||
gnome_druid_page_standard_set_title( GNOME_DRUID_PAGE_STANDARD(gdp),
|
||||
str->str );
|
||||
/* FIXME: copy in the relevant data from the currently-indexed
|
||||
* option. */
|
||||
gtk_entry_set_text( ldd->payTxnName, rod->txnMemo );
|
||||
g_string_sprintf( str, "%0.2f", rod->amount );
|
||||
gtk_entry_set_text( ldd->payAmtEntry, str->str );
|
||||
|
||||
uniq = (rod->fs != NULL);
|
||||
gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(ldd->payTxnFreqPartRb),
|
||||
!uniq );
|
||||
gtk_toggle_button_set_active( GTK_TOGGLE_BUTTON(ldd->payTxnFreqUniqRb),
|
||||
uniq );
|
||||
gtk_widget_set_sensitive( GTK_WIDGET(ldd->payFreqAlign),
|
||||
uniq );
|
||||
if ( uniq ) {
|
||||
gnc_frequency_setup( ldd->payGncFreq,
|
||||
rod->fs, rod->startDate );
|
||||
}
|
||||
|
||||
g_string_free( str, TRUE );
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
ld_pay_save_current( LoanDruidData *ldd )
|
||||
{
|
||||
gchar *tmpStr;
|
||||
RepayOptData *rod;
|
||||
|
||||
g_assert( ldd->currentIdx >= 0 );
|
||||
g_assert( ldd->currentIdx <= ldd->ld.repayOptCount );
|
||||
rod = ldd->ld.repayOpts[ ldd->currentIdx ];
|
||||
|
||||
tmpStr = gtk_editable_get_chars( GTK_EDITABLE(ldd->payTxnName),
|
||||
0, -1 );
|
||||
if ( rod->txnMemo != NULL ) {
|
||||
g_free( rod->txnMemo );
|
||||
}
|
||||
rod->txnMemo = tmpStr;
|
||||
tmpStr = NULL;
|
||||
|
||||
tmpStr = gtk_editable_get_chars( GTK_EDITABLE(ldd->payAmtEntry),
|
||||
0, -1 );
|
||||
rod->amount = (float)strtod( tmpStr, NULL );
|
||||
g_free( tmpStr );
|
||||
|
||||
/* if ( rb toggled )
|
||||
* ensure freqspec/startdate setup
|
||||
* save
|
||||
* else
|
||||
* if (freqspec setup)
|
||||
* remove freqspec/startdate
|
||||
*/
|
||||
|
||||
/* neither of these should happen. */
|
||||
g_assert( ! (rod->fs && !rod->startDate) );
|
||||
g_assert( ! (!rod->fs && rod->startDate) );
|
||||
|
||||
if ( gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(ldd->payTxnFreqUniqRb)) ) {
|
||||
if ( rod->fs == NULL ) {
|
||||
rod->fs = xaccFreqSpecMalloc( gnc_get_current_book() );
|
||||
}
|
||||
if ( rod->startDate == NULL ) {
|
||||
rod->startDate = g_date_new();
|
||||
}
|
||||
gnc_frequency_save_state( ldd->payGncFreq,
|
||||
rod->fs, rod->startDate );
|
||||
} else {
|
||||
if ( rod->fs ) {
|
||||
xaccFreqSpecFree( rod->fs );
|
||||
rod->fs = NULL;
|
||||
}
|
||||
if ( rod->startDate ) {
|
||||
g_date_free( rod->startDate );
|
||||
rod->startDate = NULL;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
gboolean
|
||||
ld_pay_next( GnomeDruidPage *gdp, gpointer arg1, gpointer ud )
|
||||
{
|
||||
int i;
|
||||
LoanDruidData *ldd;
|
||||
|
||||
ldd = (LoanDruidData*)ud;
|
||||
/* save current data */
|
||||
ld_pay_save_current( ldd );
|
||||
|
||||
/* Go through opts list and select next enabled option. */
|
||||
for ( i=(++ldd->currentIdx);
|
||||
(i < ldd->ld.repayOptCount)
|
||||
&& !ldd->ld.repayOpts[i]->enabled; i++ )
|
||||
;
|
||||
if ( i != ldd->ld.repayOptCount ) {
|
||||
ldd->currentIdx = i;
|
||||
ld_pay_prep( gdp, arg1, ud );
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static
|
||||
gboolean
|
||||
ld_pay_back( GnomeDruidPage *gdp, gpointer arg1, gpointer ud )
|
||||
{
|
||||
int i;
|
||||
LoanDruidData *ldd;
|
||||
|
||||
ldd = (LoanDruidData*)ud;
|
||||
|
||||
/* save current data */
|
||||
ld_pay_save_current( ldd );
|
||||
|
||||
for ( i=(--ldd->currentIdx);
|
||||
(i > -1) && !ldd->ld.repayOpts[i]->enabled;
|
||||
i-- ) {
|
||||
}
|
||||
if ( i != -1 ) {
|
||||
ldd->currentIdx = i;
|
||||
ld_pay_prep( gdp, arg1, ud );
|
||||
return TRUE;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static
|
||||
gboolean
|
||||
ld_fin_back ( GnomeDruidPage *gdp, gpointer arg1, gpointer ud )
|
||||
{
|
||||
LoanDruidData *ldd;
|
||||
|
||||
ldd = (LoanDruidData*)ud;
|
||||
if ( (ldd->currentIdx < 0)
|
||||
|| (ldd->currentIdx >= ldd->ld.repayOptCount)
|
||||
|| !ldd->ld.repayOpts[ldd->currentIdx]->enabled ) {
|
||||
int i;
|
||||
for ( i=ldd->ld.repayOptCount-1;
|
||||
(i > -1)
|
||||
&& !ldd->ld.repayOpts[i]->enabled;
|
||||
i-- )
|
||||
;
|
||||
if ( i == -1 ) {
|
||||
/* transition to Repayment page. */
|
||||
GtkWidget *pg;
|
||||
pg = glade_xml_get_widget( ldd->gxml, PG_REPAYMENT );
|
||||
gnome_druid_set_page( ldd->druid, GNOME_DRUID_PAGE(pg) );
|
||||
return TRUE;
|
||||
}
|
||||
ldd->currentIdx = i;
|
||||
}
|
||||
return FALSE;
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
ld_pay_freq_toggle( GtkToggleButton *tb, gpointer ud )
|
||||
{
|
||||
LoanDruidData *ldd;
|
||||
gboolean uniq;
|
||||
|
||||
ldd = (LoanDruidData*)ud;
|
||||
|
||||
g_assert( ldd->currentIdx >= 0 );
|
||||
g_assert( ldd->currentIdx <= ldd->ld.repayOptCount );
|
||||
|
||||
uniq = gtk_toggle_button_get_active( GTK_TOGGLE_BUTTON(ldd->payTxnFreqUniqRb) );
|
||||
gtk_widget_set_sensitive( GTK_WIDGET(ldd->payFreqAlign), uniq );
|
||||
}
|
24
src/gnome/druid-loan.h
Normal file
24
src/gnome/druid-loan.h
Normal file
@ -0,0 +1,24 @@
|
||||
/********************************************************************\
|
||||
* druid-loan.h : A Gnome Druid for setting up loan-repayment *
|
||||
* scheduled transactions. *
|
||||
* Copyright (C) 2002 Joshua Sled <jsled@asynchronous.org> *
|
||||
* *
|
||||
* This program is free software; you can redistribute it and/or *
|
||||
* modify it under the terms of the GNU General Public License as *
|
||||
* published by the Free Software Foundation; either version 2 of *
|
||||
* the License, or (at your option) any later version. *
|
||||
* *
|
||||
* This program is distributed in the hope that it will be useful, *
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of *
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
|
||||
* GNU General Public License for more details. *
|
||||
* *
|
||||
* You should have received a copy of the GNU General Public License*
|
||||
* along with this program; if not, contact: *
|
||||
* *
|
||||
* Free Software Foundation Voice: +1-617-542-5942 *
|
||||
* 59 Temple Place - Suite 330 Fax: +1-617-542-2652 *
|
||||
* Boston, MA 02111-1307, USA gnu@gnu.org *
|
||||
\********************************************************************/
|
||||
|
||||
struct LoanDruidData_* gnc_ui_sx_loan_druid_create();
|
File diff suppressed because it is too large
Load Diff
@ -42,6 +42,7 @@
|
||||
#include "dialog-totd.h"
|
||||
#include "dialog-transfer.h"
|
||||
#include "dialog-utils.h"
|
||||
#include "druid-loan.h"
|
||||
#include "gfec.h"
|
||||
#include "global-options.h"
|
||||
#include "gnc-engine.h"
|
||||
@ -723,6 +724,13 @@ gnc_main_window_sched_xaction_slr_cb (GtkWidget *widget, gpointer data) {
|
||||
}
|
||||
}
|
||||
|
||||
static
|
||||
void
|
||||
gnc_main_window_sx_loan_druid_cb( GtkWidget *widget, gpointer data)
|
||||
{
|
||||
gnc_ui_sx_loan_druid_create();
|
||||
}
|
||||
|
||||
void
|
||||
gnc_main_window_about_cb (GtkWidget *widget, gpointer data)
|
||||
{
|
||||
@ -921,6 +929,14 @@ gnc_main_window_create_menus(GNCMDIInfo * maininfo)
|
||||
GNOME_APP_PIXMAP_NONE, NULL,
|
||||
0, 0, NULL
|
||||
},
|
||||
GNOMEUIINFO_SEPARATOR,
|
||||
{ GNOME_APP_UI_ITEM,
|
||||
N_( "Mortgage/Loan Repayment Setup" ),
|
||||
N_( "Setup scheduled transactions for repayment of a loan" ),
|
||||
gnc_main_window_sx_loan_druid_cb, NULL, NULL,
|
||||
GNOME_APP_PIXMAP_NONE, NULL,
|
||||
0, 0, NULL
|
||||
},
|
||||
GNOMEUIINFO_END
|
||||
};
|
||||
|
||||
|
@ -4,12 +4,13 @@ SUBDIRS = gnumeric printing
|
||||
gncscmdir = ${GNC_SCM_INSTALL_DIR}
|
||||
gncscmmoddir = ${GNC_SHAREDIR}/guile-modules/gnucash
|
||||
|
||||
gncscmmod_DATA = process.scm main.scm price-quotes.scm
|
||||
gncscmmod_DATA = process.scm main.scm price-quotes.scm fin.scm
|
||||
|
||||
gnc_regular_scm_files = \
|
||||
build-config.scm \
|
||||
command-line.scm \
|
||||
doc.scm \
|
||||
fin.scm \
|
||||
help-topics-index.scm \
|
||||
main-window.scm \
|
||||
path.scm \
|
||||
|
63
src/scm/fin.scm
Normal file
63
src/scm/fin.scm
Normal file
@ -0,0 +1,63 @@
|
||||
;; This program is free software; you can redistribute it and/or
|
||||
;; modify it under the terms of the GNU General Public License as
|
||||
;; published by the Free Software Foundation; either version 2 of
|
||||
;; the License, or (at your option) any later version.
|
||||
;;
|
||||
;; This program is distributed in the hope that it will be useful,
|
||||
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
;; GNU General Public License for more details.
|
||||
;;
|
||||
;; You should have received a copy of the GNU General Public License
|
||||
;; along with this program; if not, contact:
|
||||
;;
|
||||
;; Free Software Foundation Voice: +1-617-542-5942
|
||||
;; 59 Temple Place - Suite 330 Fax: +1-617-542-2652
|
||||
;; Boston, MA 02111-1307, USA gnu@gnu.org
|
||||
|
||||
;; Copyright 2002 Joshua Sled <jsled@asynchronous.org>
|
||||
;; pretty literal copies of similar code from gnumeric-1.0.8
|
||||
|
||||
(define (gnc:ipmt rate nper per pv fv type)
|
||||
(* rate
|
||||
(- 0 (calc-principal pv
|
||||
(calc-pmt rate nper pv fv type)
|
||||
rate (- per 1))
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
(define (gnc:ppmt rate nper per pv fv type)
|
||||
(let ((pmt (calc-pmt rate nper pv fv type)))
|
||||
(let ((ipmt (* rate
|
||||
(- 0 (calc-principal pv pmt rate (- per 1))))))
|
||||
(- pmt ipmt)))
|
||||
)
|
||||
|
||||
(define (gnc:pmt rate nper pv fv type)
|
||||
(calc-pmt rate nper pv fv type))
|
||||
|
||||
(define (calc-pmt rate nper pv fv type)
|
||||
(let ((pvif (calc-pvif rate nper))
|
||||
(fvifa (calc-fvifa rate nper)))
|
||||
(/ (- (* (- 0 pv) pvif) fv)
|
||||
(* fvifa
|
||||
(+ 1.0
|
||||
(* rate type)))))
|
||||
)
|
||||
|
||||
(define (calc-pvif rate nper)
|
||||
(expt (+ 1 rate) nper)
|
||||
)
|
||||
|
||||
(define (calc-fvifa rate nper)
|
||||
(/ (- (expt (+ 1 rate) nper) 1) rate)
|
||||
)
|
||||
|
||||
(define (calc-principal pv pmt rate per)
|
||||
(+ (* pv (expt (+ 1.0 rate) per))
|
||||
(* pmt (/ (- (expt (+ 1 rate) per)
|
||||
1)
|
||||
rate)))
|
||||
)
|
||||
|
@ -394,6 +394,8 @@ string and 'directories' must be a list of strings."
|
||||
(load-from-path "main-window.scm") ;; depends on app-utils (N_, etc.)...
|
||||
(load-from-path "tip-of-the-day.scm") ;; depends on app-utils (config-var...)
|
||||
(load-from-path "printing/print-check.scm") ;; depends on simple-obj...
|
||||
;; +jsled - 2002.07.08
|
||||
(load-from-path "fin.scm")
|
||||
|
||||
(gnc:initialize-tip-of-the-day)
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user