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.
This commit is contained in:
John Wiegley 2009-11-11 03:41:59 -05:00
parent afe87280e0
commit e8ea2d4938
9 changed files with 99 additions and 73 deletions

View file

@ -43,7 +43,7 @@
#include "xact.h" #include "xact.h"
#define LEDGER_MAGIC 0x4c454447 #define LEDGER_MAGIC 0x4c454447
#define ARCHIVE_VERSION 0x03000004 #define ARCHIVE_VERSION 0x03000005
//BOOST_IS_ABSTRACT(ledger::scope_t) //BOOST_IS_ABSTRACT(ledger::scope_t)
BOOST_CLASS_EXPORT(ledger::scope_t) BOOST_CLASS_EXPORT(ledger::scope_t)

View file

@ -70,9 +70,9 @@ public:
list.remove(func); list.remove(func);
} }
bool run_hooks(Data& item, bool post) { bool run_hooks(Data& item) {
foreach (T * func, list) foreach (T * func, list)
if (! (*func)(item, post)) if (! (*func)(item))
return false; return false;
return true; return true;
} }

View file

@ -126,9 +126,7 @@ bool journal_t::add_xact(xact_t * xact)
{ {
xact->journal = this; xact->journal = this;
if (! xact_finalize_hooks.run_hooks(*xact, false) || if (! xact->finalize() || ! xact_finalize_hooks.run_hooks(*xact)) {
! xact->finalize() ||
! xact_finalize_hooks.run_hooks(*xact, true)) {
xact->journal = NULL; xact->journal = NULL;
return false; return false;
} }

View file

@ -61,6 +61,7 @@ public:
account_t * account; account_t * account;
amount_t amount; // can be null until finalization amount_t amount; // can be null until finalization
optional<expr_t> amount_expr;
optional<amount_t> cost; optional<amount_t> cost;
optional<amount_t> assigned_amount; optional<amount_t> assigned_amount;
@ -212,6 +213,7 @@ private:
ar & xact; ar & xact;
ar & account; ar & account;
ar & amount; ar & amount;
ar & amount_expr;
ar & cost; ar & cost;
ar & assigned_amount; ar & assigned_amount;
} }

View file

@ -136,8 +136,8 @@ namespace {
py_xact_finalizer_t(object obj) : pyobj(obj) {} py_xact_finalizer_t(object obj) : pyobj(obj) {}
py_xact_finalizer_t(const py_xact_finalizer_t& other) py_xact_finalizer_t(const py_xact_finalizer_t& other)
: pyobj(other.pyobj) {} : pyobj(other.pyobj) {}
virtual bool operator()(xact_t& xact, bool post) { virtual bool operator()(xact_t& xact) {
return call<bool>(pyobj.ptr(), xact, post); 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) std::size_t py_read(journal_t& journal, const string& pathname)

View file

@ -132,10 +132,12 @@ namespace {
std::streamsize len, std::streamsize len,
account_t * account, account_t * account,
xact_t * xact, xact_t * xact,
bool honor_strict = true); bool honor_strict = true,
bool defer_expr = false);
bool parse_posts(account_t * account, bool parse_posts(account_t * account,
xact_base_t& xact); xact_base_t& xact,
const bool defer_expr = false);
xact_t * parse_xact(char * line, xact_t * parse_xact(char * line,
std::streamsize len, std::streamsize len,
@ -145,11 +147,13 @@ namespace {
const string& name); const string& name);
}; };
void parse_amount_expr(scope_t& scope, void parse_amount_expr(scope_t& scope,
std::istream& in, std::istream& in,
amount_t& amount, amount_t& amount,
post_t * post, optional<expr_t> * amount_expr,
const parse_flags_t& flags = PARSE_DEFAULT) post_t * post,
const parse_flags_t& flags = PARSE_DEFAULT,
const bool defer_expr = false)
{ {
expr_t expr(in, flags.plus_flags(PARSE_PARTIAL)); expr_t expr(in, flags.plus_flags(PARSE_PARTIAL));
@ -166,17 +170,22 @@ namespace {
if (expr) { if (expr) {
bind_scope_t bound_scope(scope, *post); bind_scope_t bound_scope(scope, *post);
if (defer_expr) {
value_t result(expr.calc(bound_scope)); assert(amount_expr);
if (result.is_long()) { *amount_expr = expr;
amount = result.to_amount(); (*amount_expr)->compile(bound_scope);
} else { } else {
if (! result.is_amount()) value_t result(expr.calc(bound_scope));
throw_(parse_error, _("Postings may only specify simple amounts")); if (result.is_long()) {
amount = result.to_amount();
amount = result.as_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; reveal_context = false;
if (parse_posts(account_stack.front(), *ae.get())) { if (parse_posts(account_stack.front(), *ae.get(), true)) {
reveal_context = true; reveal_context = true;
journal.auto_xacts.push_back(ae.get()); journal.auto_xacts.push_back(ae.get());
@ -592,7 +601,7 @@ void instance_t::period_xact_directive(char * line)
pe->journal = &journal; pe->journal = &journal;
if (pe->finalize()) { if (pe->finalize()) {
extend_xact_base(&journal, *pe.get(), true); extend_xact_base(&journal, *pe.get());
journal.period_xacts.push_back(pe.get()); journal.period_xacts.push_back(pe.get());
@ -817,7 +826,8 @@ post_t * instance_t::parse_post(char * line,
std::streamsize len, std::streamsize len,
account_t * account, account_t * account,
xact_t * xact, xact_t * xact,
bool honor_strict) bool honor_strict,
bool defer_expr)
{ {
TRACE_START(post_details, 1, "Time spent parsing postings:"); 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 if (*next != '(') // indicates a value expression
post->amount.parse(stream, PARSE_NO_REDUCE); post->amount.parse(stream, PARSE_NO_REDUCE);
else else
parse_amount_expr(scope, stream, post->amount, post.get(), parse_amount_expr(scope, stream, post->amount, &post->amount_expr,
PARSE_NO_REDUCE | PARSE_SINGLE | PARSE_NO_ASSIGN); post.get(), PARSE_NO_REDUCE | PARSE_SINGLE |
PARSE_NO_ASSIGN, defer_expr);
if (! post->amount.is_null() && honor_strict && strict && if (! post->amount.is_null() && honor_strict && strict &&
post->amount.has_commodity() && post->amount.has_commodity() &&
@ -965,9 +976,9 @@ post_t * instance_t::parse_post(char * line,
if (*p != '(') // indicates a value expression if (*p != '(') // indicates a value expression
post->cost->parse(cstream, PARSE_NO_MIGRATE); post->cost->parse(cstream, PARSE_NO_MIGRATE);
else else
parse_amount_expr(scope, cstream, *post->cost, post.get(), parse_amount_expr(scope, cstream, *post->cost, NULL, post.get(),
PARSE_NO_MIGRATE | PARSE_SINGLE | PARSE_NO_MIGRATE | PARSE_SINGLE | PARSE_NO_ASSIGN,
PARSE_NO_ASSIGN); defer_expr);
if (post->cost->sign() < 0) if (post->cost->sign() < 0)
throw parse_error(_("A posting's cost may not be negative")); 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 if (*p != '(') // indicates a value expression
post->assigned_amount->parse(stream, PARSE_NO_MIGRATE); post->assigned_amount->parse(stream, PARSE_NO_MIGRATE);
else else
parse_amount_expr(scope, stream, *post->assigned_amount, post.get(), parse_amount_expr(scope, stream, *post->assigned_amount, NULL,
PARSE_SINGLE | PARSE_NO_MIGRATE); post.get(), PARSE_SINGLE | PARSE_NO_MIGRATE,
defer_expr);
if (post->assigned_amount->is_null()) { if (post->assigned_amount->is_null()) {
if (post->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, bool instance_t::parse_posts(account_t * account,
xact_base_t& xact) xact_base_t& xact,
const bool defer_expr)
{ {
TRACE_START(xact_posts, 1, "Time spent parsing postings:"); 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); std::streamsize len = read_line(line);
assert(len > 0); 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); xact.add_post(post);
added = true; added = true;
} }

View file

@ -480,7 +480,7 @@ bool xact_t::valid() const
return true; 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()); 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) && if (! initial_post->has_flags(ITEM_GENERATED) &&
predicate(*initial_post)) { predicate(*initial_post)) {
foreach (post_t * post, posts) { foreach (post_t * post, posts) {
amount_t amt; amount_t post_amount;
assert(post->amount); if (post->amount.is_null()) {
if (! post->amount.commodity()) { if (! post->amount_expr)
if ((post_handler && throw_(amount_error,
! initial_post->has_flags(POST_CALCULATED)) || _("Automated transaction's posting has no amount"));
initial_post->amount.is_null())
continue; bind_scope_t bound_scope(*scope_t::default_scope, *initial_post);
amt = initial_post->amount * post->amount; 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 { } else {
if (post_handler) post_amount = post->amount;
continue;
amt = post->amount;
} }
amount_t amt;
if (! post_amount.commodity())
amt = initial_post->amount * post_amount;
else
amt = post_amount;
IF_DEBUG("xact.extend") { IF_DEBUG("xact.extend") {
DEBUG("xact.extend", DEBUG("xact.extend",
"Initial post on line " << initial_post->pos->beg_line << ": " "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", DEBUG("xact.extend",
"Posting on line " << post->pos->beg_line << ": " "Posting on line " << post->pos->beg_line << ": "
<< "amount " << post->amount << ", amt " << amt << "amount " << post_amount << ", amt " << amt
<< " (precision " << post->amount.precision() << " (precision " << post_amount.precision()
<< " != " << amt.precision() << ")"); << " != " << amt.precision() << ")");
#if defined(DEBUG_ON) #if defined(DEBUG_ON)
if (post->amount.keep_precision()) if (post_amount.keep_precision())
DEBUG("xact.extend", " precision is kept"); DEBUG("xact.extend", " precision is kept");
if (amt.keep_precision()) if (amt.keep_precision())
DEBUG("xact.extend", " amt precision is kept"); 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, void extend_xact_base(journal_t * journal,
xact_base_t& base, xact_base_t& base)
bool post_handler)
{ {
foreach (auto_xact_t * xact, journal->auto_xacts) 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) void to_xml(std::ostream& out, const xact_t& xact)

View file

@ -142,7 +142,7 @@ private:
struct xact_finalizer_t { struct xact_finalizer_t {
virtual ~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 class auto_xact_t : public xact_base_t
@ -167,7 +167,7 @@ public:
TRACE_DTOR(auto_xact_t); 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) #if defined(HAVE_BOOST_SERIALIZATION)
private: private:
@ -201,7 +201,7 @@ struct auto_xact_finalizer_t : public xact_finalizer_t
TRACE_DTOR(auto_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) #if defined(HAVE_BOOST_SERIALIZATION)
private: private:
@ -258,7 +258,7 @@ class func_finalizer_t : public xact_finalizer_t
func_finalizer_t(); func_finalizer_t();
public: public:
typedef function<bool (xact_t& xact, bool post)> func_t; typedef function<bool (xact_t& xact)> func_t;
func_t func; func_t func;
@ -273,15 +273,15 @@ public:
TRACE_DTOR(func_finalizer_t); TRACE_DTOR(func_finalizer_t);
} }
virtual bool operator()(xact_t& xact, bool post) { virtual bool operator()(xact_t& xact) {
return func(xact, post); 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) { inline bool auto_xact_finalizer_t::operator()(xact_t& xact) {
extend_xact_base(journal, xact, post); extend_xact_base(journal, xact);
return true; return true;
} }

View file

@ -4,16 +4,16 @@
>>>2 >>>2
While parsing file "$sourcepath/src/amount.h", line 67: While parsing file "$sourcepath/src/amount.h", line 67:
Error: No quantity specified for amount 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 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_ 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_ 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_ 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& 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& Error: Invalid date/time: line std::istream&
=== 7 === 7