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:
parent
afe87280e0
commit
e8ea2d4938
9 changed files with 99 additions and 73 deletions
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -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)
|
||||||
|
|
|
||||||
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
47
src/xact.cc
47
src/xact.cc
|
|
@ -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)
|
||||||
|
|
|
||||||
18
src/xact.h
18
src/xact.h
|
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -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
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue