summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--NEWS116
-rw-r--r--parsexp.cc8
-rw-r--r--parsexp.h3
-rw-r--r--report.cc28
-rw-r--r--report.h4
-rw-r--r--session.cc22
-rw-r--r--session.h5
-rw-r--r--textual.cc95
8 files changed, 247 insertions, 34 deletions
diff --git a/NEWS b/NEWS
index ac56c581..cd95ed87 100644
--- a/NEWS
+++ b/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
diff --git a/parsexp.cc b/parsexp.cc
index 47d0b5a7..2494ddb4 100644
--- a/parsexp.cc
+++ b/parsexp.cc
@@ -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 {
diff --git a/parsexp.h b/parsexp.h
index 81bf2adc..5b4f58c9 100644
--- a/parsexp.h
+++ b/parsexp.h
@@ -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;
diff --git a/report.cc b/report.cc
index 57aa6329..e2d80d94 100644
--- a/report.cc
+++ b/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)
diff --git a/report.h b/report.h
index 6ffe477e..29d25f96 100644
--- a/report.h
+++ b/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
//
diff --git a/session.cc b/session.cc
index 9e8806a8..55d73b0a 100644
--- a/session.cc
+++ b/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)
{
diff --git a/session.h b/session.h
index ada80669..1534cd0b 100644
--- a/session.h
+++ b/session.h
@@ -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
//
diff --git a/textual.cc b/textual.cc
index 1a97ebf1..1027814b 100644
--- a/textual.cc
+++ b/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: