Redesigned the draft_t class
This commit is contained in:
parent
fb8be53edb
commit
7411c74d6d
7 changed files with 598 additions and 582 deletions
559
src/derive.cc
559
src/derive.cc
|
|
@ -1,559 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2003-2009, John Wiegley. All rights reserved.
|
|
||||||
*
|
|
||||||
* Redistribution and use in source and binary forms, with or without
|
|
||||||
* modification, are permitted provided that the following conditions are
|
|
||||||
* met:
|
|
||||||
*
|
|
||||||
* - Redistributions of source code must retain the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer.
|
|
||||||
*
|
|
||||||
* - Redistributions in binary form must reproduce the above copyright
|
|
||||||
* notice, this list of conditions and the following disclaimer in the
|
|
||||||
* documentation and/or other materials provided with the distribution.
|
|
||||||
*
|
|
||||||
* - Neither the name of New Artisans LLC nor the names of its
|
|
||||||
* contributors may be used to endorse or promote products derived from
|
|
||||||
* this software without specific prior written permission.
|
|
||||||
*
|
|
||||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
||||||
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
|
||||||
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
|
||||||
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
|
||||||
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
|
||||||
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
|
||||||
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
|
||||||
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
|
||||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
||||||
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <system.hh>
|
|
||||||
|
|
||||||
#include "derive.h"
|
|
||||||
#include "xact.h"
|
|
||||||
#include "post.h"
|
|
||||||
#include "account.h"
|
|
||||||
#include "journal.h"
|
|
||||||
#include "session.h"
|
|
||||||
#include "report.h"
|
|
||||||
#include "output.h"
|
|
||||||
|
|
||||||
namespace ledger {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
struct xact_template_t
|
|
||||||
{
|
|
||||||
optional<date_t> date;
|
|
||||||
optional<string> code;
|
|
||||||
optional<string> note;
|
|
||||||
mask_t payee_mask;
|
|
||||||
|
|
||||||
struct post_template_t {
|
|
||||||
bool from;
|
|
||||||
optional<mask_t> account_mask;
|
|
||||||
optional<amount_t> amount;
|
|
||||||
optional<string> cost_operator;
|
|
||||||
optional<amount_t> cost;
|
|
||||||
|
|
||||||
post_template_t() : from(false) {}
|
|
||||||
};
|
|
||||||
|
|
||||||
std::list<post_template_t> posts;
|
|
||||||
|
|
||||||
xact_template_t() {}
|
|
||||||
|
|
||||||
void dump(std::ostream& out) const
|
|
||||||
{
|
|
||||||
if (date)
|
|
||||||
out << _("Date: ") << *date << std::endl;
|
|
||||||
else
|
|
||||||
out << _("Date: <today>") << std::endl;
|
|
||||||
|
|
||||||
if (code)
|
|
||||||
out << _("Code: ") << *code << std::endl;
|
|
||||||
if (note)
|
|
||||||
out << _("Note: ") << *note << std::endl;
|
|
||||||
|
|
||||||
if (payee_mask.empty())
|
|
||||||
out << _("Payee mask: INVALID (template expression will cause an error)")
|
|
||||||
<< std::endl;
|
|
||||||
else
|
|
||||||
out << _("Payee mask: ") << payee_mask << std::endl;
|
|
||||||
|
|
||||||
if (posts.empty()) {
|
|
||||||
out << std::endl
|
|
||||||
<< _("<Posting copied from last related transaction>")
|
|
||||||
<< std::endl;
|
|
||||||
} else {
|
|
||||||
bool has_only_from = true;
|
|
||||||
bool has_only_to = true;
|
|
||||||
|
|
||||||
foreach (const post_template_t& post, posts) {
|
|
||||||
if (post.from)
|
|
||||||
has_only_to = false;
|
|
||||||
else
|
|
||||||
has_only_from = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (const post_template_t& post, posts) {
|
|
||||||
straccstream accum;
|
|
||||||
out << std::endl
|
|
||||||
<< ACCUM(accum << _("[Posting \"%1\"]")
|
|
||||||
<< (post.from ? _("from") : _("to")))
|
|
||||||
<< std::endl;
|
|
||||||
|
|
||||||
if (post.account_mask)
|
|
||||||
out << _(" Account mask: ") << *post.account_mask << std::endl;
|
|
||||||
else if (post.from)
|
|
||||||
out << _(" Account mask: <use last of last related accounts>") << std::endl;
|
|
||||||
else
|
|
||||||
out << _(" Account mask: <use first of last related accounts>") << std::endl;
|
|
||||||
|
|
||||||
if (post.amount)
|
|
||||||
out << _(" Amount: ") << *post.amount << std::endl;
|
|
||||||
|
|
||||||
if (post.cost)
|
|
||||||
out << _(" Cost: ") << *post.cost_operator
|
|
||||||
<< " " << *post.cost << std::endl;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
xact_template_t
|
|
||||||
args_to_xact_template(value_t::sequence_t::const_iterator begin,
|
|
||||||
value_t::sequence_t::const_iterator end)
|
|
||||||
{
|
|
||||||
regex date_mask(_("([0-9]+(?:[-/.][0-9]+)?(?:[-/.][0-9]+))?"));
|
|
||||||
smatch what;
|
|
||||||
|
|
||||||
xact_template_t tmpl;
|
|
||||||
bool check_for_date = true;
|
|
||||||
|
|
||||||
optional<date_time::weekdays> weekday;
|
|
||||||
xact_template_t::post_template_t * post = NULL;
|
|
||||||
|
|
||||||
for (; begin != end; begin++) {
|
|
||||||
if (check_for_date &&
|
|
||||||
regex_match((*begin).to_string(), what, date_mask)) {
|
|
||||||
tmpl.date = parse_date(what[0]);
|
|
||||||
check_for_date = false;
|
|
||||||
}
|
|
||||||
else if (check_for_date &&
|
|
||||||
bool(weekday = string_to_day_of_week(what[0]))) {
|
|
||||||
short dow = static_cast<short>(*weekday);
|
|
||||||
date_t date = CURRENT_DATE() - date_duration(1);
|
|
||||||
while (date.day_of_week() != dow)
|
|
||||||
date -= date_duration(1);
|
|
||||||
tmpl.date = date;
|
|
||||||
check_for_date = false;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
string arg = (*begin).to_string();
|
|
||||||
|
|
||||||
if (arg == "at") {
|
|
||||||
if (begin == end)
|
|
||||||
throw std::runtime_error(_("Invalid xact command arguments"));
|
|
||||||
tmpl.payee_mask = (*++begin).to_string();
|
|
||||||
}
|
|
||||||
else if (arg == "to" || arg == "from") {
|
|
||||||
if (! post || post->account_mask) {
|
|
||||||
tmpl.posts.push_back(xact_template_t::post_template_t());
|
|
||||||
post = &tmpl.posts.back();
|
|
||||||
}
|
|
||||||
if (begin == end)
|
|
||||||
throw std::runtime_error(_("Invalid xact command arguments"));
|
|
||||||
post->account_mask = mask_t((*++begin).to_string());
|
|
||||||
post->from = arg == "from";
|
|
||||||
}
|
|
||||||
else if (arg == "on") {
|
|
||||||
if (begin == end)
|
|
||||||
throw std::runtime_error(_("Invalid xact command arguments"));
|
|
||||||
tmpl.date = parse_date((*++begin).to_string());
|
|
||||||
check_for_date = false;
|
|
||||||
}
|
|
||||||
else if (arg == "code") {
|
|
||||||
if (begin == end)
|
|
||||||
throw std::runtime_error(_("Invalid xact command arguments"));
|
|
||||||
tmpl.code = (*++begin).to_string();
|
|
||||||
}
|
|
||||||
else if (arg == "note") {
|
|
||||||
if (begin == end)
|
|
||||||
throw std::runtime_error(_("Invalid xact command arguments"));
|
|
||||||
tmpl.note = (*++begin).to_string();
|
|
||||||
}
|
|
||||||
else if (arg == "rest") {
|
|
||||||
; // just ignore this argument
|
|
||||||
}
|
|
||||||
else if (arg == "@" || arg == "@@") {
|
|
||||||
amount_t cost;
|
|
||||||
post->cost_operator = arg;
|
|
||||||
if (begin == end)
|
|
||||||
throw std::runtime_error(_("Invalid xact command arguments"));
|
|
||||||
arg = (*++begin).to_string();
|
|
||||||
if (! cost.parse(arg, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE))
|
|
||||||
throw std::runtime_error(_("Invalid xact command arguments"));
|
|
||||||
post->cost = cost;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
// Without a preposition, it is either:
|
|
||||||
//
|
|
||||||
// A payee, if we have not seen one
|
|
||||||
// An account or an amount, if we have
|
|
||||||
// An account if an amount has just been seen
|
|
||||||
// An amount if an account has just been seen
|
|
||||||
|
|
||||||
if (tmpl.payee_mask.empty()) {
|
|
||||||
tmpl.payee_mask = arg;
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
amount_t amt;
|
|
||||||
optional<mask_t> account;
|
|
||||||
|
|
||||||
if (! amt.parse(arg, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE))
|
|
||||||
account = mask_t(arg);
|
|
||||||
|
|
||||||
if (! post ||
|
|
||||||
(account && post->account_mask) ||
|
|
||||||
(! account && post->amount)) {
|
|
||||||
tmpl.posts.push_back(xact_template_t::post_template_t());
|
|
||||||
post = &tmpl.posts.back();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (account) {
|
|
||||||
post->from = false;
|
|
||||||
post->account_mask = account;
|
|
||||||
} else {
|
|
||||||
post->amount = amt;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! tmpl.posts.empty()) {
|
|
||||||
bool has_only_from = true;
|
|
||||||
bool has_only_to = true;
|
|
||||||
|
|
||||||
// A single account at the end of the line is the "from" account
|
|
||||||
if (tmpl.posts.size() > 1 &&
|
|
||||||
tmpl.posts.back().account_mask && ! tmpl.posts.back().amount)
|
|
||||||
tmpl.posts.back().from = true;
|
|
||||||
|
|
||||||
foreach (xact_template_t::post_template_t& post, tmpl.posts) {
|
|
||||||
if (post.from)
|
|
||||||
has_only_to = false;
|
|
||||||
else
|
|
||||||
has_only_from = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (has_only_from) {
|
|
||||||
tmpl.posts.push_front(xact_template_t::post_template_t());
|
|
||||||
}
|
|
||||||
else if (has_only_to) {
|
|
||||||
tmpl.posts.push_back(xact_template_t::post_template_t());
|
|
||||||
tmpl.posts.back().from = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return tmpl;
|
|
||||||
}
|
|
||||||
|
|
||||||
xact_t * derive_xact_from_template(xact_template_t& tmpl,
|
|
||||||
report_t& report)
|
|
||||||
{
|
|
||||||
if (tmpl.payee_mask.empty())
|
|
||||||
throw std::runtime_error(_("xact' command requires at least a payee"));
|
|
||||||
|
|
||||||
xact_t * matching = NULL;
|
|
||||||
journal_t& journal(*report.session.journal.get());
|
|
||||||
std::auto_ptr<xact_t> added(new xact_t);
|
|
||||||
|
|
||||||
for (xacts_list::reverse_iterator j = journal.xacts.rbegin();
|
|
||||||
j != journal.xacts.rend();
|
|
||||||
j++) {
|
|
||||||
if (tmpl.payee_mask.match((*j)->payee)) {
|
|
||||||
matching = *j;
|
|
||||||
DEBUG("derive.xact",
|
|
||||||
"Found payee match: transaction on line " << (*j)->pos->beg_line);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! tmpl.date) {
|
|
||||||
added->_date = CURRENT_DATE();
|
|
||||||
DEBUG("derive.xact", "Setting date to current date");
|
|
||||||
} else {
|
|
||||||
added->_date = tmpl.date;
|
|
||||||
DEBUG("derive.xact", "Setting date to template date: " << *tmpl.date);
|
|
||||||
}
|
|
||||||
|
|
||||||
added->set_state(item_t::UNCLEARED);
|
|
||||||
|
|
||||||
if (matching) {
|
|
||||||
added->payee = matching->payee;
|
|
||||||
added->code = matching->code;
|
|
||||||
added->note = matching->note;
|
|
||||||
|
|
||||||
#if defined(DEBUG_ON)
|
|
||||||
DEBUG("derive.xact", "Setting payee from match: " << added->payee);
|
|
||||||
if (added->code)
|
|
||||||
DEBUG("derive.xact", "Setting code from match: " << *added->code);
|
|
||||||
if (added->note)
|
|
||||||
DEBUG("derive.xact", "Setting note from match: " << *added->note);
|
|
||||||
#endif
|
|
||||||
} else {
|
|
||||||
added->payee = tmpl.payee_mask.str();
|
|
||||||
DEBUG("derive.xact", "Setting payee from template: " << added->payee);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tmpl.code) {
|
|
||||||
added->code = tmpl.code;
|
|
||||||
DEBUG("derive.xact", "Now setting code from template: " << *added->code);
|
|
||||||
}
|
|
||||||
if (tmpl.note) {
|
|
||||||
added->note = tmpl.note;
|
|
||||||
DEBUG("derive.xact", "Now setting note from template: " << *added->note);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (tmpl.posts.empty()) {
|
|
||||||
if (matching) {
|
|
||||||
DEBUG("derive.xact", "Template had no postings, copying from match");
|
|
||||||
|
|
||||||
foreach (post_t * post, matching->posts) {
|
|
||||||
added->add_post(new post_t(*post));
|
|
||||||
added->posts.back()->set_state(item_t::UNCLEARED);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
throw_(std::runtime_error,
|
|
||||||
_("No accounts, and no past transaction matching '%1'")
|
|
||||||
<< tmpl.payee_mask);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
DEBUG("derive.xact", "Template had postings");
|
|
||||||
|
|
||||||
bool any_post_has_amount = false;
|
|
||||||
foreach (xact_template_t::post_template_t& post, tmpl.posts) {
|
|
||||||
if (post.amount) {
|
|
||||||
DEBUG("derive.xact", " and at least one has an amount specified");
|
|
||||||
any_post_has_amount = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (xact_template_t::post_template_t& post, tmpl.posts) {
|
|
||||||
std::auto_ptr<post_t> new_post;
|
|
||||||
|
|
||||||
commodity_t * found_commodity = NULL;
|
|
||||||
|
|
||||||
if (matching) {
|
|
||||||
if (post.account_mask) {
|
|
||||||
DEBUG("derive.xact",
|
|
||||||
"Looking for matching posting based on account mask");
|
|
||||||
|
|
||||||
foreach (post_t * x, matching->posts) {
|
|
||||||
if (post.account_mask->match(x->account->fullname())) {
|
|
||||||
new_post.reset(new post_t(*x));
|
|
||||||
DEBUG("derive.xact",
|
|
||||||
"Founding posting from line " << x->pos->beg_line);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (post.from) {
|
|
||||||
for (posts_list::reverse_iterator j = matching->posts.rbegin();
|
|
||||||
j != matching->posts.rend();
|
|
||||||
j++) {
|
|
||||||
if ((*j)->must_balance()) {
|
|
||||||
new_post.reset(new post_t(**j));
|
|
||||||
DEBUG("derive.xact",
|
|
||||||
"Copied last real posting from matching");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
for (posts_list::iterator j = matching->posts.begin();
|
|
||||||
j != matching->posts.end();
|
|
||||||
j++) {
|
|
||||||
if ((*j)->must_balance()) {
|
|
||||||
new_post.reset(new post_t(**j));
|
|
||||||
DEBUG("derive.xact",
|
|
||||||
"Copied first real posting from matching");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! new_post.get()) {
|
|
||||||
new_post.reset(new post_t);
|
|
||||||
DEBUG("derive.xact", "New posting was NULL, creating a blank one");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! new_post->account) {
|
|
||||||
DEBUG("derive.xact", "New posting still needs an account");
|
|
||||||
|
|
||||||
if (post.account_mask) {
|
|
||||||
DEBUG("derive.xact", "The template has an account mask");
|
|
||||||
|
|
||||||
account_t * acct = NULL;
|
|
||||||
if (! acct) {
|
|
||||||
acct = journal.find_account_re(post.account_mask->str());
|
|
||||||
#if defined(DEBUG_ON)
|
|
||||||
if (acct)
|
|
||||||
DEBUG("derive.xact", "Found account as a regular expression");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
if (! acct) {
|
|
||||||
acct = journal.find_account(post.account_mask->str());
|
|
||||||
#if defined(DEBUG_ON)
|
|
||||||
if (acct)
|
|
||||||
DEBUG("derive.xact", "Found (or created) account by name");
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
|
|
||||||
// Find out the default commodity to use by looking at the last
|
|
||||||
// commodity used in that account
|
|
||||||
for (xacts_list::reverse_iterator j = journal.xacts.rbegin();
|
|
||||||
j != journal.xacts.rend();
|
|
||||||
j++) {
|
|
||||||
foreach (post_t * x, (*j)->posts) {
|
|
||||||
if (x->account == acct && ! x->amount.is_null()) {
|
|
||||||
new_post.reset(new post_t(*x));
|
|
||||||
DEBUG("derive.xact",
|
|
||||||
"Found account in journal postings, setting new posting");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
new_post->account = acct;
|
|
||||||
DEBUG("derive.xact",
|
|
||||||
"Set new posting's account to: " << acct->fullname());
|
|
||||||
} else {
|
|
||||||
if (post.from) {
|
|
||||||
new_post->account = journal.find_account(_("Liabilities:Unknown"));
|
|
||||||
DEBUG("derive.xact",
|
|
||||||
"Set new posting's account to: Liabilities:Unknown");
|
|
||||||
} else {
|
|
||||||
new_post->account = journal.find_account(_("Expenses:Unknown"));
|
|
||||||
DEBUG("derive.xact",
|
|
||||||
"Set new posting's account to: Expenses:Unknown");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (new_post.get() && ! new_post->amount.is_null()) {
|
|
||||||
found_commodity = &new_post->amount.commodity();
|
|
||||||
|
|
||||||
if (any_post_has_amount) {
|
|
||||||
new_post->amount = amount_t();
|
|
||||||
DEBUG("derive.xact", "New posting has an amount, but we cleared it");
|
|
||||||
} else {
|
|
||||||
any_post_has_amount = true;
|
|
||||||
DEBUG("derive.xact", "New posting has an amount, and we're using it");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (post.amount) {
|
|
||||||
new_post->amount = *post.amount;
|
|
||||||
DEBUG("derive.xact", "Copied over posting amount");
|
|
||||||
|
|
||||||
if (post.from) {
|
|
||||||
new_post->amount.in_place_negate();
|
|
||||||
DEBUG("derive.xact", "Negated new posting amount");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (post.cost) {
|
|
||||||
if (post.cost->sign() < 0)
|
|
||||||
throw parse_error(_("A posting's cost may not be negative"));
|
|
||||||
|
|
||||||
post.cost->in_place_unround();
|
|
||||||
|
|
||||||
if (*post.cost_operator == "@") {
|
|
||||||
// For the sole case where the cost might be uncommoditized,
|
|
||||||
// guarantee that the commodity of the cost after multiplication
|
|
||||||
// is the same as it was before.
|
|
||||||
commodity_t& cost_commodity(post.cost->commodity());
|
|
||||||
*post.cost *= new_post->amount;
|
|
||||||
post.cost->set_commodity(cost_commodity);
|
|
||||||
}
|
|
||||||
|
|
||||||
new_post->cost = *post.cost;
|
|
||||||
DEBUG("derive.xact", "Copied over posting cost");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (found_commodity &&
|
|
||||||
! new_post->amount.is_null() &&
|
|
||||||
! new_post->amount.has_commodity()) {
|
|
||||||
new_post->amount.set_commodity(*found_commodity);
|
|
||||||
DEBUG("derive.xact", "Set posting amount commodity to: "
|
|
||||||
<< new_post->amount.commodity());
|
|
||||||
|
|
||||||
new_post->amount = new_post->amount.rounded();
|
|
||||||
DEBUG("derive.xact",
|
|
||||||
"Rounded posting amount to: " << new_post->amount);
|
|
||||||
}
|
|
||||||
|
|
||||||
added->add_post(new_post.release());
|
|
||||||
added->posts.back()->set_state(item_t::UNCLEARED);
|
|
||||||
|
|
||||||
DEBUG("derive.xact", "Added new posting to derived entry");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! journal.xact_finalize_hooks.run_hooks(*added.get(), false) ||
|
|
||||||
! added->finalize() ||
|
|
||||||
! journal.xact_finalize_hooks.run_hooks(*added.get(), true)) {
|
|
||||||
throw_(std::runtime_error,
|
|
||||||
_("Failed to finalize derived transaction (check commodities)"));
|
|
||||||
}
|
|
||||||
|
|
||||||
return added.release();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
value_t template_command(call_scope_t& args)
|
|
||||||
{
|
|
||||||
report_t& report(find_scope<report_t>(args));
|
|
||||||
std::ostream& out(report.output_stream);
|
|
||||||
|
|
||||||
value_t::sequence_t::const_iterator begin = args.value().begin();
|
|
||||||
value_t::sequence_t::const_iterator end = args.value().end();
|
|
||||||
|
|
||||||
out << _("--- Input arguments ---") << std::endl;
|
|
||||||
args.value().dump(out);
|
|
||||||
out << std::endl << std::endl;
|
|
||||||
|
|
||||||
xact_template_t tmpl = args_to_xact_template(begin, end);
|
|
||||||
|
|
||||||
out << _("--- Transaction template ---") << std::endl;
|
|
||||||
tmpl.dump(out);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
value_t xact_command(call_scope_t& args)
|
|
||||||
{
|
|
||||||
value_t::sequence_t::const_iterator begin = args.value().begin();
|
|
||||||
value_t::sequence_t::const_iterator end = args.value().end();
|
|
||||||
|
|
||||||
report_t& report(find_scope<report_t>(args));
|
|
||||||
xact_template_t tmpl = args_to_xact_template(begin, end);
|
|
||||||
std::auto_ptr<xact_t> new_xact(derive_xact_from_template(tmpl, report));
|
|
||||||
|
|
||||||
// Only consider actual postings for the "xact" command
|
|
||||||
report.HANDLER(limit_).on(string("#xact"), "actual");
|
|
||||||
|
|
||||||
report.xact_report(post_handler_ptr
|
|
||||||
(new format_posts(report,
|
|
||||||
report.HANDLER(print_format_).str())),
|
|
||||||
*new_xact.get());
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace ledger
|
|
||||||
525
src/draft.cc
Normal file
525
src/draft.cc
Normal file
|
|
@ -0,0 +1,525 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2003-2009, John Wiegley. All rights reserved.
|
||||||
|
*
|
||||||
|
* Redistribution and use in source and binary forms, with or without
|
||||||
|
* modification, are permitted provided that the following conditions are
|
||||||
|
* met:
|
||||||
|
*
|
||||||
|
* - Redistributions of source code must retain the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer.
|
||||||
|
*
|
||||||
|
* - Redistributions in binary form must reproduce the above copyright
|
||||||
|
* notice, this list of conditions and the following disclaimer in the
|
||||||
|
* documentation and/or other materials provided with the distribution.
|
||||||
|
*
|
||||||
|
* - Neither the name of New Artisans LLC nor the names of its
|
||||||
|
* contributors may be used to endorse or promote products derived from
|
||||||
|
* this software without specific prior written permission.
|
||||||
|
*
|
||||||
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
||||||
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
|
||||||
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
|
||||||
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
|
||||||
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
|
||||||
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
|
||||||
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
|
||||||
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||||
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
||||||
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <system.hh>
|
||||||
|
|
||||||
|
#include "draft.h"
|
||||||
|
#include "xact.h"
|
||||||
|
#include "post.h"
|
||||||
|
#include "account.h"
|
||||||
|
#include "journal.h"
|
||||||
|
#include "session.h"
|
||||||
|
#include "report.h"
|
||||||
|
#include "output.h"
|
||||||
|
|
||||||
|
namespace ledger {
|
||||||
|
|
||||||
|
void draft_t::xact_template_t::dump(std::ostream& out) const
|
||||||
|
{
|
||||||
|
if (date)
|
||||||
|
out << _("Date: ") << *date << std::endl;
|
||||||
|
else
|
||||||
|
out << _("Date: <today>") << std::endl;
|
||||||
|
|
||||||
|
if (code)
|
||||||
|
out << _("Code: ") << *code << std::endl;
|
||||||
|
if (note)
|
||||||
|
out << _("Note: ") << *note << std::endl;
|
||||||
|
|
||||||
|
if (payee_mask.empty())
|
||||||
|
out << _("Payee mask: INVALID (template expression will cause an error)")
|
||||||
|
<< std::endl;
|
||||||
|
else
|
||||||
|
out << _("Payee mask: ") << payee_mask << std::endl;
|
||||||
|
|
||||||
|
if (posts.empty()) {
|
||||||
|
out << std::endl
|
||||||
|
<< _("<Posting copied from last related transaction>")
|
||||||
|
<< std::endl;
|
||||||
|
} else {
|
||||||
|
bool has_only_from = true;
|
||||||
|
bool has_only_to = true;
|
||||||
|
|
||||||
|
foreach (const post_template_t& post, posts) {
|
||||||
|
if (post.from)
|
||||||
|
has_only_to = false;
|
||||||
|
else
|
||||||
|
has_only_from = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (const post_template_t& post, posts) {
|
||||||
|
straccstream accum;
|
||||||
|
out << std::endl
|
||||||
|
<< ACCUM(accum << _("[Posting \"%1\"]")
|
||||||
|
<< (post.from ? _("from") : _("to")))
|
||||||
|
<< std::endl;
|
||||||
|
|
||||||
|
if (post.account_mask)
|
||||||
|
out << _(" Account mask: ") << *post.account_mask << std::endl;
|
||||||
|
else if (post.from)
|
||||||
|
out << _(" Account mask: <use last of last related accounts>") << std::endl;
|
||||||
|
else
|
||||||
|
out << _(" Account mask: <use first of last related accounts>") << std::endl;
|
||||||
|
|
||||||
|
if (post.amount)
|
||||||
|
out << _(" Amount: ") << *post.amount << std::endl;
|
||||||
|
|
||||||
|
if (post.cost)
|
||||||
|
out << _(" Cost: ") << *post.cost_operator
|
||||||
|
<< " " << *post.cost << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void draft_t::parse_args(const value_t& args)
|
||||||
|
{
|
||||||
|
regex date_mask(_("([0-9]+(?:[-/.][0-9]+)?(?:[-/.][0-9]+))?"));
|
||||||
|
smatch what;
|
||||||
|
bool check_for_date = true;
|
||||||
|
|
||||||
|
tmpl = xact_template_t();
|
||||||
|
|
||||||
|
optional<date_time::weekdays> weekday;
|
||||||
|
xact_template_t::post_template_t * post = NULL;
|
||||||
|
|
||||||
|
value_t::sequence_t::const_iterator begin = args.begin();
|
||||||
|
value_t::sequence_t::const_iterator end = args.end();
|
||||||
|
|
||||||
|
for (; begin != end; begin++) {
|
||||||
|
if (check_for_date &&
|
||||||
|
regex_match((*begin).to_string(), what, date_mask)) {
|
||||||
|
tmpl->date = parse_date(what[0]);
|
||||||
|
check_for_date = false;
|
||||||
|
}
|
||||||
|
else if (check_for_date &&
|
||||||
|
bool(weekday = string_to_day_of_week(what[0]))) {
|
||||||
|
short dow = static_cast<short>(*weekday);
|
||||||
|
date_t date = CURRENT_DATE() - date_duration(1);
|
||||||
|
while (date.day_of_week() != dow)
|
||||||
|
date -= date_duration(1);
|
||||||
|
tmpl->date = date;
|
||||||
|
check_for_date = false;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
string arg = (*begin).to_string();
|
||||||
|
|
||||||
|
if (arg == "at") {
|
||||||
|
if (begin == end)
|
||||||
|
throw std::runtime_error(_("Invalid xact command arguments"));
|
||||||
|
tmpl->payee_mask = (*++begin).to_string();
|
||||||
|
}
|
||||||
|
else if (arg == "to" || arg == "from") {
|
||||||
|
if (! post || post->account_mask) {
|
||||||
|
tmpl->posts.push_back(xact_template_t::post_template_t());
|
||||||
|
post = &tmpl->posts.back();
|
||||||
|
}
|
||||||
|
if (begin == end)
|
||||||
|
throw std::runtime_error(_("Invalid xact command arguments"));
|
||||||
|
post->account_mask = mask_t((*++begin).to_string());
|
||||||
|
post->from = arg == "from";
|
||||||
|
}
|
||||||
|
else if (arg == "on") {
|
||||||
|
if (begin == end)
|
||||||
|
throw std::runtime_error(_("Invalid xact command arguments"));
|
||||||
|
tmpl->date = parse_date((*++begin).to_string());
|
||||||
|
check_for_date = false;
|
||||||
|
}
|
||||||
|
else if (arg == "code") {
|
||||||
|
if (begin == end)
|
||||||
|
throw std::runtime_error(_("Invalid xact command arguments"));
|
||||||
|
tmpl->code = (*++begin).to_string();
|
||||||
|
}
|
||||||
|
else if (arg == "note") {
|
||||||
|
if (begin == end)
|
||||||
|
throw std::runtime_error(_("Invalid xact command arguments"));
|
||||||
|
tmpl->note = (*++begin).to_string();
|
||||||
|
}
|
||||||
|
else if (arg == "rest") {
|
||||||
|
; // just ignore this argument
|
||||||
|
}
|
||||||
|
else if (arg == "@" || arg == "@@") {
|
||||||
|
amount_t cost;
|
||||||
|
post->cost_operator = arg;
|
||||||
|
if (begin == end)
|
||||||
|
throw std::runtime_error(_("Invalid xact command arguments"));
|
||||||
|
arg = (*++begin).to_string();
|
||||||
|
if (! cost.parse(arg, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE))
|
||||||
|
throw std::runtime_error(_("Invalid xact command arguments"));
|
||||||
|
post->cost = cost;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
// Without a preposition, it is either:
|
||||||
|
//
|
||||||
|
// A payee, if we have not seen one
|
||||||
|
// An account or an amount, if we have
|
||||||
|
// An account if an amount has just been seen
|
||||||
|
// An amount if an account has just been seen
|
||||||
|
|
||||||
|
if (tmpl->payee_mask.empty()) {
|
||||||
|
tmpl->payee_mask = arg;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
amount_t amt;
|
||||||
|
optional<mask_t> account;
|
||||||
|
|
||||||
|
if (! amt.parse(arg, PARSE_SOFT_FAIL | PARSE_NO_MIGRATE))
|
||||||
|
account = mask_t(arg);
|
||||||
|
|
||||||
|
if (! post ||
|
||||||
|
(account && post->account_mask) ||
|
||||||
|
(! account && post->amount)) {
|
||||||
|
tmpl->posts.push_back(xact_template_t::post_template_t());
|
||||||
|
post = &tmpl->posts.back();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (account) {
|
||||||
|
post->from = false;
|
||||||
|
post->account_mask = account;
|
||||||
|
} else {
|
||||||
|
post->amount = amt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! tmpl->posts.empty()) {
|
||||||
|
bool has_only_from = true;
|
||||||
|
bool has_only_to = true;
|
||||||
|
|
||||||
|
// A single account at the end of the line is the "from" account
|
||||||
|
if (tmpl->posts.size() > 1 &&
|
||||||
|
tmpl->posts.back().account_mask && ! tmpl->posts.back().amount)
|
||||||
|
tmpl->posts.back().from = true;
|
||||||
|
|
||||||
|
foreach (xact_template_t::post_template_t& post, tmpl->posts) {
|
||||||
|
if (post.from)
|
||||||
|
has_only_to = false;
|
||||||
|
else
|
||||||
|
has_only_from = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (has_only_from) {
|
||||||
|
tmpl->posts.push_front(xact_template_t::post_template_t());
|
||||||
|
}
|
||||||
|
else if (has_only_to) {
|
||||||
|
tmpl->posts.push_back(xact_template_t::post_template_t());
|
||||||
|
tmpl->posts.back().from = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
xact_t * draft_t::insert(journal_t& journal)
|
||||||
|
{
|
||||||
|
if (tmpl->payee_mask.empty())
|
||||||
|
throw std::runtime_error(_("xact' command requires at least a payee"));
|
||||||
|
|
||||||
|
xact_t * matching = NULL;
|
||||||
|
|
||||||
|
std::auto_ptr<xact_t> added(new xact_t);
|
||||||
|
|
||||||
|
for (xacts_list::reverse_iterator j = journal.xacts.rbegin();
|
||||||
|
j != journal.xacts.rend();
|
||||||
|
j++) {
|
||||||
|
if (tmpl->payee_mask.match((*j)->payee)) {
|
||||||
|
matching = *j;
|
||||||
|
DEBUG("derive.xact",
|
||||||
|
"Found payee match: transaction on line " << (*j)->pos->beg_line);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! tmpl->date) {
|
||||||
|
added->_date = CURRENT_DATE();
|
||||||
|
DEBUG("derive.xact", "Setting date to current date");
|
||||||
|
} else {
|
||||||
|
added->_date = tmpl->date;
|
||||||
|
DEBUG("derive.xact", "Setting date to template date: " << *tmpl->date);
|
||||||
|
}
|
||||||
|
|
||||||
|
added->set_state(item_t::UNCLEARED);
|
||||||
|
|
||||||
|
if (matching) {
|
||||||
|
added->payee = matching->payee;
|
||||||
|
added->code = matching->code;
|
||||||
|
added->note = matching->note;
|
||||||
|
|
||||||
|
#if defined(DEBUG_ON)
|
||||||
|
DEBUG("derive.xact", "Setting payee from match: " << added->payee);
|
||||||
|
if (added->code)
|
||||||
|
DEBUG("derive.xact", "Setting code from match: " << *added->code);
|
||||||
|
if (added->note)
|
||||||
|
DEBUG("derive.xact", "Setting note from match: " << *added->note);
|
||||||
|
#endif
|
||||||
|
} else {
|
||||||
|
added->payee = tmpl->payee_mask.str();
|
||||||
|
DEBUG("derive.xact", "Setting payee from template: " << added->payee);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tmpl->code) {
|
||||||
|
added->code = tmpl->code;
|
||||||
|
DEBUG("derive.xact", "Now setting code from template: " << *added->code);
|
||||||
|
}
|
||||||
|
if (tmpl->note) {
|
||||||
|
added->note = tmpl->note;
|
||||||
|
DEBUG("derive.xact", "Now setting note from template: " << *added->note);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (tmpl->posts.empty()) {
|
||||||
|
if (matching) {
|
||||||
|
DEBUG("derive.xact", "Template had no postings, copying from match");
|
||||||
|
|
||||||
|
foreach (post_t * post, matching->posts) {
|
||||||
|
added->add_post(new post_t(*post));
|
||||||
|
added->posts.back()->set_state(item_t::UNCLEARED);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw_(std::runtime_error,
|
||||||
|
_("No accounts, and no past transaction matching '%1'")
|
||||||
|
<< tmpl->payee_mask);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
DEBUG("derive.xact", "Template had postings");
|
||||||
|
|
||||||
|
bool any_post_has_amount = false;
|
||||||
|
foreach (xact_template_t::post_template_t& post, tmpl->posts) {
|
||||||
|
if (post.amount) {
|
||||||
|
DEBUG("derive.xact", " and at least one has an amount specified");
|
||||||
|
any_post_has_amount = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach (xact_template_t::post_template_t& post, tmpl->posts) {
|
||||||
|
std::auto_ptr<post_t> new_post;
|
||||||
|
|
||||||
|
commodity_t * found_commodity = NULL;
|
||||||
|
|
||||||
|
if (matching) {
|
||||||
|
if (post.account_mask) {
|
||||||
|
DEBUG("derive.xact",
|
||||||
|
"Looking for matching posting based on account mask");
|
||||||
|
|
||||||
|
foreach (post_t * x, matching->posts) {
|
||||||
|
if (post.account_mask->match(x->account->fullname())) {
|
||||||
|
new_post.reset(new post_t(*x));
|
||||||
|
DEBUG("derive.xact",
|
||||||
|
"Founding posting from line " << x->pos->beg_line);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (post.from) {
|
||||||
|
for (posts_list::reverse_iterator j = matching->posts.rbegin();
|
||||||
|
j != matching->posts.rend();
|
||||||
|
j++) {
|
||||||
|
if ((*j)->must_balance()) {
|
||||||
|
new_post.reset(new post_t(**j));
|
||||||
|
DEBUG("derive.xact",
|
||||||
|
"Copied last real posting from matching");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
for (posts_list::iterator j = matching->posts.begin();
|
||||||
|
j != matching->posts.end();
|
||||||
|
j++) {
|
||||||
|
if ((*j)->must_balance()) {
|
||||||
|
new_post.reset(new post_t(**j));
|
||||||
|
DEBUG("derive.xact",
|
||||||
|
"Copied first real posting from matching");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! new_post.get()) {
|
||||||
|
new_post.reset(new post_t);
|
||||||
|
DEBUG("derive.xact", "New posting was NULL, creating a blank one");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! new_post->account) {
|
||||||
|
DEBUG("derive.xact", "New posting still needs an account");
|
||||||
|
|
||||||
|
if (post.account_mask) {
|
||||||
|
DEBUG("derive.xact", "The template has an account mask");
|
||||||
|
|
||||||
|
account_t * acct = NULL;
|
||||||
|
if (! acct) {
|
||||||
|
acct = journal.find_account_re(post.account_mask->str());
|
||||||
|
#if defined(DEBUG_ON)
|
||||||
|
if (acct)
|
||||||
|
DEBUG("derive.xact", "Found account as a regular expression");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
if (! acct) {
|
||||||
|
acct = journal.find_account(post.account_mask->str());
|
||||||
|
#if defined(DEBUG_ON)
|
||||||
|
if (acct)
|
||||||
|
DEBUG("derive.xact", "Found (or created) account by name");
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
|
// Find out the default commodity to use by looking at the last
|
||||||
|
// commodity used in that account
|
||||||
|
for (xacts_list::reverse_iterator j = journal.xacts.rbegin();
|
||||||
|
j != journal.xacts.rend();
|
||||||
|
j++) {
|
||||||
|
foreach (post_t * x, (*j)->posts) {
|
||||||
|
if (x->account == acct && ! x->amount.is_null()) {
|
||||||
|
new_post.reset(new post_t(*x));
|
||||||
|
DEBUG("derive.xact",
|
||||||
|
"Found account in journal postings, setting new posting");
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
new_post->account = acct;
|
||||||
|
DEBUG("derive.xact",
|
||||||
|
"Set new posting's account to: " << acct->fullname());
|
||||||
|
} else {
|
||||||
|
if (post.from) {
|
||||||
|
new_post->account = journal.find_account(_("Liabilities:Unknown"));
|
||||||
|
DEBUG("derive.xact",
|
||||||
|
"Set new posting's account to: Liabilities:Unknown");
|
||||||
|
} else {
|
||||||
|
new_post->account = journal.find_account(_("Expenses:Unknown"));
|
||||||
|
DEBUG("derive.xact",
|
||||||
|
"Set new posting's account to: Expenses:Unknown");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new_post.get() && ! new_post->amount.is_null()) {
|
||||||
|
found_commodity = &new_post->amount.commodity();
|
||||||
|
|
||||||
|
if (any_post_has_amount) {
|
||||||
|
new_post->amount = amount_t();
|
||||||
|
DEBUG("derive.xact", "New posting has an amount, but we cleared it");
|
||||||
|
} else {
|
||||||
|
any_post_has_amount = true;
|
||||||
|
DEBUG("derive.xact", "New posting has an amount, and we're using it");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (post.amount) {
|
||||||
|
new_post->amount = *post.amount;
|
||||||
|
DEBUG("derive.xact", "Copied over posting amount");
|
||||||
|
|
||||||
|
if (post.from) {
|
||||||
|
new_post->amount.in_place_negate();
|
||||||
|
DEBUG("derive.xact", "Negated new posting amount");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (post.cost) {
|
||||||
|
if (post.cost->sign() < 0)
|
||||||
|
throw parse_error(_("A posting's cost may not be negative"));
|
||||||
|
|
||||||
|
post.cost->in_place_unround();
|
||||||
|
|
||||||
|
if (*post.cost_operator == "@") {
|
||||||
|
// For the sole case where the cost might be uncommoditized,
|
||||||
|
// guarantee that the commodity of the cost after multiplication
|
||||||
|
// is the same as it was before.
|
||||||
|
commodity_t& cost_commodity(post.cost->commodity());
|
||||||
|
*post.cost *= new_post->amount;
|
||||||
|
post.cost->set_commodity(cost_commodity);
|
||||||
|
}
|
||||||
|
|
||||||
|
new_post->cost = *post.cost;
|
||||||
|
DEBUG("derive.xact", "Copied over posting cost");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (found_commodity &&
|
||||||
|
! new_post->amount.is_null() &&
|
||||||
|
! new_post->amount.has_commodity()) {
|
||||||
|
new_post->amount.set_commodity(*found_commodity);
|
||||||
|
DEBUG("derive.xact", "Set posting amount commodity to: "
|
||||||
|
<< new_post->amount.commodity());
|
||||||
|
|
||||||
|
new_post->amount = new_post->amount.rounded();
|
||||||
|
DEBUG("derive.xact",
|
||||||
|
"Rounded posting amount to: " << new_post->amount);
|
||||||
|
}
|
||||||
|
|
||||||
|
added->add_post(new_post.release());
|
||||||
|
added->posts.back()->set_state(item_t::UNCLEARED);
|
||||||
|
|
||||||
|
DEBUG("derive.xact", "Added new posting to derived entry");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (! journal.add_xact(added.get()))
|
||||||
|
throw_(std::runtime_error,
|
||||||
|
_("Failed to finalize derived transaction (check commodities)"));
|
||||||
|
|
||||||
|
return added.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
value_t template_command(call_scope_t& args)
|
||||||
|
{
|
||||||
|
report_t& report(find_scope<report_t>(args));
|
||||||
|
std::ostream& out(report.output_stream);
|
||||||
|
|
||||||
|
out << _("--- Input arguments ---") << std::endl;
|
||||||
|
args.value().dump(out);
|
||||||
|
out << std::endl << std::endl;
|
||||||
|
|
||||||
|
draft_t draft(args.value());
|
||||||
|
|
||||||
|
out << _("--- Transaction template ---") << std::endl;
|
||||||
|
draft.dump(out);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
value_t xact_command(call_scope_t& args)
|
||||||
|
{
|
||||||
|
report_t& report(find_scope<report_t>(args));
|
||||||
|
draft_t draft(args.value());
|
||||||
|
|
||||||
|
xact_t * new_xact = draft.insert(*report.session.journal.get());
|
||||||
|
|
||||||
|
// Only consider actual postings for the "xact" command
|
||||||
|
report.HANDLER(limit_).on(string("#xact"), "actual");
|
||||||
|
|
||||||
|
report.xact_report(post_handler_ptr
|
||||||
|
(new format_posts(report,
|
||||||
|
report.HANDLER(print_format_).str())),
|
||||||
|
*new_xact);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace ledger
|
||||||
|
|
@ -42,21 +42,72 @@
|
||||||
#ifndef _DERIVE_H
|
#ifndef _DERIVE_H
|
||||||
#define _DERIVE_H
|
#define _DERIVE_H
|
||||||
|
|
||||||
|
#include "exprbase.h"
|
||||||
#include "value.h"
|
#include "value.h"
|
||||||
|
|
||||||
namespace ledger {
|
namespace ledger {
|
||||||
|
|
||||||
class call_scope_t;
|
class journal_t;
|
||||||
|
class xact_t;
|
||||||
|
|
||||||
|
class draft_t : public expr_base_t<value_t>
|
||||||
|
{
|
||||||
|
typedef expr_base_t<value_t> base_type;
|
||||||
|
|
||||||
|
struct xact_template_t
|
||||||
|
{
|
||||||
|
optional<date_t> date;
|
||||||
|
optional<string> code;
|
||||||
|
optional<string> note;
|
||||||
|
mask_t payee_mask;
|
||||||
|
|
||||||
|
struct post_template_t {
|
||||||
|
bool from;
|
||||||
|
optional<mask_t> account_mask;
|
||||||
|
optional<amount_t> amount;
|
||||||
|
optional<string> cost_operator;
|
||||||
|
optional<amount_t> cost;
|
||||||
|
|
||||||
|
post_template_t() : from(false) {}
|
||||||
|
};
|
||||||
|
|
||||||
|
std::list<post_template_t> posts;
|
||||||
|
|
||||||
|
xact_template_t() {}
|
||||||
|
|
||||||
|
void dump(std::ostream& out) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
optional<xact_template_t> tmpl;
|
||||||
|
|
||||||
|
public:
|
||||||
|
draft_t(const value_t& args) : base_type() {
|
||||||
|
TRACE_CTOR(draft_t, "value_t");
|
||||||
|
if (! args.empty())
|
||||||
|
parse_args(args);
|
||||||
|
}
|
||||||
|
~draft_t() {
|
||||||
|
TRACE_DTOR(draft_t);
|
||||||
|
}
|
||||||
|
|
||||||
|
void parse_args(const value_t& args);
|
||||||
|
|
||||||
|
virtual result_type real_calc(scope_t&) {
|
||||||
|
assert(0);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
xact_t * insert(journal_t& journal);
|
||||||
|
|
||||||
|
virtual void dump(std::ostream& out) const {
|
||||||
|
if (tmpl)
|
||||||
|
tmpl->dump(out);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
value_t xact_command(call_scope_t& args);
|
value_t xact_command(call_scope_t& args);
|
||||||
value_t template_command(call_scope_t& args);
|
value_t template_command(call_scope_t& args);
|
||||||
|
|
||||||
class xact_t;
|
|
||||||
class report_t;
|
|
||||||
xact_t * derive_new_xact(report_t& report,
|
|
||||||
value_t::sequence_t::const_iterator i,
|
|
||||||
value_t::sequence_t::const_iterator end);
|
|
||||||
|
|
||||||
} // namespace ledger
|
} // namespace ledger
|
||||||
|
|
||||||
#endif // _DERIVE_H
|
#endif // _DERIVE_H
|
||||||
|
|
@ -105,8 +105,7 @@ class format_t : public expr_base_t<string>
|
||||||
void dump(std::ostream& out) const;
|
void dump(std::ostream& out) const;
|
||||||
};
|
};
|
||||||
|
|
||||||
string format_string;
|
scoped_ptr<element_t> elements;
|
||||||
scoped_ptr<element_t> elements;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
static enum elision_style_t {
|
static enum elision_style_t {
|
||||||
|
|
|
||||||
|
|
@ -42,7 +42,7 @@
|
||||||
#include "precmd.h"
|
#include "precmd.h"
|
||||||
#include "stats.h"
|
#include "stats.h"
|
||||||
#include "generate.h"
|
#include "generate.h"
|
||||||
#include "derive.h"
|
#include "draft.h"
|
||||||
#include "emacs.h"
|
#include "emacs.h"
|
||||||
|
|
||||||
namespace ledger {
|
namespace ledger {
|
||||||
|
|
|
||||||
|
|
@ -31,7 +31,7 @@
|
||||||
|
|
||||||
#include <system.hh>
|
#include <system.hh>
|
||||||
|
|
||||||
#include "derive.h"
|
#include "draft.h"
|
||||||
#include "xact.h"
|
#include "xact.h"
|
||||||
#include "post.h"
|
#include "post.h"
|
||||||
#include "account.h"
|
#include "account.h"
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
VERSION = 3.0
|
VERSION = 3.0.0
|
||||||
ACLOCAL_AMFLAGS = -I m4
|
ACLOCAL_AMFLAGS = -I m4
|
||||||
dist_man_MANS = doc/ledger.1
|
dist_man_MANS = doc/ledger.1
|
||||||
SUBDIRS = po intl
|
SUBDIRS = po intl
|
||||||
|
|
@ -25,9 +25,9 @@ libledger_util_la_SOURCES = \
|
||||||
lib/sha1.cpp
|
lib/sha1.cpp
|
||||||
|
|
||||||
libledger_util_la_CPPFLAGS = $(lib_cppflags)
|
libledger_util_la_CPPFLAGS = $(lib_cppflags)
|
||||||
libledger_util_la_LDFLAGS = -release $(VERSION).0
|
libledger_util_la_LDFLAGS = -release $(VERSION)
|
||||||
|
|
||||||
libledger_math_la_SOURCES = \
|
libledger_math_la_SOURCES = \
|
||||||
src/value.cc \
|
src/value.cc \
|
||||||
src/balance.cc \
|
src/balance.cc \
|
||||||
src/quotes.cc \
|
src/quotes.cc \
|
||||||
|
|
@ -37,9 +37,9 @@ libledger_math_la_SOURCES = \
|
||||||
src/amount.cc
|
src/amount.cc
|
||||||
|
|
||||||
libledger_math_la_CPPFLAGS = $(lib_cppflags)
|
libledger_math_la_CPPFLAGS = $(lib_cppflags)
|
||||||
libledger_math_la_LDFLAGS = -release $(VERSION).0
|
libledger_math_la_LDFLAGS = -release $(VERSION)
|
||||||
|
|
||||||
libledger_expr_la_SOURCES = \
|
libledger_expr_la_SOURCES = \
|
||||||
src/option.cc \
|
src/option.cc \
|
||||||
src/format.cc \
|
src/format.cc \
|
||||||
src/query.cc \
|
src/query.cc \
|
||||||
|
|
@ -51,9 +51,9 @@ libledger_expr_la_SOURCES = \
|
||||||
src/token.cc
|
src/token.cc
|
||||||
|
|
||||||
libledger_expr_la_CPPFLAGS = $(lib_cppflags)
|
libledger_expr_la_CPPFLAGS = $(lib_cppflags)
|
||||||
libledger_expr_la_LDFLAGS = -release $(VERSION).0
|
libledger_expr_la_LDFLAGS = -release $(VERSION)
|
||||||
|
|
||||||
libledger_data_la_SOURCES = \
|
libledger_data_la_SOURCES = \
|
||||||
src/compare.cc \
|
src/compare.cc \
|
||||||
src/iterators.cc \
|
src/iterators.cc \
|
||||||
src/timelog.cc \
|
src/timelog.cc \
|
||||||
|
|
@ -65,13 +65,13 @@ libledger_data_la_SOURCES = \
|
||||||
src/post.cc \
|
src/post.cc \
|
||||||
src/item.cc
|
src/item.cc
|
||||||
|
|
||||||
libledger_data_la_CPPFLAGS = $(lib_cppflags)
|
libledger_data_la_CPPFLAGS = $(lib_cppflags)
|
||||||
libledger_data_la_LDFLAGS = -release $(VERSION).0
|
libledger_data_la_LDFLAGS = -release $(VERSION)
|
||||||
|
|
||||||
libledger_report_la_SOURCES = \
|
libledger_report_la_SOURCES = \
|
||||||
src/stats.cc \
|
src/stats.cc \
|
||||||
src/generate.cc \
|
src/generate.cc \
|
||||||
src/derive.cc \
|
src/draft.cc \
|
||||||
src/emacs.cc \
|
src/emacs.cc \
|
||||||
src/output.cc \
|
src/output.cc \
|
||||||
src/precmd.cc \
|
src/precmd.cc \
|
||||||
|
|
@ -82,7 +82,7 @@ libledger_report_la_SOURCES = \
|
||||||
src/session.cc
|
src/session.cc
|
||||||
|
|
||||||
libledger_report_la_CPPFLAGS = $(lib_cppflags)
|
libledger_report_la_CPPFLAGS = $(lib_cppflags)
|
||||||
libledger_report_la_LDFLAGS = -release $(VERSION).0
|
libledger_report_la_LDFLAGS = -release $(VERSION)
|
||||||
|
|
||||||
pkginclude_HEADERS = \
|
pkginclude_HEADERS = \
|
||||||
src/utils.h \
|
src/utils.h \
|
||||||
|
|
@ -132,7 +132,7 @@ pkginclude_HEADERS = \
|
||||||
src/temps.h \
|
src/temps.h \
|
||||||
src/chain.h \
|
src/chain.h \
|
||||||
src/precmd.h \
|
src/precmd.h \
|
||||||
src/derive.h \
|
src/draft.h \
|
||||||
src/generate.h \
|
src/generate.h \
|
||||||
src/stats.h \
|
src/stats.h \
|
||||||
src/output.h \
|
src/output.h \
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue