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"    51 namespace bl = boost::locale;
    53 G_GNUC_UNUSED 
static QofLogModule log_module = GNC_MOD_IMPORT;
    56 std::map<GncTransPropType, const char*> gnc_csv_col_type_strs = {
    57         { GncTransPropType::NONE, N_(
"None") },
    58         { GncTransPropType::UNIQUE_ID, N_(
"Transaction ID") },
    59         { GncTransPropType::DATE, N_(
"Date") },
    60         { GncTransPropType::NUM, N_(
"Number") },
    61         { GncTransPropType::DESCRIPTION, N_(
"Description") },
    62         { GncTransPropType::NOTES, N_(
"Notes") },
    63         { GncTransPropType::COMMODITY, N_(
"Transaction Commodity") },
    64         { GncTransPropType::VOID_REASON, N_(
"Void Reason") },
    65         { GncTransPropType::ACTION, N_(
"Action") },
    66         { GncTransPropType::ACCOUNT, N_(
"Account") },
    67         { GncTransPropType::AMOUNT, N_(
"Amount") },
    68         { GncTransPropType::AMOUNT_NEG, N_(
"Amount (Negated)") },
    69         { GncTransPropType::VALUE, N_(
"Value") },
    70         { GncTransPropType::VALUE_NEG, N_(
"Value (Negated)") },
    71         { GncTransPropType::PRICE, N_(
"Price") },
    72         { GncTransPropType::MEMO, N_(
"Memo") },
    73         { GncTransPropType::REC_STATE, N_(
"Reconciled") },
    74         { GncTransPropType::REC_DATE, N_(
"Reconcile Date") },
    75         { GncTransPropType::TACTION, N_(
"Transfer Action") },
    76         { GncTransPropType::TACCOUNT, N_(
"Transfer Account") },
    77         { GncTransPropType::TAMOUNT, N_(
"Transfer Amount") },
    78         { GncTransPropType::TAMOUNT_NEG, N_(
"Transfer Amount (Negated)") },
    79         { GncTransPropType::TMEMO, N_(
"Transfer Memo") },
    80         { GncTransPropType::TREC_STATE, N_(
"Transfer Reconciled") },
    81         { GncTransPropType::TREC_DATE, N_(
"Transfer Reconcile Date") }
    88 std::vector<GncTransPropType> twosplit_blacklist = {
    89         GncTransPropType::UNIQUE_ID };
    90 std::vector<GncTransPropType> multisplit_blacklist = {
    91         GncTransPropType::TACTION,
    92         GncTransPropType::TACCOUNT,
    93         GncTransPropType::TAMOUNT,
    94         GncTransPropType::TAMOUNT_NEG,
    95         GncTransPropType::TMEMO,
    96         GncTransPropType::TREC_STATE,
    97         GncTransPropType::TREC_DATE
   100 std::vector<GncTransPropType> multi_col_props = {
   101     GncTransPropType::AMOUNT,
   102     GncTransPropType::AMOUNT_NEG,
   103     GncTransPropType::TAMOUNT,
   104     GncTransPropType::TAMOUNT_NEG,
   105     GncTransPropType::VALUE,
   106     GncTransPropType::VALUE_NEG
   109 bool is_multi_col_prop (GncTransPropType prop)
   111     return (std::find (multi_col_props.cbegin(),
   112                        multi_col_props.cend(), prop) != multi_col_props.cend());
   115 GncTransPropType sanitize_trans_prop (GncTransPropType prop, 
bool multi_split)
   117     auto bl = multi_split ? multisplit_blacklist : twosplit_blacklist;
   118     if (std::find(bl.begin(), bl.end(), prop) == bl.end())
   121         return GncTransPropType::NONE;
   131 GncNumeric parse_monetary (
const std::string &str, 
int currency_format)
   138     if(!boost::regex_search(str, boost::regex(
"[0-9]")))
   139         throw std::invalid_argument (_(
"Value doesn't appear to contain a valid number."));
   141     auto expr = boost::make_u32regex(
"[[:Sc:][:blank:]]|--");
   142     std::string str_no_symbols = boost::u32regex_replace(str, expr, 
"");
   145     gnc_numeric val = gnc_numeric_zero();
   147     switch (currency_format)
   152             throw std::invalid_argument (_(
"Value can't be parsed into a number using the selected currency format."));
   157             throw std::invalid_argument (_(
"Value can't be parsed into a number using the selected currency format."));
   162             throw std::invalid_argument (_(
"Value can't be parsed into a number using the selected currency format."));
   169 static char parse_reconciled (
const std::string& reconcile)
   171     if (g_strcmp0 (reconcile.c_str(), gnc_get_reconcile_str(
NREC)) == 0) 
   173     else if (g_strcmp0 (reconcile.c_str(), gnc_get_reconcile_str(
CREC)) == 0) 
   175     else if (g_strcmp0 (reconcile.c_str(), gnc_get_reconcile_str(
YREC)) == 0) 
   177     else if (g_strcmp0 (reconcile.c_str(), gnc_get_reconcile_str(
FREC)) == 0) 
   179     else if (g_strcmp0 (reconcile.c_str(), gnc_get_reconcile_str(
VREC)) == 0) 
   182         throw std::invalid_argument (_(
"Value can't be parsed into a valid reconcile state."));
   185 gnc_commodity* parse_commodity (
const std::string& comm_str)
   187     if (comm_str.empty())
   191     gnc_commodity* comm = 
nullptr;
   194     comm = gnc_commodity_table_lookup_unique (
table, comm_str.c_str());
   198         comm = gnc_commodity_table_lookup (
table,
   199                 GNC_COMMODITY_NS_CURRENCY, comm_str.c_str());
   205         for (
auto ns = namespaces; ns; ns = ns->next)
   207             gchar* ns_str = (gchar*)ns->data;
   208             if (g_utf8_collate(ns_str, GNC_COMMODITY_NS_CURRENCY) == 0)
   211             comm = gnc_commodity_table_lookup (
table,
   212                     ns_str, comm_str.c_str());
   216         g_list_free (namespaces);
   220         throw std::invalid_argument (_(
"Value can't be parsed into a valid commodity."));
   225 void GncPreTrans::set (GncTransPropType prop_type, 
const std::string& value)
   230         m_errors.erase(prop_type);
   234             case GncTransPropType::UNIQUE_ID:
   240             case GncTransPropType::DATE:
   244                 else if (!m_multi_split)
   245                     throw std::invalid_argument (
   246                         (bl::format (std::string{_(
"Date field can not be empty if 'Multi-split' option is unset.\n")}) %
   247                                      std::string{_(gnc_csv_col_type_strs[prop_type])}).str());
   250             case GncTransPropType::NUM:
   256             case GncTransPropType::DESCRIPTION:
   260                 else if (!m_multi_split)
   261                     throw std::invalid_argument (
   262                         (bl::format (std::string{_(
"Description field can not be empty if 'Multi-split' option is unset.\n")}) %
   263                                      std::string{_(gnc_csv_col_type_strs[prop_type])}).str());
   266             case GncTransPropType::NOTES:
   272             case GncTransPropType::COMMODITY:
   273                 m_currency = 
nullptr;
   274                 m_currency = parse_commodity (value);
   277             case GncTransPropType::VOID_REASON:
   278                 m_void_reason.reset();
   280                     m_void_reason = value;
   285                 PWARN (
"%d is an invalid property for a transaction", static_cast<int>(prop_type));
   289     catch (
const std::exception& e)
   291         auto err_str = (bl::format (std::string{_(
"{1}: {2}")}) %
   292                         std::string{_(gnc_csv_col_type_strs[prop_type])} %
   294         m_errors.emplace(prop_type, err_str);
   299 void GncPreTrans::reset (GncTransPropType prop_type)
   301         set (prop_type, std::string());
   304         m_errors.erase(prop_type);
   307 StrVec GncPreTrans::verify_essentials (
void)
   309     auto errors = StrVec();
   312         errors.emplace_back(_(
"No valid date."));
   315         errors.emplace_back(_(
"No valid description."));
   320 std::shared_ptr<DraftTransaction> GncPreTrans::create_trans (QofBook* book, gnc_commodity* currency)
   328     auto check = verify_essentials();
   331         auto err_msg = std::string(
"Not creating transaction because essentials not set properly:");
   332         auto add_bullet_item = [](std::string& a, std::string& b)->std::string { 
return std::move(a) + 
"\n• " + b; };
   333         err_msg = std::accumulate (check.begin(), check.end(), std::move (err_msg), add_bullet_item);
   334         PWARN (
"%s", err_msg.c_str());
   346                         static_cast<time64>(
GncDateTime(*m_date, DayPart::neutral)));
   358     return std::make_shared<DraftTransaction>(trans);
   366     return (!m_differ || m_differ == parent->m_differ) &&
   367             (!m_date || m_date == parent->m_date) &&
   368             (!m_num || m_num == parent->m_num) &&
   369             (!m_desc || m_desc == parent->m_desc) &&
   370             (!m_notes || m_notes == parent->m_notes) &&
   371             (!m_currency || m_currency == parent->m_currency) &&
   372             (!m_void_reason || m_void_reason == parent->m_void_reason) &&
   373             parent->m_errors.empty(); 
   376 ErrMap GncPreTrans::errors ()
   381 void GncPreTrans::reset_cross_split_counters()
   383     m_alt_currencies.clear();
   384     m_acct_commodities.clear();
   388 bool GncPreTrans::is_multi_currency()
   390     auto num_comm = m_acct_commodities.size() + m_alt_currencies.size();
   391     if (m_currency && (std::find (m_alt_currencies.cbegin(),m_alt_currencies.cend(), m_currency) == m_alt_currencies.cend()))
   393     return (num_comm > 1);
   397 void GncPreSplit::UpdateCrossSplitCounters ()
   399     if (m_account && *m_account)
   401         auto acct = *m_account;
   403         auto alt_currs = m_pre_trans->m_alt_currencies;
   404         auto acct_comms = m_pre_trans->m_acct_commodities;
   405         auto curr = 
static_cast<gnc_commodity*
> (
nullptr);
   414         auto has_curr = [curr] (
const gnc_commodity *vec_curr) { 
return gnc_commodity_equiv (curr, vec_curr); };
   415         if (curr && std::none_of (alt_currs.cbegin(), alt_currs.cend(), has_curr))
   416             m_pre_trans->m_alt_currencies.push_back(curr);
   417         auto has_comm = [comm] (
const gnc_commodity *vec_comm) { 
return gnc_commodity_equiv (comm, vec_comm); };
   418         if (comm && std::none_of (acct_comms.cbegin(), acct_comms.cend(), has_comm))
   419             m_pre_trans->m_acct_commodities.push_back(comm);
   423 void GncPreSplit::set (GncTransPropType prop_type, 
const std::string& value)
   428         m_errors.erase(prop_type);
   433             case GncTransPropType::ACTION:
   439             case GncTransPropType::TACTION:
   445             case GncTransPropType::ACCOUNT:
   448                     throw std::invalid_argument (_(
"Account value can't be empty."));
   449                 if ((acct = gnc_account_imap_find_any (gnc_get_current_book(), IMAP_CAT_CSV, value.c_str())) ||
   453                     throw std::invalid_argument (_(
"Account value can't be mapped back to an account."));
   456             case GncTransPropType::TACCOUNT:
   459                     throw std::invalid_argument (_(
"Transfer account value can't be empty."));
   461                 if ((acct = gnc_account_imap_find_any (gnc_get_current_book(), IMAP_CAT_CSV,value.c_str())) ||
   465                     throw std::invalid_argument (_(
"Transfer account value can't be mapped back to an account."));
   468             case GncTransPropType::MEMO:
   474             case GncTransPropType::TMEMO:
   480             case GncTransPropType::AMOUNT:
   482                 m_amount = parse_monetary (value, m_currency_format); 
   485             case GncTransPropType::AMOUNT_NEG:
   486                 m_amount_neg.reset();
   487                 m_amount_neg = parse_monetary (value, m_currency_format); 
   490             case GncTransPropType::VALUE:
   492                 m_value = parse_monetary (value, m_currency_format); 
   495             case GncTransPropType::VALUE_NEG:
   497                 m_value_neg = parse_monetary (value, m_currency_format); 
   500             case GncTransPropType::TAMOUNT:
   502                 m_tamount = parse_monetary (value, m_currency_format); 
   505             case GncTransPropType::TAMOUNT_NEG:
   506                 m_tamount_neg.reset();
   507                 m_tamount_neg = parse_monetary (value, m_currency_format); 
   510             case GncTransPropType::PRICE:
   515                 m_price = parse_monetary (value, m_currency_format); 
   518             case GncTransPropType::REC_STATE:
   520                 m_rec_state = parse_reconciled (value); 
   523             case GncTransPropType::TREC_STATE:
   524                 m_trec_state.reset();
   525                 m_trec_state = parse_reconciled (value); 
   528             case GncTransPropType::REC_DATE:
   535             case GncTransPropType::TREC_DATE:
   544                 PWARN (
"%d is an invalid property for a split", static_cast<int>(prop_type));
   548     catch (
const std::exception& e)
   550         auto err_str = (bl::format (std::string{_(
"{1}: {2}")}) %
   551                         std::string{_(gnc_csv_col_type_strs[prop_type])} %
   553         m_errors.emplace(prop_type, err_str);
   557     if (prop_type == GncTransPropType::ACCOUNT)
   558         UpdateCrossSplitCounters();
   561 void GncPreSplit::reset (GncTransPropType prop_type)
   563         set (prop_type, std::string());
   566         m_errors.erase(prop_type);
   569 void GncPreSplit::add (GncTransPropType prop_type, 
const std::string& value)
   574         if (m_errors.find(prop_type) != m_errors.cend())
   580             case GncTransPropType::AMOUNT:
   581                 num_val = parse_monetary (value, m_currency_format); 
   583                     num_val += *m_amount;
   587             case GncTransPropType::AMOUNT_NEG:
   588                 num_val = parse_monetary (value, m_currency_format); 
   590                     num_val += *m_amount_neg;
   591                 m_amount_neg = num_val;
   594             case GncTransPropType::VALUE:
   595                 num_val = parse_monetary (value, m_currency_format); 
   601             case GncTransPropType::VALUE_NEG:
   602                 num_val = parse_monetary (value, m_currency_format); 
   604                     num_val += *m_value_neg;
   605             m_value_neg = num_val;
   608             case GncTransPropType::TAMOUNT:
   609                 num_val = parse_monetary (value, m_currency_format); 
   611                     num_val += *m_tamount;
   615             case GncTransPropType::TAMOUNT_NEG:
   616                 num_val = parse_monetary (value, m_currency_format); 
   618                     num_val += *m_tamount_neg;
   619                 m_tamount_neg = num_val;
   624                 PWARN (
"%d can't be used to add values in a split", static_cast<int>(prop_type));
   628     catch (
const std::exception& e)
   630         auto err_str = (bl::format (std::string{_(
"{1}: {2}")}) %
   631                         std::string{_(gnc_csv_col_type_strs[prop_type])} %
   633         m_errors.emplace(prop_type, err_str);
   637 StrVec GncPreSplit::verify_essentials()
   639     auto err_msg = StrVec();
   641     if (!m_amount && !m_amount_neg)
   642         err_msg.emplace_back (_(
"No amount or negated amount column."));
   644     if (m_rec_state && *m_rec_state == 
YREC && !m_rec_date)
   645         err_msg.emplace_back (_(
"Split is reconciled but reconcile date column is missing or invalid."));
   647     if (m_trec_state && *m_trec_state == 
YREC && !m_trec_date)
   648         err_msg.emplace_back (_(
"Transfer split is reconciled but transfer reconcile date column is missing or invalid."));
   660     if (m_pre_trans->is_multi_currency())
   662         if (m_pre_trans->m_multi_split && !m_price && !m_value && !m_value_neg)
   663             err_msg.emplace_back( _(
"Choice of accounts makes this a multi-currency transaction but price or (negated) value column is missing or invalid."));
   664         else if (!m_pre_trans->m_multi_split &&
   665             !m_price && !m_value && !m_value_neg && !m_tamount && !m_tamount_neg )
   666             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."));
   680 static void trans_add_split (Transaction* trans, 
Account* account,
   682                             const std::optional<std::string>& action,
   683                             const std::optional<std::string>& memo,
   684                             const std::optional<char>& rec_state,
   685                             const std::optional<GncDate>& rec_date)
   689     xaccSplitSetAccount (split, account);
   690     xaccSplitSetParent (split, trans);
   701     if (rec_state && *rec_state != 
'n')
   703     if (rec_state && *rec_state == 
YREC && rec_date)
   705                 static_cast<time64>(
GncDateTime(*rec_date, DayPart::neutral)));
   709 void GncPreSplit::create_split (std::shared_ptr<DraftTransaction> draft_trans)
   717     auto check = verify_essentials();
   720         auto err_msg = std::string(
"Not creating split because essentials not set properly:");
   721         auto add_bullet_item = [](std::string& a, std::string& b)->std::string { 
return std::move(a) + 
"\n• " + b; };
   722         err_msg = std::accumulate (check.begin(), check.end(), std::move (err_msg), add_bullet_item);
   723         PWARN (
"%s", err_msg.c_str());
   727     auto splits_created = 0;
   733         account = *m_account;
   735         taccount = *m_taccount;
   739         amount -= *m_amount_neg;
   741     std::optional<GncNumeric> tamount;
   742     if (m_tamount || m_tamount_neg)
   746             *tamount += *m_tamount;
   748             *tamount -= *m_tamount_neg;
   759     if (m_value || m_value_neg)
   764             value -= *m_value_neg;
   771         value = amount * *m_price;
   779                                              acct_comm, trans_curr, time);
   780         GncNumeric rate = nprice ? gnc_price_get_value (nprice): gnc_numeric_zero();
   786                 value = amount * rate;
   788                 value = amount * rate.
inv();
   791             PERR(
"No price found, can't create this split.");
   795     trans_add_split (draft_trans->trans, account, amount, value, m_action, m_memo, m_rec_state, m_rec_date);
   807         auto tvalue = -value;
   815             tamount = tvalue * m_price->inv();
   823                                                     acct_comm, trans_curr, time);
   824             GncNumeric rate = nprice ? gnc_price_get_value (nprice): gnc_numeric_zero();
   830                     tamount = tvalue * rate.
inv();
   832                     tamount = tvalue * rate;
   837             trans_add_split (draft_trans->trans, taccount, *tamount, tvalue, m_taction, m_tmemo, m_trec_state, m_trec_date);
   841                 PWARN(
"No price found, defer creation of second split to generic import matcher.");
   844     if (splits_created == 1)
   854         draft_trans->m_price = m_price;
   855         draft_trans->m_taction = m_taction;
   856         draft_trans->m_tmemo = m_tmemo;
   857         draft_trans->m_tamount = tamount;
   858         draft_trans->m_taccount = m_taccount;
   859         draft_trans->m_trec_state = m_trec_state;
   860         draft_trans->m_trec_date = m_trec_date;
   866 ErrMap GncPreSplit::errors (
void)
   872 void GncPreSplit::set_account (
Account* acct)
   879     UpdateCrossSplitCounters();
 void xaccSplitSetValue(Split *split, gnc_numeric val)
The xaccSplitSetValue() method sets the value of this split in the transaction's commodity. 
 
gnc_commodity_table * gnc_commodity_table_get_table(QofBook *book)
Returns the commodity table associated with a book. 
 
Transaction * xaccMallocTransaction(QofBook *book)
 The xaccMallocTransaction() will malloc memory and initialize it. 
 
gboolean xaccParseAmountImport(const char *in_str, gboolean monetary, gnc_numeric *result, char **endstr, gboolean skip)
Similar to xaccParseAmount, but with two differences. 
 
void xaccTransSetDatePostedSecsNormalized(Transaction *trans, time64 time)
This function sets the posted date of the transaction, specified by a time64 (see ctime(3))...
 
void xaccSplitSetAction(Split *split, const char *actn)
The Action is an arbitrary user-assigned string. 
 
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...
 
a simple price database for gnucash 
 
utility functions for the GnuCash UI 
 
void xaccTransSetNotes(Transaction *trans, const char *notes)
Sets the transaction Notes. 
 
void xaccTransSetDescription(Transaction *trans, const char *desc)
Sets the transaction Description. 
 
void xaccTransSetNum(Transaction *trans, const char *xnum)
Sets the transaction Number (or ID) field; rather than use this function directly, see 'gnc_set_num_action' in engine/engine-helpers.c & .h which takes a user-set book option for selecting the source for the num-cell (the transaction-number or the split-action field) in registers/reports into account automatically. 
 
gboolean gnc_numeric_zero_p(gnc_numeric a)
Returns 1 if the given gnc_numeric is 0 (zero), else returns 0. 
 
The primary numeric class for representing amounts and values. 
 
void xaccSplitSetReconcile(Split *split, char recn)
Set the reconcile flag. 
 
#define PERR(format, args...)
Log a serious error. 
 
GNCPriceDB * gnc_pricedb_get_db(QofBook *book)
Return the pricedb associated with the book. 
 
#define VREC
split is void 
 
void xaccTransSetCurrency(Transaction *trans, gnc_commodity *curr)
Set a new currency on a transaction. 
 
#define PWARN(format, args...)
Log a warning. 
 
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, regardless of what the user has set for this option. 
 
void xaccSplitSetAmount(Split *split, gnc_numeric amt)
The xaccSplitSetAmount() method sets the amount in the account's commodity that the split should have...
 
Account handling public routines. 
 
#define YREC
The Split has been reconciled. 
 
void xaccSplitSetMemo(Split *split, const char *memo)
The memo is an arbitrary string associated with a split. 
 
GList * gnc_commodity_table_get_namespaces(const gnc_commodity_table *table)
Return a list of all namespaces in the commodity table. 
 
#define FREC
frozen into accounting period 
 
time64 xaccTransRetDatePosted(const Transaction *trans)
Retrieve the posted date of the transaction. 
 
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, but uses fully-qualified names using the given separator. 
 
#define xaccTransGetBook(X)
 
void xaccTransBeginEdit(Transaction *trans)
The xaccTransBeginEdit() method must be called before any changes are made to a transaction or any of...
 
#define CREC
The Split has been cleared. 
 
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. 
 
Split * xaccMallocSplit(QofBook *book)
Constructor. 
 
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. 
 
void xaccSplitSetDateReconciledSecs(Split *split, time64 secs)
Set the date on which this split was reconciled by specifying the time as time64. ...
 
bool is_part_of(std::shared_ptr< GncPreTrans > parent)
Check whether the harvested transaction properties for this instance match those of another one (the ...
 
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account's commodity. 
 
gnc_commodity * xaccTransGetCurrency(const Transaction *trans)
Returns the valuation commodity of this transaction. 
 
GncNumeric inv() const noexcept
 
API for Transactions and Splits (journal entries) 
 
gboolean gnc_commodity_equiv(const gnc_commodity *a, const gnc_commodity *b)
This routine returns TRUE if the two commodities are equivalent. 
 
static const std::vector< GncDateFormat > c_formats
A vector with all the date formats supported by the string constructor. 
 
#define NREC
not reconciled or cleared