summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2006-03-24 01:41:22 +0000
committerJohn Wiegley <johnw@newartisans.com>2008-04-13 02:41:31 -0400
commit44561c1c1d233d9432de319a71b44a3e05275d49 (patch)
tree3b9fa53fa4bd0e50e0890f6aafea69533e89832c
parent964e74e333cb20d3519be64f79e19224f2bcc84e (diff)
downloadfork-ledger-44561c1c1d233d9432de319a71b44a3e05275d49.tar.gz
fork-ledger-44561c1c1d233d9432de319a71b44a3e05275d49.tar.bz2
fork-ledger-44561c1c1d233d9432de319a71b44a3e05275d49.zip
Further refinement of commodity lot information.
-rw-r--r--amount.cc78
-rw-r--r--amount.h15
-rw-r--r--config.cc8
-rw-r--r--config.h3
-rw-r--r--journal.cc7
-rw-r--r--ledger.texi87
-rw-r--r--textual.cc21
-rw-r--r--valexpr.cc6
-rw-r--r--walk.cc29
-rw-r--r--walk.h16
10 files changed, 218 insertions, 52 deletions
diff --git a/amount.cc b/amount.cc
index 251d3e7c..c89dd1ec 100644
--- a/amount.cc
+++ b/amount.cc
@@ -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;
}
}
diff --git a/amount.h b/amount.h
index dfef8d06..70f86328 100644
--- a/amount.h
+++ b/amount.h
@@ -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);
}
diff --git a/config.cc b/config.cc
index 278da258..50c72aa4 100644
--- a/config.cc
+++ b/config.cc
@@ -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 },
diff --git a/config.h b/config.h
index 83248296..45b28773 100644
--- a/config.h
+++ b/config.h
@@ -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);
diff --git a/journal.cc b/journal.cc
index a8d9c849..abe7e67c 100644
--- a/journal.cc
+++ b/journal.cc
@@ -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
diff --git a/textual.cc b/textual.cc
index da8fc04b..bd94ebaa 100644
--- a/textual.cc
+++ b/textual.cc
@@ -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);
diff --git a/valexpr.cc b/valexpr.cc
index 51d29a48..2f154e96 100644
--- a/valexpr.cc
+++ b/valexpr.cc
@@ -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 + "'");
}
diff --git a/walk.cc b/walk.cc
index 3a8ce640..32c165c2 100644
--- a/walk.cc
+++ b/walk.cc
@@ -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);
diff --git a/walk.h b/walk.h
index 505d27a7..25a36b4e 100644
--- a/walk.h
+++ b/walk.h
@@ -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];