27#include <glib/gi18n.h>
33#include "gnc-autoclear.h"
46#define log_module "gnc.autoclear"
50 uint64_t m_counter = 0;
51 std::optional<double> m_seconds;
52 std::chrono::steady_clock::time_point m_start;
53 RuntimeMonitor (
double seconds) : m_start(std::chrono::steady_clock::now())
55 if (seconds > 0) m_seconds = seconds;
59 return std::chrono::duration_cast<std::chrono::duration<double>>(std::chrono::steady_clock::now() - m_start).count();
63 if (!m_seconds.has_value())
return false;
64 if (++m_counter % 100000 != 0)
return false;
65 return get_elapsed() > *m_seconds;
77using SplitInfoVec = std::vector<SplitInfo>;
78using SplitVec = std::vector<Split*>;
82 const char* abort =
nullptr;
83 gint abort_id = Autoclear::ABORT_NONE;
88path_to_str(
const SplitInfoVec& path)
90 if (path.empty())
return "<empty>";
92 static char buff[1000];
94 char* end = buff +
sizeof(buff);
95 for (
const auto& split_info : path)
99 if (p < end) *p++ =
'|';
100 else return "<overflow>";
102 auto res = std::to_chars (p, end, split_info.m_amount);
103 if (res.ec == std::errc()) p = res.ptr;
104 else return "<overflow>";
106 p = std::min (end - 1, p);
112subset_sum (SplitInfoVec::const_iterator iter,
113 SplitInfoVec::const_iterator end,
114 SplitInfoVec& path,
Solution& solution,
117 DEBUG (
"this=%" PRId64
" target=%" PRId64
" rem_pos=%" PRId64
118 " rem_neg=%" PRId64
" path=%s",
119 iter == end ? 0 : iter->m_amount, target,
120 iter == end ? 0 : iter->m_rem_pos,
121 iter == end ? 0 : iter->m_rem_neg, path_to_str (path));
125 DEBUG (
"SOLUTION FOUND: %s%s", path_to_str (path),
126 solution.splits.empty() ?
"" :
" ABORT: AMBIGUOUS");
127 if (!solution.splits.empty())
129 solution.abort_id = Autoclear::ABORT_MULTI;
130 solution.abort = N_(
"Cannot uniquely clear splits. Found multiple possibilities.");
135 solution.splits.resize (path.size());
136 std::transform (path.begin(), path.end(), solution.splits.begin(),
142 if (solution.abort_id != Autoclear::ABORT_NONE || iter == end)
145 if (monitor.should_abort())
147 DEBUG (
"ABORT: timeout");
148 solution.abort_id = Autoclear::ABORT_TIMEOUT;
149 solution.abort = N_(
"Auto-clear exceeds allocated time");
153 if (target < iter->m_rem_neg || target > iter->m_rem_pos)
155 DEBUG (
"PRUNE target=%" PRId64
" rem_pos=%" PRId64
" rem_neg=%" PRId64,
156 target, iter->m_rem_pos, iter->m_rem_neg);
160 auto next = std::next(iter);
163 path.push_back (*iter);
164 subset_sum (next, end, path, solution, target - iter->m_amount, monitor);
168 subset_sum (next, end, path, solution, target, monitor);
172gnc_account_get_autoclear_splits (
Account *account, gnc_numeric toclear_value,
173 time64 end_date, GError **error,
176 g_return_val_if_fail (account && error,
nullptr);
179 auto numeric_to_int64 = [scu](gnc_numeric num) -> int64_t
181 return gnc_numeric_num
183 (num, scu, GNC_HOW_DENOM_EXACT | GNC_HOW_RND_NEVER));
186 int64_t target{numeric_to_int64 (toclear_value)};
188 for (
auto split : xaccAccountGetSplits (account))
196 DEBUG (
"skipping zero-amount split %p", split);
198 splits.push_back ({amt, 0, 0, split});
201 static GQuark autoclear_quark = g_quark_from_static_string (
"autoclear");
204 g_set_error (error, autoclear_quark, Autoclear::ABORT_NOP,
"%s",
205 N_(
"Account is already at Auto-Clear Balance."));
210 std::sort (splits.begin(), splits.end(),
213 int64_t aa = std::llabs(a.m_amount);
214 int64_t bb = std::llabs(b.m_amount);
215 return (aa == bb) ? a.m_amount > b.m_amount : aa > bb;
219 int64_t rem_pos{0}, rem_neg{0};
220 std::for_each (splits.rbegin(), splits.rend(),
223 s.m_rem_pos = rem_pos += std::max<int64_t>(s.m_amount, 0);
224 s.m_rem_neg = rem_neg += std::min<int64_t>(s.m_amount, 0);
230 path.reserve (splits.size());
232 subset_sum (splits.begin(), splits.end(), path, solution, target, monitor);
234 DEBUG (
"finished subset_sum in %f seconds", monitor.get_elapsed());
236 if (solution.splits.empty())
238 g_set_error (error, autoclear_quark, Autoclear::ABORT_UNREACHABLE,
"%s",
239 N_(
"The selected amount cannot be cleared."));
242 else if (solution.abort_id)
244 g_set_error (error, autoclear_quark,
245 solution.abort_id,
"%s", solution.abort);
249 return std::accumulate
250 (solution.splits.begin(), solution.splits.end(),
251 static_cast<GList*
>(
nullptr), g_list_prepend);
Account handling public routines.
Account public routines (C++ api)
API for Transactions and Splits (journal entries)
API for Transactions and Splits (journal entries)
int xaccAccountGetCommoditySCU(const Account *acc)
Return the SCU for the account.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
time64 xaccTransGetDate(const Transaction *trans)
Retrieve the posted date of the transaction.
#define DEBUG(format, args...)
Print a debugging message.
Transaction * xaccSplitGetParent(const Split *split)
Returns the parent transaction of the split.
char xaccSplitGetReconcile(const Split *split)
Returns the value of the reconcile flag.
#define NREC
not reconciled or cleared
gnc_numeric xaccSplitGetAmount(const Split *split)
Returns the amount of the split in the account's commodity.