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;
77 using SplitInfoVec = std::vector<SplitInfo>;
78 using SplitVec = std::vector<Split*>;
82 const char* abort =
nullptr;
83 gint abort_id = Autoclear::ABORT_NONE;
88 path_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);
112 subset_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);
172 gnc_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
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);
Never round at all, and signal an error if there is a fractional result in a computation.
time64 xaccTransGetDate(const Transaction *trans)
Retrieve the posted date of the transaction.
int xaccAccountGetCommoditySCU(const Account *acc)
Return the SCU for the account.
#define DEBUG(format, args...)
Print a debugging message.
char xaccSplitGetReconcile(const Split *split)
Returns the value of the reconcile flag.
Transaction * xaccSplitGetParent(const Split *split)
Returns the parent transaction of the split.
API for Transactions and Splits (journal entries)
Use any denominator which gives an exactly correct ratio of numerator to denominator.
Account handling public routines.
gnc_numeric gnc_numeric_convert(gnc_numeric n, gint64 denom, gint how)
Change the denominator of a gnc_numeric value to the specified denominator under standard arguments '...
Account public routines (C++ api)
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
API for Transactions and Splits (journal entries)
#define NREC
not reconciled or cleared
gnc_numeric xaccSplitGetAmount(const Split *split)
Returns the amount of the split in the account's commodity.