summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJohn Wiegley <johnw@newartisans.com>2009-11-11 03:41:59 -0500
committerJohn Wiegley <johnw@newartisans.com>2009-11-11 04:22:37 -0500
commite8ea2d4938fcbb0988fd1e2021d97a519c67ffd8 (patch)
tree3aadd6947972d1b802f89b90db683994071b134c
parentafe87280e091d4f094f068c5f21aecccd2d1831b (diff)
downloadfork-ledger-e8ea2d4938fcbb0988fd1e2021d97a519c67ffd8.tar.gz
fork-ledger-e8ea2d4938fcbb0988fd1e2021d97a519c67ffd8.tar.bz2
fork-ledger-e8ea2d4938fcbb0988fd1e2021d97a519c67ffd8.zip
Automated postings defer amount expression calculation
This allows for value expressions to be used which reference the incoming posting, for example: = Income:Clients: (Liabilities:Taxes:VAT1) (floor(amount) * 1) (Liabilities:Taxes:VAT2) 0.19 2009/07/27 * Invoice Assets:Bank:Checking $1,190.45 Income:Clients:ACME_Inc The automated posting for VAT1 will use the floored amount multiplied by a factor, while the posting for VAT2 multiples the whole amount as before.
-rw-r--r--src/archive.cc2
-rw-r--r--src/hooks.h4
-rw-r--r--src/journal.cc4
-rw-r--r--src/post.h2
-rw-r--r--src/py_journal.cc8
-rw-r--r--src/textual.cc75
-rw-r--r--src/xact.cc47
-rw-r--r--src/xact.h18
-rw-r--r--test/regress/25A099C9.test12
9 files changed, 99 insertions, 73 deletions
diff --git a/src/archive.cc b/src/archive.cc
index f76b7543..5ea6cd8e 100644
--- a/src/archive.cc
+++ b/src/archive.cc
@@ -43,7 +43,7 @@
#include "xact.h"
#define LEDGER_MAGIC 0x4c454447
-#define ARCHIVE_VERSION 0x03000004
+#define ARCHIVE_VERSION 0x03000005
//BOOST_IS_ABSTRACT(ledger::scope_t)
BOOST_CLASS_EXPORT(ledger::scope_t)
diff --git a/src/hooks.h b/src/hooks.h
index da6fcf84..20c7750c 100644
--- a/src/hooks.h
+++ b/src/hooks.h
@@ -70,9 +70,9 @@ public:
list.remove(func);
}
- bool run_hooks(Data& item, bool post) {
+ bool run_hooks(Data& item) {
foreach (T * func, list)
- if (! (*func)(item, post))
+ if (! (*func)(item))
return false;
return true;
}
diff --git a/src/journal.cc b/src/journal.cc
index 550b4f4c..b7ad9a23 100644
--- a/src/journal.cc
+++ b/src/journal.cc
@@ -126,9 +126,7 @@ bool journal_t::add_xact(xact_t * xact)
{
xact->journal = this;
- if (! xact_finalize_hooks.run_hooks(*xact, false) ||
- ! xact->finalize() ||
- ! xact_finalize_hooks.run_hooks(*xact, true)) {
+ if (! xact->finalize() || ! xact_finalize_hooks.run_hooks(*xact)) {
xact->journal = NULL;
return false;
}
diff --git a/src/post.h b/src/post.h
index addf0629..d9e50580 100644
--- a/src/post.h
+++ b/src/post.h
@@ -61,6 +61,7 @@ public:
account_t * account;
amount_t amount; // can be null until finalization
+ optional<expr_t> amount_expr;
optional<amount_t> cost;
optional<amount_t> assigned_amount;
@@ -212,6 +213,7 @@ private:
ar & xact;
ar & account;
ar & amount;
+ ar & amount_expr;
ar & cost;
ar & assigned_amount;
}
diff --git a/src/py_journal.cc b/src/py_journal.cc
index 17c42c21..88447b92 100644
--- a/src/py_journal.cc
+++ b/src/py_journal.cc
@@ -136,8 +136,8 @@ namespace {
py_xact_finalizer_t(object obj) : pyobj(obj) {}
py_xact_finalizer_t(const py_xact_finalizer_t& other)
: pyobj(other.pyobj) {}
- virtual bool operator()(xact_t& xact, bool post) {
- return call<bool>(pyobj.ptr(), xact, post);
+ virtual bool operator()(xact_t& xact) {
+ return call<bool>(pyobj.ptr(), xact);
}
};
@@ -161,9 +161,9 @@ namespace {
}
}
- void py_run_xact_finalizers(journal_t& journal, xact_t& xact, bool post)
+ void py_run_xact_finalizers(journal_t& journal, xact_t& xact)
{
- journal.xact_finalize_hooks.run_hooks(xact, post);
+ journal.xact_finalize_hooks.run_hooks(xact);
}
std::size_t py_read(journal_t& journal, const string& pathname)
diff --git a/src/textual.cc b/src/textual.cc
index f5d0635c..8f0dd89c 100644
--- a/src/textual.cc
+++ b/src/textual.cc
@@ -132,10 +132,12 @@ namespace {
std::streamsize len,
account_t * account,
xact_t * xact,
- bool honor_strict = true);
+ bool honor_strict = true,
+ bool defer_expr = false);
- bool parse_posts(account_t * account,
- xact_base_t& xact);
+ bool parse_posts(account_t * account,
+ xact_base_t& xact,
+ const bool defer_expr = false);
xact_t * parse_xact(char * line,
std::streamsize len,
@@ -145,11 +147,13 @@ namespace {
const string& name);
};
- void parse_amount_expr(scope_t& scope,
- std::istream& in,
- amount_t& amount,
- post_t * post,
- const parse_flags_t& flags = PARSE_DEFAULT)
+ void parse_amount_expr(scope_t& scope,
+ std::istream& in,
+ amount_t& amount,
+ optional<expr_t> * amount_expr,
+ post_t * post,
+ const parse_flags_t& flags = PARSE_DEFAULT,
+ const bool defer_expr = false)
{
expr_t expr(in, flags.plus_flags(PARSE_PARTIAL));
@@ -166,17 +170,22 @@ namespace {
if (expr) {
bind_scope_t bound_scope(scope, *post);
-
- value_t result(expr.calc(bound_scope));
- if (result.is_long()) {
- amount = result.to_amount();
+ if (defer_expr) {
+ assert(amount_expr);
+ *amount_expr = expr;
+ (*amount_expr)->compile(bound_scope);
} else {
- if (! result.is_amount())
- throw_(parse_error, _("Postings may only specify simple amounts"));
-
- amount = result.as_amount();
+ value_t result(expr.calc(bound_scope));
+ if (result.is_long()) {
+ amount = result.to_amount();
+ } else {
+ if (! result.is_amount())
+ throw_(amount_error,
+ _("Amount expressions must result in a simple amount"));
+ amount = result.as_amount();
+ }
+ DEBUG("textual.parse", "The posting amount is " << amount);
}
- DEBUG("textual.parse", "The posting amount is " << amount);
}
}
}
@@ -548,7 +557,7 @@ void instance_t::automated_xact_directive(char * line)
reveal_context = false;
- if (parse_posts(account_stack.front(), *ae.get())) {
+ if (parse_posts(account_stack.front(), *ae.get(), true)) {
reveal_context = true;
journal.auto_xacts.push_back(ae.get());
@@ -592,7 +601,7 @@ void instance_t::period_xact_directive(char * line)
pe->journal = &journal;
if (pe->finalize()) {
- extend_xact_base(&journal, *pe.get(), true);
+ extend_xact_base(&journal, *pe.get());
journal.period_xacts.push_back(pe.get());
@@ -817,7 +826,8 @@ post_t * instance_t::parse_post(char * line,
std::streamsize len,
account_t * account,
xact_t * xact,
- bool honor_strict)
+ bool honor_strict,
+ bool defer_expr)
{
TRACE_START(post_details, 1, "Time spent parsing postings:");
@@ -919,8 +929,9 @@ post_t * instance_t::parse_post(char * line,
if (*next != '(') // indicates a value expression
post->amount.parse(stream, PARSE_NO_REDUCE);
else
- parse_amount_expr(scope, stream, post->amount, post.get(),
- PARSE_NO_REDUCE | PARSE_SINGLE | PARSE_NO_ASSIGN);
+ parse_amount_expr(scope, stream, post->amount, &post->amount_expr,
+ post.get(), PARSE_NO_REDUCE | PARSE_SINGLE |
+ PARSE_NO_ASSIGN, defer_expr);
if (! post->amount.is_null() && honor_strict && strict &&
post->amount.has_commodity() &&
@@ -965,9 +976,9 @@ post_t * instance_t::parse_post(char * line,
if (*p != '(') // indicates a value expression
post->cost->parse(cstream, PARSE_NO_MIGRATE);
else
- parse_amount_expr(scope, cstream, *post->cost, post.get(),
- PARSE_NO_MIGRATE | PARSE_SINGLE |
- PARSE_NO_ASSIGN);
+ parse_amount_expr(scope, cstream, *post->cost, NULL, post.get(),
+ PARSE_NO_MIGRATE | PARSE_SINGLE | PARSE_NO_ASSIGN,
+ defer_expr);
if (post->cost->sign() < 0)
throw parse_error(_("A posting's cost may not be negative"));
@@ -1017,8 +1028,9 @@ post_t * instance_t::parse_post(char * line,
if (*p != '(') // indicates a value expression
post->assigned_amount->parse(stream, PARSE_NO_MIGRATE);
else
- parse_amount_expr(scope, stream, *post->assigned_amount, post.get(),
- PARSE_SINGLE | PARSE_NO_MIGRATE);
+ parse_amount_expr(scope, stream, *post->assigned_amount, NULL,
+ post.get(), PARSE_SINGLE | PARSE_NO_MIGRATE,
+ defer_expr);
if (post->assigned_amount->is_null()) {
if (post->amount.is_null())
@@ -1118,8 +1130,9 @@ post_t * instance_t::parse_post(char * line,
}
}
-bool instance_t::parse_posts(account_t * account,
- xact_base_t& xact)
+bool instance_t::parse_posts(account_t * account,
+ xact_base_t& xact,
+ const bool defer_expr)
{
TRACE_START(xact_posts, 1, "Time spent parsing postings:");
@@ -1130,7 +1143,9 @@ bool instance_t::parse_posts(account_t * account,
std::streamsize len = read_line(line);
assert(len > 0);
- if (post_t * post = parse_post(line, len, account, NULL, false)) {
+ if (post_t * post =
+ parse_post(line, len, account, NULL, /* honor_strict= */ false,
+ defer_expr)) {
xact.add_post(post);
added = true;
}
diff --git a/src/xact.cc b/src/xact.cc
index d94464a6..8ac5280a 100644
--- a/src/xact.cc
+++ b/src/xact.cc
@@ -480,7 +480,7 @@ bool xact_t::valid() const
return true;
}
-void auto_xact_t::extend_xact(xact_base_t& xact, bool post_handler)
+void auto_xact_t::extend_xact(xact_base_t& xact)
{
posts_list initial_posts(xact.posts.begin(), xact.posts.end());
@@ -490,20 +490,32 @@ void auto_xact_t::extend_xact(xact_base_t& xact, bool post_handler)
if (! initial_post->has_flags(ITEM_GENERATED) &&
predicate(*initial_post)) {
foreach (post_t * post, posts) {
- amount_t amt;
- assert(post->amount);
- if (! post->amount.commodity()) {
- if ((post_handler &&
- ! initial_post->has_flags(POST_CALCULATED)) ||
- initial_post->amount.is_null())
- continue;
- amt = initial_post->amount * post->amount;
+ amount_t post_amount;
+ if (post->amount.is_null()) {
+ if (! post->amount_expr)
+ throw_(amount_error,
+ _("Automated transaction's posting has no amount"));
+
+ bind_scope_t bound_scope(*scope_t::default_scope, *initial_post);
+ value_t result(post->amount_expr->calc(bound_scope));
+ if (result.is_long()) {
+ post_amount = result.to_amount();
+ } else {
+ if (! result.is_amount())
+ throw_(amount_error,
+ _("Amount expressions must result in a simple amount"));
+ post_amount = result.as_amount();
+ }
} else {
- if (post_handler)
- continue;
- amt = post->amount;
+ post_amount = post->amount;
}
+ amount_t amt;
+ if (! post_amount.commodity())
+ amt = initial_post->amount * post_amount;
+ else
+ amt = post_amount;
+
IF_DEBUG("xact.extend") {
DEBUG("xact.extend",
"Initial post on line " << initial_post->pos->beg_line << ": "
@@ -517,12 +529,12 @@ void auto_xact_t::extend_xact(xact_base_t& xact, bool post_handler)
DEBUG("xact.extend",
"Posting on line " << post->pos->beg_line << ": "
- << "amount " << post->amount << ", amt " << amt
- << " (precision " << post->amount.precision()
+ << "amount " << post_amount << ", amt " << amt
+ << " (precision " << post_amount.precision()
<< " != " << amt.precision() << ")");
#if defined(DEBUG_ON)
- if (post->amount.keep_precision())
+ if (post_amount.keep_precision())
DEBUG("xact.extend", " precision is kept");
if (amt.keep_precision())
DEBUG("xact.extend", " amt precision is kept");
@@ -556,11 +568,10 @@ void auto_xact_t::extend_xact(xact_base_t& xact, bool post_handler)
}
void extend_xact_base(journal_t * journal,
- xact_base_t& base,
- bool post_handler)
+ xact_base_t& base)
{
foreach (auto_xact_t * xact, journal->auto_xacts)
- xact->extend_xact(base, post_handler);
+ xact->extend_xact(base);
}
void to_xml(std::ostream& out, const xact_t& xact)
diff --git a/src/xact.h b/src/xact.h
index 9a52fafe..ff1f65fa 100644
--- a/src/xact.h
+++ b/src/xact.h
@@ -142,7 +142,7 @@ private:
struct xact_finalizer_t {
virtual ~xact_finalizer_t() {}
- virtual bool operator()(xact_t& xact, bool post) = 0;
+ virtual bool operator()(xact_t& xact) = 0;
};
class auto_xact_t : public xact_base_t
@@ -167,7 +167,7 @@ public:
TRACE_DTOR(auto_xact_t);
}
- virtual void extend_xact(xact_base_t& xact, bool post);
+ virtual void extend_xact(xact_base_t& xact);
#if defined(HAVE_BOOST_SERIALIZATION)
private:
@@ -201,7 +201,7 @@ struct auto_xact_finalizer_t : public xact_finalizer_t
TRACE_DTOR(auto_xact_finalizer_t);
}
- virtual bool operator()(xact_t& xact, bool post);
+ virtual bool operator()(xact_t& xact);
#if defined(HAVE_BOOST_SERIALIZATION)
private:
@@ -258,7 +258,7 @@ class func_finalizer_t : public xact_finalizer_t
func_finalizer_t();
public:
- typedef function<bool (xact_t& xact, bool post)> func_t;
+ typedef function<bool (xact_t& xact)> func_t;
func_t func;
@@ -273,15 +273,15 @@ public:
TRACE_DTOR(func_finalizer_t);
}
- virtual bool operator()(xact_t& xact, bool post) {
- return func(xact, post);
+ virtual bool operator()(xact_t& xact) {
+ return func(xact);
}
};
-void extend_xact_base(journal_t * journal, xact_base_t& xact, bool post);
+void extend_xact_base(journal_t * journal, xact_base_t& xact);
-inline bool auto_xact_finalizer_t::operator()(xact_t& xact, bool post) {
- extend_xact_base(journal, xact, post);
+inline bool auto_xact_finalizer_t::operator()(xact_t& xact) {
+ extend_xact_base(journal, xact);
return true;
}
diff --git a/test/regress/25A099C9.test b/test/regress/25A099C9.test
index 8aa8954f..604939d8 100644
--- a/test/regress/25A099C9.test
+++ b/test/regress/25A099C9.test
@@ -4,16 +4,16 @@
>>>2
While parsing file "$sourcepath/src/amount.h", line 67:
Error: No quantity specified for amount
-While parsing file "$sourcepath/src/amount.h", line 712:
+While parsing file "$sourcepath/src/amount.h", line 721:
Error: Invalid date/time: line amount_t amoun
-While parsing file "$sourcepath/src/amount.h", line 718:
+While parsing file "$sourcepath/src/amount.h", line 727:
Error: Invalid date/time: line string amount_
-While parsing file "$sourcepath/src/amount.h", line 724:
+While parsing file "$sourcepath/src/amount.h", line 733:
Error: Invalid date/time: line string amount_
-While parsing file "$sourcepath/src/amount.h", line 730:
+While parsing file "$sourcepath/src/amount.h", line 739:
Error: Invalid date/time: line string amount_
-While parsing file "$sourcepath/src/amount.h", line 736:
+While parsing file "$sourcepath/src/amount.h", line 745:
Error: Invalid date/time: line std::ostream&
-While parsing file "$sourcepath/src/amount.h", line 743:
+While parsing file "$sourcepath/src/amount.h", line 752:
Error: Invalid date/time: line std::istream&
=== 7