24#include <glib/gi18n.h>
31#include "engine-helpers.h"
36#include <gnc-exp-parser.h>
45#include <boost/locale.hpp>
46#include <boost/regex.hpp>
47#include <boost/regex/icu.hpp>
48#include <gnc-locale-utils.hpp>
49#include "gnc-imp-props-tx.hpp"
52namespace bl = boost::locale;
54G_GNUC_UNUSED
static QofLogModule log_module = GNC_MOD_IMPORT;
57std::map<GncTransPropType, const char*> gnc_csv_col_type_strs = {
58 { GncTransPropType::NONE, N_(
"None") },
59 { GncTransPropType::UNIQUE_ID, N_(
"Transaction ID") },
60 { GncTransPropType::DATE, N_(
"Date") },
61 { GncTransPropType::NUM, N_(
"Number") },
62 { GncTransPropType::DESCRIPTION, N_(
"Description") },
63 { GncTransPropType::NOTES, N_(
"Notes") },
64 { GncTransPropType::COMMODITY, N_(
"Transaction Commodity") },
65 { GncTransPropType::VOID_REASON, N_(
"Void Reason") },
66 { GncTransPropType::ACTION, N_(
"Action") },
67 { GncTransPropType::ACCOUNT, N_(
"Account") },
68 { GncTransPropType::AMOUNT, N_(
"Amount") },
69 { GncTransPropType::AMOUNT_NEG, N_(
"Amount (Negated)") },
70 { GncTransPropType::VALUE, N_(
"Value") },
71 { GncTransPropType::VALUE_NEG, N_(
"Value (Negated)") },
72 { GncTransPropType::PRICE, N_(
"Price") },
73 { GncTransPropType::MEMO, N_(
"Memo") },
74 { GncTransPropType::REC_STATE, N_(
"Reconciled") },
75 { GncTransPropType::REC_DATE, N_(
"Reconcile Date") },
76 { GncTransPropType::TACTION, N_(
"Transfer Action") },
77 { GncTransPropType::TACCOUNT, N_(
"Transfer Account") },
78 { GncTransPropType::TAMOUNT, N_(
"Transfer Amount") },
79 { GncTransPropType::TAMOUNT_NEG, N_(
"Transfer Amount (Negated)") },
80 { GncTransPropType::TMEMO, N_(
"Transfer Memo") },
81 { GncTransPropType::TREC_STATE, N_(
"Transfer Reconciled") },
82 { GncTransPropType::TREC_DATE, N_(
"Transfer Reconcile Date") }
89std::vector<GncTransPropType> twosplit_blacklist = {
90 GncTransPropType::UNIQUE_ID };
91std::vector<GncTransPropType> multisplit_blacklist = {
92 GncTransPropType::TACTION,
93 GncTransPropType::TACCOUNT,
94 GncTransPropType::TAMOUNT,
95 GncTransPropType::TAMOUNT_NEG,
96 GncTransPropType::TMEMO,
97 GncTransPropType::TREC_STATE,
98 GncTransPropType::TREC_DATE
101std::vector<GncTransPropType> multi_col_props = {
102 GncTransPropType::AMOUNT,
103 GncTransPropType::AMOUNT_NEG,
104 GncTransPropType::TAMOUNT,
105 GncTransPropType::TAMOUNT_NEG,
106 GncTransPropType::VALUE,
107 GncTransPropType::VALUE_NEG
110bool is_multi_col_prop (GncTransPropType prop)
112 return (std::find (multi_col_props.cbegin(),
113 multi_col_props.cend(), prop) != multi_col_props.cend());
116GncTransPropType sanitize_trans_prop (GncTransPropType prop,
bool multi_split)
118 auto bl = multi_split ? multisplit_blacklist : twosplit_blacklist;
119 if (std::find(bl.begin(), bl.end(), prop) == bl.end())
122 return GncTransPropType::NONE;
132GncNumeric parse_monetary (
const std::string &str,
int currency_format)
139 static constexpr ctll::fixed_string digit_re{
"[0-9]"};
140 if(!ctre::search<digit_re>(str))
141 throw std::invalid_argument (
_(
"Value doesn't appear to contain a valid number."));
143 static const auto expr = boost::make_u32regex(
"[[:Sc:][:blank:]]|--");
144 std::string str_no_symbols;
145 boost::u32regex_replace(icu::UnicodeString::fromUTF8(str), expr,
"").toUTF8String(str_no_symbols);
148 gnc_numeric val = gnc_numeric_zero();
150 switch (currency_format)
155 throw std::invalid_argument (
_(
"Value can't be parsed into a number using the selected currency format."));
160 throw std::invalid_argument (
_(
"Value can't be parsed into a number using the selected currency format."));
165 throw std::invalid_argument (
_(
"Value can't be parsed into a number using the selected currency format."));
172static char parse_reconciled (
const std::string& reconcile)
174 if (g_strcmp0 (reconcile.c_str(), gnc_get_reconcile_str(
NREC)) == 0)
176 else if (g_strcmp0 (reconcile.c_str(), gnc_get_reconcile_str(
CREC)) == 0)
178 else if (g_strcmp0 (reconcile.c_str(), gnc_get_reconcile_str(
YREC)) == 0)
180 else if (g_strcmp0 (reconcile.c_str(), gnc_get_reconcile_str(
FREC)) == 0)
182 else if (g_strcmp0 (reconcile.c_str(), gnc_get_reconcile_str(
VREC)) == 0)
185 throw std::invalid_argument (
_(
"Value can't be parsed into a valid reconcile state."));
188gnc_commodity* parse_commodity (
const std::string& comm_str)
190 if (comm_str.empty())
194 gnc_commodity* comm =
nullptr;
197 comm = gnc_commodity_table_lookup_unique (
table, comm_str.c_str());
201 comm = gnc_commodity_table_lookup (
table,
202 GNC_COMMODITY_NS_CURRENCY, comm_str.c_str());
208 for (
auto ns = namespaces; ns; ns = ns->next)
210 gchar* ns_str = (gchar*)ns->data;
211 if (g_utf8_collate(ns_str, GNC_COMMODITY_NS_CURRENCY) == 0)
214 comm = gnc_commodity_table_lookup (
table,
215 ns_str, comm_str.c_str());
219 g_list_free (namespaces);
223 throw std::invalid_argument (
_(
"Value can't be parsed into a valid commodity."));
228void GncPreTrans::set (GncTransPropType prop_type,
const std::string& value)
233 m_errors.erase(prop_type);
237 case GncTransPropType::UNIQUE_ID:
243 case GncTransPropType::DATE:
247 else if (!m_multi_split)
248 throw std::invalid_argument (
249 (bl::format (std::string{
_(
"Date field can not be empty if 'Multi-split' option is unset.\n")}) %
250 std::string{
_(gnc_csv_col_type_strs[prop_type])}).str());
253 case GncTransPropType::NUM:
259 case GncTransPropType::DESCRIPTION:
263 else if (!m_multi_split)
264 throw std::invalid_argument (
265 (bl::format (std::string{
_(
"Description field can not be empty if 'Multi-split' option is unset.\n")}) %
266 std::string{
_(gnc_csv_col_type_strs[prop_type])}).str());
269 case GncTransPropType::NOTES:
275 case GncTransPropType::COMMODITY:
276 m_currency =
nullptr;
277 m_currency = parse_commodity (value);
280 case GncTransPropType::VOID_REASON:
281 m_void_reason.reset();
283 m_void_reason = value;
288 PWARN (
"%d is an invalid property for a transaction",
static_cast<int>(prop_type));
292 catch (
const std::exception& e)
294 auto err_str = (bl::format (std::string{
_(
"{1}: {2}")}) %
295 std::string{
_(gnc_csv_col_type_strs[prop_type])} %
297 m_errors.emplace(prop_type, err_str);
302void GncPreTrans::reset (GncTransPropType prop_type)
304 set (prop_type, std::string());
307 m_errors.erase(prop_type);
310StrVec GncPreTrans::verify_essentials (
void)
312 auto errors = StrVec();
315 errors.emplace_back(
_(
"No valid date."));
318 errors.emplace_back(
_(
"No valid description."));
323std::shared_ptr<DraftTransaction> GncPreTrans::create_trans (
QofBook* book, gnc_commodity* currency)
331 auto check = verify_essentials();
334 auto err_msg = std::string(
"Not creating transaction because essentials not set properly:");
335 auto add_bullet_item = [](std::string& a, std::string& b)->std::string {
return std::move(a) +
"\n• " + b; };
336 err_msg = std::accumulate (check.begin(), check.end(), std::move (err_msg), add_bullet_item);
337 PWARN (
"%s", err_msg.c_str());
361 return std::make_shared<DraftTransaction>(trans);
369 return (!m_differ || m_differ == parent->m_differ) &&
370 (!m_date || m_date == parent->m_date) &&
371 (!m_num || m_num == parent->m_num) &&
372 (!m_desc || m_desc == parent->m_desc) &&
373 (!m_notes || m_notes == parent->m_notes) &&
374 (!m_currency || m_currency == parent->m_currency) &&
375 (!m_void_reason || m_void_reason == parent->m_void_reason) &&
376 parent->m_errors.empty();
379ErrMap GncPreTrans::errors ()
384void GncPreTrans::reset_cross_split_counters()
386 m_alt_currencies.clear();
387 m_acct_commodities.clear();
391bool GncPreTrans::is_multi_currency()
393 auto num_comm = m_acct_commodities.size() + m_alt_currencies.size();
394 if (m_currency && (std::find (m_alt_currencies.cbegin(),m_alt_currencies.cend(), m_currency) == m_alt_currencies.cend()))
396 return (num_comm > 1);
400void GncPreSplit::UpdateCrossSplitCounters ()
402 if (m_account && *m_account)
404 auto acct = *m_account;
406 auto alt_currs = m_pre_trans->m_alt_currencies;
407 auto acct_comms = m_pre_trans->m_acct_commodities;
408 auto curr =
static_cast<gnc_commodity*
> (
nullptr);
417 auto has_curr = [curr] (
const gnc_commodity *vec_curr) {
return gnc_commodity_equiv (curr, vec_curr); };
418 if (curr && std::none_of (alt_currs.cbegin(), alt_currs.cend(), has_curr))
419 m_pre_trans->m_alt_currencies.push_back(curr);
420 auto has_comm = [comm] (
const gnc_commodity *vec_comm) {
return gnc_commodity_equiv (comm, vec_comm); };
421 if (comm && std::none_of (acct_comms.cbegin(), acct_comms.cend(), has_comm))
422 m_pre_trans->m_acct_commodities.push_back(comm);
426void GncPreSplit::set (GncTransPropType prop_type,
const std::string& value)
431 m_errors.erase(prop_type);
436 case GncTransPropType::ACTION:
442 case GncTransPropType::TACTION:
448 case GncTransPropType::ACCOUNT:
451 throw std::invalid_argument (
_(
"Account value can't be empty."));
452 if ((acct = gnc_account_imap_find_any (gnc_get_current_book(), IMAP_CAT_CSV, value.c_str())) ||
456 throw std::invalid_argument (
_(
"Account value can't be mapped back to an account."));
459 case GncTransPropType::TACCOUNT:
462 throw std::invalid_argument (
_(
"Transfer account value can't be empty."));
464 if ((acct = gnc_account_imap_find_any (gnc_get_current_book(), IMAP_CAT_CSV,value.c_str())) ||
468 throw std::invalid_argument (
_(
"Transfer account value can't be mapped back to an account."));
471 case GncTransPropType::MEMO:
477 case GncTransPropType::TMEMO:
483 case GncTransPropType::AMOUNT:
485 m_amount = parse_monetary (value, m_currency_format);
488 case GncTransPropType::AMOUNT_NEG:
489 m_amount_neg.reset();
490 m_amount_neg = parse_monetary (value, m_currency_format);
493 case GncTransPropType::VALUE:
495 m_value = parse_monetary (value, m_currency_format);
498 case GncTransPropType::VALUE_NEG:
500 m_value_neg = parse_monetary (value, m_currency_format);
503 case GncTransPropType::TAMOUNT:
505 m_tamount = parse_monetary (value, m_currency_format);
508 case GncTransPropType::TAMOUNT_NEG:
509 m_tamount_neg.reset();
510 m_tamount_neg = parse_monetary (value, m_currency_format);
513 case GncTransPropType::PRICE:
518 m_price = parse_monetary (value, m_currency_format);
521 case GncTransPropType::REC_STATE:
523 m_rec_state = parse_reconciled (value);
526 case GncTransPropType::TREC_STATE:
527 m_trec_state.reset();
528 m_trec_state = parse_reconciled (value);
531 case GncTransPropType::REC_DATE:
538 case GncTransPropType::TREC_DATE:
547 PWARN (
"%d is an invalid property for a split",
static_cast<int>(prop_type));
551 catch (
const std::exception& e)
553 auto err_str = (bl::format (std::string{
_(
"{1}: {2}")}) %
554 std::string{
_(gnc_csv_col_type_strs[prop_type])} %
556 m_errors.emplace(prop_type, err_str);
560 if (prop_type == GncTransPropType::ACCOUNT)
561 UpdateCrossSplitCounters();
564void GncPreSplit::reset (GncTransPropType prop_type)
566 set (prop_type, std::string());
569 m_errors.erase(prop_type);
572void GncPreSplit::add (GncTransPropType prop_type,
const std::string& value)
577 if (m_errors.find(prop_type) != m_errors.cend())
583 case GncTransPropType::AMOUNT:
584 num_val = parse_monetary (value, m_currency_format);
586 num_val += *m_amount;
590 case GncTransPropType::AMOUNT_NEG:
591 num_val = parse_monetary (value, m_currency_format);
593 num_val += *m_amount_neg;
594 m_amount_neg = num_val;
597 case GncTransPropType::VALUE:
598 num_val = parse_monetary (value, m_currency_format);
604 case GncTransPropType::VALUE_NEG:
605 num_val = parse_monetary (value, m_currency_format);
607 num_val += *m_value_neg;
608 m_value_neg = num_val;
611 case GncTransPropType::TAMOUNT:
612 num_val = parse_monetary (value, m_currency_format);
614 num_val += *m_tamount;
618 case GncTransPropType::TAMOUNT_NEG:
619 num_val = parse_monetary (value, m_currency_format);
621 num_val += *m_tamount_neg;
622 m_tamount_neg = num_val;
627 PWARN (
"%d can't be used to add values in a split",
static_cast<int>(prop_type));
631 catch (
const std::exception& e)
633 auto err_str = (bl::format (std::string{
_(
"{1}: {2}")}) %
634 std::string{
_(gnc_csv_col_type_strs[prop_type])} %
636 m_errors.emplace(prop_type, err_str);
640StrVec GncPreSplit::verify_essentials()
642 auto err_msg = StrVec();
644 if (!m_amount && !m_amount_neg)
645 err_msg.emplace_back (
_(
"No amount or negated amount column."));
647 if (m_rec_state && *m_rec_state ==
YREC && !m_rec_date)
648 err_msg.emplace_back (
_(
"Split is reconciled but reconcile date column is missing or invalid."));
650 if (m_trec_state && *m_trec_state ==
YREC && !m_trec_date)
651 err_msg.emplace_back (
_(
"Transfer split is reconciled but transfer reconcile date column is missing or invalid."));
663 if (m_pre_trans->is_multi_currency())
665 if (m_pre_trans->m_multi_split && !m_price && !m_value && !m_value_neg)
666 err_msg.emplace_back(
_(
"Choice of accounts makes this a multi-currency transaction but price or (negated) value column is missing or invalid."));
667 else if (!m_pre_trans->m_multi_split &&
668 !m_price && !m_value && !m_value_neg && !m_tamount && !m_tamount_neg )
669 err_msg.emplace_back(
_(
"Choice of accounts makes this a multi-currency transaction but price, (negated) value or (negated) transfer amount column is missing or invalid."));
683static void trans_add_split (Transaction* trans,
Account* account,
685 const std::optional<std::string>& action,
686 const std::optional<std::string>& memo,
687 const std::optional<char>& rec_state,
688 const std::optional<GncDate>& rec_date)
692 xaccSplitSetAccount (split, account);
693 xaccSplitSetParent (split, trans);
704 if (rec_state && *rec_state !=
'n')
706 if (rec_state && *rec_state ==
YREC && rec_date)
712void GncPreSplit::create_split (std::shared_ptr<DraftTransaction> draft_trans)
720 auto check = verify_essentials();
723 auto err_msg = std::string(
"Not creating split because essentials not set properly:");
724 auto add_bullet_item = [](std::string& a, std::string& b)->std::string {
return std::move(a) +
"\n• " + b; };
725 err_msg = std::accumulate (check.begin(), check.end(), std::move (err_msg), add_bullet_item);
726 PWARN (
"%s", err_msg.c_str());
730 auto splits_created = 0;
736 account = *m_account;
738 taccount = *m_taccount;
742 amount -= *m_amount_neg;
744 std::optional<GncNumeric> tamount;
745 if (m_tamount || m_tamount_neg)
749 *tamount += *m_tamount;
751 *tamount -= *m_tamount_neg;
762 if (m_value || m_value_neg)
767 value -= *m_value_neg;
774 value = amount * *m_price;
782 acct_comm, trans_curr, time);
783 GncNumeric rate = nprice ? gnc_price_get_value (nprice): gnc_numeric_zero();
789 value = amount * rate;
791 value = amount * rate.
inv();
794 PERR(
"No price found, can't create this split.");
798 trans_add_split (draft_trans->trans, account, amount, value, m_action, m_memo, m_rec_state, m_rec_date);
810 auto tvalue = -value;
818 tamount = tvalue * m_price->inv();
826 acct_comm, trans_curr, time);
827 GncNumeric rate = nprice ? gnc_price_get_value (nprice): gnc_numeric_zero();
833 tamount = tvalue * rate.
inv();
835 tamount = tvalue * rate;
840 trans_add_split (draft_trans->trans, taccount, *tamount, tvalue, m_taction, m_tmemo, m_trec_state, m_trec_date);
844 PWARN(
"No price found, defer creation of second split to generic import matcher.");
847 if (splits_created == 1)
857 draft_trans->m_price = m_price;
858 draft_trans->m_taction = m_taction;
859 draft_trans->m_tmemo = m_tmemo;
860 draft_trans->m_tamount = tamount;
861 draft_trans->m_taccount = m_taccount;
862 draft_trans->m_trec_state = m_trec_state;
863 draft_trans->m_trec_date = m_trec_date;
869ErrMap GncPreSplit::errors (
void)
875void GncPreSplit::set_account (
Account* acct)
882 UpdateCrossSplitCounters();
Account handling public routines.
API for Transactions and Splits (journal entries)
static const std::vector< GncDateFormat > c_formats
A vector with all the date formats supported by the string constructor.
The primary numeric class for representing amounts and values.
GncNumeric inv() const noexcept
bool is_part_of(std::shared_ptr< GncPreTrans > parent)
Check whether the harvested transaction properties for this instance match those of another one (the ...
a simple price database for gnucash
utility functions for the GnuCash UI
gnc_commodity * gnc_account_get_currency_or_parent(const Account *account)
Returns a gnc_commodity that is a currency, suitable for being a Transaction's currency.
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account's commodity
gboolean gnc_commodity_equiv(const gnc_commodity *a, const gnc_commodity *b)
This routine returns TRUE if the two commodities are equivalent.
gnc_commodity_table * gnc_commodity_table_get_table(QofBook *book)
Returns the commodity table associated with a book.
GList * gnc_commodity_table_get_namespaces(const gnc_commodity_table *table)
Return a list of all namespaces in the commodity table.
gboolean gnc_commodity_is_currency(const gnc_commodity *cm)
Checks to see if the specified commodity is an ISO 4217 recognized currency or a legacy currency.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
void xaccTransSetDescription(Transaction *trans, const char *desc)
Sets the transaction Description.
gnc_commodity * xaccTransGetCurrency(const Transaction *trans)
Returns the valuation commodity of this transaction.
void xaccTransSetNum(Transaction *trans, const char *xnum)
Sets the transaction Number (or ID) field; rather than use this function directly,...
time64 xaccTransRetDatePosted(const Transaction *trans)
Retrieve the posted date of the transaction.
Transaction * xaccMallocTransaction(QofBook *book)
The xaccMallocTransaction() will malloc memory and initialize it.
void xaccTransSetDatePostedSecsNormalized(Transaction *trans, time64 time)
This function sets the posted date of the transaction, specified by a time64 (see ctime(3)).
void xaccTransSetCurrency(Transaction *trans, gnc_commodity *curr)
Set a new currency on a transaction.
#define xaccTransGetBook(X)
Account * gnc_account_lookup_by_full_name(const Account *any_acc, const gchar *name)
The gnc_account_lookup_full_name() subroutine works like gnc_account_lookup_by_name,...
void xaccTransBeginEdit(Transaction *trans)
The xaccTransBeginEdit() method must be called before any changes are made to a transaction or any of...
void xaccTransSetNotes(Transaction *trans, const char *notes)
Sets the transaction Notes.
gboolean xaccParseAmountImport(const char *in_str, gboolean monetary, gnc_numeric *result, char **endstr, gboolean skip)
Similar to xaccParseAmount, but with two differences.
gboolean xaccParseAmountExtImport(const char *in_str, gboolean monetary, gunichar negative_sign, gunichar decimal_point, gunichar group_separator, const char *ignore_list, gnc_numeric *result, char **endstr)
Similar to xaccParseAmountExtended, but will not automatically set a decimal point,...
#define PERR(format, args...)
Log a serious error.
#define PWARN(format, args...)
Log a warning.
gboolean gnc_numeric_zero_p(gnc_numeric a)
Returns 1 if the given gnc_numeric is 0 (zero), else returns 0.
GNCPrice * gnc_pricedb_lookup_nearest_in_time64(GNCPriceDB *db, const gnc_commodity *c, const gnc_commodity *currency, time64 t)
Return the price between the two commoditiesz nearest to the given time.
GNCPriceDB * gnc_pricedb_get_db(QofBook *book)
Return the pricedb associated with the book.
void xaccSplitSetReconcile(Split *split, char recn)
Set the reconcile flag.
void xaccSplitSetMemo(Split *split, const char *memo)
The memo is an arbitrary string associated with a split.
void xaccSplitSetValue(Split *split, gnc_numeric val)
The xaccSplitSetValue() method sets the value of this split in the transaction's commodity.
Split * xaccMallocSplit(QofBook *book)
Constructor.
void xaccSplitSetAmount(Split *split, gnc_numeric amt)
The xaccSplitSetAmount() method sets the amount in the account's commodity that the split should have...
#define VREC
split is void
void xaccSplitSetDateReconciledSecs(Split *split, time64 secs)
Set the date on which this split was reconciled by specifying the time as time64.
#define NREC
not reconciled or cleared
#define FREC
frozen into accounting period
#define CREC
The Split has been cleared
#define YREC
The Split has been reconciled.
void xaccSplitSetAction(Split *split, const char *actn)
The Action is an arbitrary user-assigned string.