diff options
-rw-r--r-- | NEWS | 116 | ||||
-rw-r--r-- | parsexp.cc | 8 | ||||
-rw-r--r-- | parsexp.h | 3 | ||||
-rw-r--r-- | report.cc | 28 | ||||
-rw-r--r-- | report.h | 4 | ||||
-rw-r--r-- | session.cc | 22 | ||||
-rw-r--r-- | session.h | 5 | ||||
-rw-r--r-- | textual.cc | 95 |
8 files changed, 247 insertions, 34 deletions
@@ -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; @@ -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) @@ -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 // @@ -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 // @@ -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: |