Merge branch 'master' into v2.7a
This commit is contained in:
commit
0c76ac5b8f
8 changed files with 247 additions and 34 deletions
116
NEWS
116
NEWS
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
28
report.cc
28
report.cc
|
|
@ -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)
|
||||
|
|
|
|||
4
report.h
4
report.h
|
|
@ -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
|
||||
//
|
||||
|
|
|
|||
22
session.cc
22
session.cc
|
|
@ -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)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
//
|
||||
|
|
|
|||
95
textual.cc
95
textual.cc
|
|
@ -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:
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue