diff options
-rw-r--r-- | amount.cc | 78 | ||||
-rw-r--r-- | amount.h | 15 | ||||
-rw-r--r-- | config.cc | 8 | ||||
-rw-r--r-- | config.h | 3 | ||||
-rw-r--r-- | journal.cc | 7 | ||||
-rw-r--r-- | ledger.texi | 87 | ||||
-rw-r--r-- | textual.cc | 21 | ||||
-rw-r--r-- | valexpr.cc | 6 | ||||
-rw-r--r-- | walk.cc | 29 | ||||
-rw-r--r-- | walk.h | 16 |
10 files changed, 218 insertions, 52 deletions
@@ -1633,6 +1633,30 @@ amount_t commodity_base_t::value(const std::time_t moment) return price; } +bool annotated_commodity_t::operator==(const commodity_t& comm) const +{ + // If the base commodities don't match, the game's up. + if (base != comm.base) + return false; + + if (price && + (! comm.annotated || + price != static_cast<const annotated_commodity_t&>(comm).price)) + return false; + + if (date && + (! comm.annotated || + date != static_cast<const annotated_commodity_t&>(comm).date)) + return false; + + if (! tag.empty() && + (! comm.annotated || + tag != static_cast<const annotated_commodity_t&>(comm).tag)) + return false; + + return true; +} + void annotated_commodity_t::write_annotations(std::ostream& out, const amount_t& price, @@ -1751,15 +1775,55 @@ bool compare_amount_commodities::operator()(const amount_t * left, annotated_commodity_t& aleftcomm(static_cast<annotated_commodity_t&>(leftcomm)); annotated_commodity_t& arightcomm(static_cast<annotated_commodity_t&>(rightcomm)); - amount_t val = aleftcomm.price - arightcomm.price; - if (val) - return val < 0; + if (! aleftcomm.price && arightcomm.price) + return true; + if (aleftcomm.price && ! arightcomm.price) + return false; - int diff = aleftcomm.date - arightcomm.date; - if (diff) - return diff < 0; + if (aleftcomm.price && arightcomm.price) { + amount_t leftprice(aleftcomm.price); + leftprice.reduce(); + amount_t rightprice(arightcomm.price); + rightprice.reduce(); - return aleftcomm.tag < arightcomm.tag; + if (leftprice.commodity() == rightprice.commodity()) { + amount_t val = leftprice - rightprice; + if (val) + return val < 0; + } else { + // Since we have two different amounts, there's really no way + // to establish a true sorting order; we'll just do it based + // on the numerical values. + leftprice.clear_commodity(); + rightprice.clear_commodity(); + + amount_t val = leftprice - rightprice; + if (val) + return val < 0; + } + } + + if (! aleftcomm.date && arightcomm.date) + return true; + if (aleftcomm.date && ! arightcomm.date) + return false; + + if (aleftcomm.date && arightcomm.date) { + int diff = aleftcomm.date - arightcomm.date; + if (diff) + return diff < 0; + } + + if (aleftcomm.tag.empty() && ! arightcomm.tag.empty()) + return true; + if (! aleftcomm.tag.empty() && arightcomm.tag.empty()) + return false; + + if (! aleftcomm.tag.empty() && ! arightcomm.tag.empty()) + return aleftcomm.tag < arightcomm.tag; + + assert(0); + return true; } } @@ -267,6 +267,12 @@ class amount_t void parse(const std::string& str, unsigned char flags = 0); void reduce(); + amount_t reduced() const { + amount_t temp(*this); + temp.reduce(); + return temp; + } + void read_quantity(char *& data); void read_quantity(std::istream& in); void write_quantity(std::ostream& out) const; @@ -456,15 +462,18 @@ class commodity_t public: explicit commodity_t() : base(NULL), annotated(false) {} + virtual ~commodity_t() {} operator bool() const { return this != null_commodity; } - bool operator==(const commodity_t& comm) const { + virtual bool operator==(const commodity_t& comm) const { + if (comm.annotated) + return comm == *this; return base == comm.base; } bool operator!=(const commodity_t& comm) const { - return base != comm.base; + return ! (*this == comm); } std::string base_symbol() const { @@ -560,6 +569,8 @@ class annotated_commodity_t : public commodity_t annotated = true; } + virtual bool operator==(const commodity_t& comm) const; + void write_annotations(std::ostream& out) const { annotated_commodity_t::write_annotations(out, price, date, tag); } @@ -154,6 +154,7 @@ void config_t::reset() days_of_the_week = false; by_payee = false; comm_as_payee = false; + code_as_payee = false; show_revalued = false; show_revalued_only = false; download_quotes = false; @@ -578,6 +579,8 @@ config_t::chain_xact_handlers(const std::string& command, if (comm_as_payee) ptrs.push_back(formatter = new set_comm_as_payee(formatter)); + else if (code_as_payee) + ptrs.push_back(formatter = new set_code_as_payee(formatter)); return formatter; } @@ -1162,6 +1165,10 @@ OPT_BEGIN(comm_as_payee, "x") { config->comm_as_payee = true; } OPT_END(comm_as_payee); +OPT_BEGIN(code_as_payee, "") { + config->code_as_payee = true; +} OPT_END(code_as_payee); + OPT_BEGIN(budget, "") { config->budget_flags = BUDGET_BUDGETED; } OPT_END(budget); @@ -1355,6 +1362,7 @@ option_t config_options[CONFIG_OPTIONS_SIZE] = { { "by-payee", 'P', false, opt_by_payee, false }, { "cache", '\0', true, opt_cache, false }, { "cleared", 'C', false, opt_cleared, false }, + { "code-as-payee", '\0', false, opt_code_as_payee, false }, { "collapse", 'n', false, opt_collapse, false }, { "comm-as-payee", 'x', false, opt_comm_as_payee, false }, { "csv-register-format", '\0', true, opt_csv_register_format, false }, @@ -63,6 +63,7 @@ class config_t bool days_of_the_week; bool by_payee; bool comm_as_payee; + bool code_as_payee; bool show_revalued; bool show_revalued_only; bool download_quotes; @@ -109,7 +110,7 @@ class config_t std::list<item_handler<transaction_t> *>& ptrs); }; -#define CONFIG_OPTIONS_SIZE 93 +#define CONFIG_OPTIONS_SIZE 94 extern option_t config_options[CONFIG_OPTIONS_SIZE]; void option_help(std::ostream& out); @@ -101,6 +101,7 @@ bool entry_base_t::finalize() value_t balance; bool no_amounts = true; + bool saw_null = false; for (transactions_list::const_iterator x = transactions.begin(); x != transactions.end(); x++) @@ -122,6 +123,8 @@ bool entry_base_t::finalize() if (ann_comm.price) balance += ann_comm.price * (*x)->amount - *((*x)->cost); } + } else { + saw_null = true; } } @@ -146,7 +149,7 @@ bool entry_base_t::finalize() // determine its price by dividing the unit count into the value of // the balance. This is done for the last eligible commodity. - if (balance && balance.type == value_t::BALANCE && + if (! saw_null && balance && balance.type == value_t::BALANCE && ((balance_t *) balance.data)->amounts.size() == 2) { transactions_list::const_iterator x = transactions.begin(); commodity_t& this_comm = (*x)->amount.commodity(); @@ -163,7 +166,7 @@ bool entry_base_t::finalize() for (; x != transactions.end(); x++) { if ((*x)->cost || ((*x)->flags & TRANSACTION_VIRTUAL) || - (*x)->amount.commodity() != this_comm) + ! (*x)->amount || (*x)->amount.commodity() != this_comm) continue; assert((*x)->amount); diff --git a/ledger.texi b/ledger.texi index 4842e234..5f0f8a72 100644 --- a/ledger.texi +++ b/ledger.texi @@ -3296,8 +3296,8 @@ with three different accounts: @end itemize From a religious point of view, the community expects to divide its -resources into multiple ``funds'', from which it expects to make -purchases or reserve resources for later: +resources into multiple ``funds'', from which it makes purchases or +reserves resources for later: @itemize @item School fund @@ -3306,22 +3306,21 @@ purchases or reserve resources for later: @end itemize The problem with this kind of setup is that when you spend money, it -comes from two or more places: the account and the fund. And yet, the -correlation of amounts between funds and accounts is rarely +comes from two or more places at once: the account and the fund. And +yet, the correlation of amounts between funds and accounts is rarely one-to-one. What if the school fund has @samp{$500.00}, but @samp{$400.00} of that comes from Checking, and @samp{$100.00} from Savings? -Using a traditional finance package would require that money reside in -only one place. But there are really two views to the data: from the -account point of view, you want one set of reports; from the fund -point of view, another set entirely -- and yet both sets should -reflect the same expenses and incoming. It's just where the money -``resides'' that differs. +Traditional finance packages require that the money reside in only one +place. But there are really two ``views'' of the data: from the +account point of view and from the fund point of view -- yet both sets +should reflect the same overall expenses and cash flow. It's simply +where the money resides that differs. -This situation can be handled using virtual transactions to represent -the fact that money is moving to and from two kind of accounts at the -same time: +This situation can be handled one of two ways. The first is using +virtual transactions to represent the fact that money is moving to and +from two kind of accounts at the same time: @smallexample 2004/03/20 Contributions @@ -3335,9 +3334,9 @@ same time: @end smallexample The use of square brackets in the second entry ensures that the -virtual transactions balance to zero. - -Now money can be spent directly from a fund: +virtual transactions balance to zero. Now money can be spent directly +from a fund at the same time as money is drawn from a physical +account: @smallexample 2004/03/25 Payment for books (paid from Checking) @@ -3346,17 +3345,17 @@ Now money can be spent directly from a fund: (Funds:School) $-100.00 @end smallexample -When reports are generated, by default they will appear in terms of -the funds, income and expenses. In this case, you will likely want to -mask out your @samp{Assets} account, becausue the balance won't make -much sense: +When reports are generated, by default they'll appear in terms of the +funds. In this case, you will likely want to mask out your +@samp{Assets} account, because otherwise the balance won't make much +sense: @example ledger bal -^Assets @end example -If the @option{--real} option is used, the report is in terms of the -real accounts: +If the @option{--real} option is used, the report will be in terms of +the real accounts: @example ledger --real bal @@ -3373,6 +3372,50 @@ list them as you would normally, for example: (Funds:School) $-100.00 @end smallexample +The second way of tracking funds is to use entry codes. In this +respect the codes become like virtual accounts that embrace the entire +set of transactions. Basically, we are associating an entry with a +fund by setting its code. Here are two entries that desposit money +into, and spend money from, the @samp{Funds:School} fund: + +@smallexample +2004/03/25 (Funds:School) Donations + Assets:Checking $100.00 + Income:Donations + +2004/04/25 (Funds:School) Payment for books + Expenses:Books $50.00 + Assets:Checking +@end smallexample + +Note how the accounts now relate only to the real accounts, and any +balance or registers reports will reflect this. That the entries +relate to a particular fund is kept only in the code. + +How does this become a fund report? By using the +@option{--code-as-payee} option, you can generate a register report +where the payee for each transaction shows the code. Alone, this is +not terribly interesting; but when combined with the +@option{--by-payee} option, you will now see account subtotals for any +transactions related to a specific fund. So, to see the current +monetary balances of all funds, the command would be: + +@smallexample +ledger --code-as-payee -P reg ^Assets +@end smallexample + +Or to see a particular funds expenses, the @samp{School} fund in this +case: + +@smallexample +ledger --code-as-payee -P reg ^Expenses -- School +@end smallexample + +Both approaches yield different kinds of flexibility, depending on how +you prefer to think of your funds: as virtual accounts, or as tags +associated with particular entries. Your own tastes will decide which +is best for your situation. + @node Archiving previous years, Virtual transactions, Working with multiple funds and accounts, Keeping a ledger @section Archiving previous years @@ -231,7 +231,7 @@ transaction_t * parse_transaction(char * line, account_t * account, } if (*xact->cost < 0) - throw new parse_error("A transaction's cost may not be a negative value"); + throw new parse_error("A transaction's cost may not be negative"); amount_t per_unit_cost(*xact->cost); if (per_unit) @@ -239,20 +239,11 @@ transaction_t * parse_transaction(char * line, account_t * account, else per_unit_cost /= xact->amount; - if (xact->amount.commodity()) { - if (! xact->amount.commodity().annotated) { - xact->amount.annotate_commodity(per_unit_cost, - xact->entry->actual_date(), - xact->entry->code); - } else { - annotated_commodity_t& ann(static_cast<annotated_commodity_t&> - (xact->amount.commodity())); - xact->amount.annotate_commodity - (! ann.price ? per_unit_cost : amount_t(), - ! ann.date ? xact->entry->actual_date() : 0, - ann.tag.empty() ? xact->entry->code : ""); - } - } + if (xact->amount.commodity() && + ! xact->amount.commodity().annotated) + xact->amount.annotate_commodity(per_unit_cost, + xact->entry->actual_date(), + xact->entry->code); DEBUG_PRINT("ledger.textual.parse", "line " << linenum << ": " << "Total cost is " << *xact->cost); @@ -563,7 +563,7 @@ void value_expr_t::compute(value_t& result, const details_t& details, case F_COMMODITY_MASK: assert(mask); if (details.xact) - result = mask->match(details.xact->amount.commodity().symbol()); + result = mask->match(details.xact->amount.commodity().base_symbol()); else result = false; break; @@ -895,8 +895,10 @@ value_expr_t * parse_value_term(std::istream& in, scope_t * scope, if (! def) { if (buf[1] == '\0' && (buf[0] == 'c' || buf[0] == 'C' || buf[0] == 'p' || - buf[0] == 'w' || buf[0] == 'W' || buf[0] == 'e')) + buf[0] == 'w' || buf[0] == 'W' || buf[0] == 'e')) { + in.unget(); goto find_term; + } throw new value_expr_error(std::string("Unknown identifier '") + buf + "'"); } @@ -528,13 +528,40 @@ void set_comm_as_payee::operator()(transaction_t& xact) entry_t& entry = entry_temps.back(); entry._date = xact.date(); entry.code = xact.entry->code; - entry.payee = xact.amount.commodity().symbol(); + + if (xact.amount.commodity()) + entry.payee = xact.amount.commodity().symbol(); + else + entry.payee = "<none>"; xact_temps.push_back(xact); transaction_t& temp = xact_temps.back(); temp.entry = &entry; temp.state = xact.state; temp.flags |= TRANSACTION_BULK_ALLOC; + + entry.add_transaction(&temp); + + item_handler<transaction_t>::operator()(temp); +} + +void set_code_as_payee::operator()(transaction_t& xact) +{ + entry_temps.push_back(*xact.entry); + entry_t& entry = entry_temps.back(); + entry._date = xact.date(); + + if (! xact.entry->code.empty()) + entry.payee = xact.entry->code; + else + entry.payee = "<none>"; + + xact_temps.push_back(xact); + transaction_t& temp = xact_temps.back(); + temp.entry = &entry; + temp.state = xact.state; + temp.flags |= TRANSACTION_BULK_ALLOC; + entry.add_transaction(&temp); item_handler<transaction_t>::operator()(temp); @@ -579,6 +579,22 @@ class set_comm_as_payee : public item_handler<transaction_t> virtual void operator()(transaction_t& xact); }; +class set_code_as_payee : public item_handler<transaction_t> +{ + std::list<entry_t> entry_temps; + std::list<transaction_t> xact_temps; + + public: + set_code_as_payee(item_handler<transaction_t> * handler) + : item_handler<transaction_t>(handler) {} + + ~set_code_as_payee() { + clear_entries_transactions(entry_temps); + } + + virtual void operator()(transaction_t& xact); +}; + class dow_transactions : public subtotal_transactions { transactions_list days_of_the_week[7]; |