Merge branch 'master' into v2.7a

This commit is contained in:
John Wiegley 2008-07-27 19:50:25 -04:00
commit 0c76ac5b8f
8 changed files with 247 additions and 34 deletions

116
NEWS
View file

@ -2,7 +2,121 @@
* 2.6.1
- This version has no new features, it's all bug fixes.
- Added the concept of "balance setting transactions":
# Setting an account's balance
You can now manually set an account's balance to whatever you want, at
any time. Here's how it might look at the beginning of your Ledger
file:
2008/07/27 Starting fresh
Assets:Checking = $1,000.00
Equity:Opening Balances
If Assets:Checking is empty, this is no different from omitting the
"=". However, if Assets:Checking did have a prior balance, the amount
of the transaction will be auto-calculated so that the final balance
of Assets:Checking is now $1,000.00.
Let me give an example of this. Say you have this:
2008/07/27 Starting fresh
Assets:Checking $750.00
Equity:Opening Balances
2008/07/27 Starting fresh
Assets:Checking = $1,000.00
Equity:Adjustments
These two entries are exactly equivalent to these two:
2008/07/27 Starting fresh
Assets:Checking $750.00
Equity:Opening Balances
2008/07/27 Starting fresh
Assets:Checking $250.00
Equity:Adjustments
The use of the "=" sign here is that it sets the transaction's amount
to whatever is required to satisfy the assignment. This is the
behavior if the transaction's amount is left empty.
# Multiple commodities
As far as commodities go, the = sign only works if the account
balance's commodity matches the commodity of the amount after the
equals sign. However, if the account has multiple commodities, only
the matching commodity is affected. Here's what I mean:
2008/07/24 Opening Balance
Assets:Checking = $250.00 ; we force set it
Equity:Opening Balances
2008/07/24 Opening Balance
Assets:Checking = EC 250.00 ; we force set it again
Equity:Opening Balances
This is an error, because $250.00 cannot be auto-balanced to match EC
250.00. However:
2008/07/24 Opening Balance
Assets:Checking = $250.00 ; we force set it again
Assets:Checking EC 100.00 ; and add some EC's
Equity:Opening Balances
2008/07/24 Opening Balance
Assets:Checking = EC 250.00 ; we force set the EC's
Equity:Opening Balances
This is *not* an error, because the latter auto-balancing transaction
only affects the EC 100.00 part of the account's balance; the $250.00
part is left alone.
# Checking statement balances
When you reconcile a statement, there are typically one or more
transactions which result in a known balance. Here's how you specify
that in your Ledger data:
2008/07/24 Opening Balance
Assets:Checking = $100.00
Equity:Opening Balances
2008/07/30 We spend money, with a known balance afterward
Expenses:Food $20.00
Assets:Checking = $80.00
2008/07/30 Again we spend money, but this time with all the info
Expenses:Food $20.00
Assets:Checking $-20.00 = $60.00
2008/07/30 This entry yield an 'unbalanced' error
Expenses:Food $20.00
Assets:Checking $-20.00 = $30.00
The last entry in this set fails to balance with an unbalanced
remainder of $-10.00. Either the entry must be corrected, or you can
have Ledger deal with the remainder automatically:
2008/07/30 The fixed entry
Expenses:Food $20.00
Assets:Checking $-20.00 = $30.00
Equity:Adjustments
# Conclusion
This simple feature has all the utility of @check, plus auto-balancing
to match known target balances, plus the ability to guarantee that an
account which uses only one commodity does contain only that
commodity.
This feature slows down textual parsing slightly, does not affect
speed when loading from the binary cache.
- The rest of the changes in the version is all bug fixes (around 45 of
them).
* 2.6.0.90

View file

@ -765,7 +765,10 @@ parser_t::parse_logic_expr(std::istream& in, scope_t& scope, const flags_t tflag
token_t& tok = next_token(in, tflags);
switch (tok.kind) {
case token_t::EQUAL:
kind = op_t::O_EQ;
if (tflags & EXPR_PARSE_NO_ASSIGN)
tok.rewind(in);
else
kind = op_t::O_EQ;
break;
case token_t::NEQUAL:
kind = op_t::O_NEQ;
@ -1371,7 +1374,8 @@ ptr_op_t parse_value_term(std::istream& in, scope_t * scope,
bool definition = false;
if (c == '=') {
in.get(c);
if (peek_next_nonws(in) == '=') {
if ((flags & EXPR_PARSE_NO_ASSIGN) ||
peek_next_nonws(in) == '=') {
in.unget();
c = '\0';
} else {

View file

@ -46,7 +46,8 @@ class parser_t : public noncopyable
#define EXPR_PARSE_RELAXED 0x02
#define EXPR_PARSE_NO_MIGRATE 0x04
#define EXPR_PARSE_NO_REDUCE 0x08
#define EXPR_PARSE_NO_DATES 0x10
#define EXPR_PARSE_NO_ASSIGN 0x10
#define EXPR_PARSE_NO_DATES 0x20
public:
typedef uint_least8_t flags_t;

View file

@ -216,7 +216,7 @@ void report_t::transactions_report(xact_handler_ptr handler)
handler->flush();
if (DO_VERIFY())
clean_transactions();
session.clean_transactions();
}
void report_t::entry_report(xact_handler_ptr handler, entry_t& entry)
@ -226,7 +226,7 @@ void report_t::entry_report(xact_handler_ptr handler, entry_t& entry)
handler->flush();
if (DO_VERIFY())
clean_transactions(entry);
session.clean_transactions(entry);
}
void report_t::sum_all_accounts()
@ -265,8 +265,8 @@ void report_t::accounts_report(acct_handler_ptr handler,
}
if (DO_VERIFY()) {
clean_transactions();
clean_accounts();
session.clean_transactions();
session.clean_accounts();
}
}
@ -278,26 +278,6 @@ void report_t::entry_report(const entry_t& entry, const string& format)
{
}
void report_t::clean_transactions()
{
session_transactions_iterator walker(session);
pass_down_transactions
(xact_handler_ptr(new clear_transaction_xdata), walker);
}
void report_t::clean_transactions(entry_t& entry)
{
entry_transactions_iterator walker(entry);
pass_down_transactions(xact_handler_ptr(new clear_transaction_xdata), walker);
}
void report_t::clean_accounts()
{
accounts_iterator acct_walker(*session.master);
pass_down_accounts<accounts_iterator>
(acct_handler_ptr(new clear_account_xdata), acct_walker);
}
value_t report_t::abbrev(expr::call_scope_t& args)
{
if (args.size() < 2)

View file

@ -197,10 +197,6 @@ public:
void entry_report(const entry_t& entry, const string& format);
void clean_transactions();
void clean_transactions(entry_t& entry);
void clean_accounts();
//
// Utility functions for value expressions
//

View file

@ -31,6 +31,7 @@
#include "session.h"
#include "parsexp.h"
#include "walk.h"
namespace ledger {
@ -210,6 +211,7 @@ std::size_t session_t::read_data(journal_t& journal,
entry_count += read_journal(journal, data_file, acct);
if (journal.price_db)
journal.sources.push_back(*journal.price_db);
clean_accounts();
}
}
@ -241,6 +243,26 @@ account_t * session_t::find_account_re(const string& regexp)
return find_account_re_(master, mask_t(regexp));
}
void session_t::clean_transactions()
{
session_transactions_iterator walker(*this);
pass_down_transactions
(xact_handler_ptr(new clear_transaction_xdata), walker);
}
void session_t::clean_transactions(entry_t& entry)
{
entry_transactions_iterator walker(entry);
pass_down_transactions(xact_handler_ptr(new clear_transaction_xdata), walker);
}
void session_t::clean_accounts()
{
accounts_iterator acct_walker(*master);
pass_down_accounts<accounts_iterator>
(acct_handler_ptr(new clear_account_xdata), acct_walker);
}
#if 0
value_t session_t::resolve(const string& name, expr::scope_t& locals)
{

View file

@ -158,6 +158,11 @@ class session_t : public expr::symbol_scope_t
}
account_t * find_account_re(const string& regexp);
void clean_accounts();
void clean_transactions();
void clean_transactions(entry_t& entry);
//
// Scope members
//

View file

@ -180,13 +180,15 @@ transaction_t * parse_transaction(char * line, account_t * account,
goto finished;
if (p == ';')
goto parse_note;
if (p == '=' && entry)
goto parse_assign;
try {
unsigned long beg = (long)in.tellg();
xact->amount_expr =
parse_amount_expr(in, xact->amount, xact.get(),
EXPR_PARSE_NO_REDUCE);
EXPR_PARSE_NO_REDUCE | EXPR_PARSE_NO_ASSIGN);
saw_amount = true;
if (! xact->amount.is_null()) {
@ -235,7 +237,8 @@ transaction_t * parse_transaction(char * line, account_t * account,
unsigned long beg = (long)in.tellg();
if (parse_amount_expr(in, *xact->cost, xact.get(),
EXPR_PARSE_NO_MIGRATE))
EXPR_PARSE_NO_MIGRATE |
EXPR_PARSE_NO_ASSIGN))
throw new parse_error
("A transaction's cost must evaluate to a constant value");
assert(xact->cost->valid());
@ -283,6 +286,94 @@ transaction_t * parse_transaction(char * line, account_t * account,
}
}
parse_assign:
if (entry != NULL) {
// Add this amount to the related account now
account_xdata_t& xdata(account_xdata(*xact->account));
if (xact->amount) {
xdata.value += xact->amount;
DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
"XACT assign: account total = " << xdata.value);
}
// Parse the optional assigned (= AMOUNT)
if (in.good() && ! in.eof()) {
p = peek_next_nonws(in);
if (p == '=') {
in.get(p);
DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
"Found a balance assignment indicator");
if (in.good() && ! in.eof()) {
amount_t amt;
try {
#if 0
unsigned long beg = (long)in.tellg();
#endif
if (parse_amount_expr(in, amt, xact.get(),
EXPR_PARSE_NO_MIGRATE))
throw new parse_error
("An assigned balance must evaluate to a constant value");
DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
"XACT assign: parsed amt = " << amt);
#if 0
unsigned long end = (long)in.tellg();
#endif
amount_t diff;
if (xdata.value.is_amount()) {
diff = amt - xdata.value.as_amount();
}
else if (xdata.value.is_balance()) {
optional<amount_t> comm_bal =
xdata.value.as_balance().commodity_amount(amt.commodity());
diff = amt - (comm_bal ? *comm_bal : amount_t(0L));
}
else if (xdata.value.is_balance_pair()) {
optional<amount_t> comm_bal =
xdata.value.as_balance_pair().commodity_amount(amt.commodity());
diff = amt - (comm_bal ? *comm_bal : amount_t(0L));
}
else {
diff = amt;
}
DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
"XACT assign: diff = " << diff);
if (! diff.is_realzero()) {
if (xact->amount) {
transaction_t * temp =
new transaction_t(xact->account, diff,
TRANSACTION_GENERATED |
TRANSACTION_CALCULATED);
entry->add_transaction(temp);
DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
"Created balancing transaction");
} else {
xact->amount = diff;
DEBUG("ledger.textual.parse", "line " << linenum << ": " <<
"Overwrite null transaction");
}
xdata.value = amt;
}
}
catch (error * err) {
err_desc = "While parsing assigned balance:";
throw err;
}
}
}
}
}
// Parse the optional note
parse_note: