diff options
author | John Wiegley <johnw@newartisans.com> | 2009-11-11 03:41:59 -0500 |
---|---|---|
committer | John Wiegley <johnw@newartisans.com> | 2009-11-11 04:22:37 -0500 |
commit | e8ea2d4938fcbb0988fd1e2021d97a519c67ffd8 (patch) | |
tree | 3aadd6947972d1b802f89b90db683994071b134c | |
parent | afe87280e091d4f094f068c5f21aecccd2d1831b (diff) | |
download | fork-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.cc | 2 | ||||
-rw-r--r-- | src/hooks.h | 4 | ||||
-rw-r--r-- | src/journal.cc | 4 | ||||
-rw-r--r-- | src/post.h | 2 | ||||
-rw-r--r-- | src/py_journal.cc | 8 | ||||
-rw-r--r-- | src/textual.cc | 75 | ||||
-rw-r--r-- | src/xact.cc | 47 | ||||
-rw-r--r-- | src/xact.h | 18 | ||||
-rw-r--r-- | test/regress/25A099C9.test | 12 |
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; } @@ -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) @@ -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 |