GnuCash 038f90a+
Loading...
Searching...
No Matches
assistant-stock-transaction.cpp
1/********************************************************************\
2 * assistant-stock-transaction.cpp -- stock assistant for GnuCash *
3 * Copyright (C) 2022 Christopher Lam *
4 * *
5 * This program is free software; you can redistribute it and/or *
6 * modify it under the terms of the GNU General Public License as *
7 * published by the Free Software Foundation; either version 2 of *
8 * the License, or (at your option) any later version. *
9 * *
10 * This program is distributed in the hope that it will be useful, *
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of *
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the *
13 * GNU General Public License for more details. *
14 * *
15 * You should have received a copy of the GNU General Public License*
16 * along with this program; if not, contact: *
17 * *
18 * Free Software Foundation Voice: +1-617-542-5942 *
19 * 51 Franklin Street, Fifth Floor Fax: +1-617-542-2652 *
20 * Boston, MA 02110-1301, USA gnu@gnu.org *
21\********************************************************************/
22
23#include <config.h>
24
25#include <cstddef>
26#include <exception>
27#include <gtk/gtk.h>
28#include <glib/gi18n.h>
29#include <cinttypes>
30#include <memory>
31#include <vector>
32#include <string>
33#include <numeric>
34#include <algorithm>
35#include <optional>
36#include <stdexcept>
37#include <sstream>
38
39#include "Account.h"
40#include "Account.hpp"
41#include "Transaction.h"
42#include "engine-helpers.h"
43#include "dialog-utils.h"
44#include "assistant-stock-transaction.h"
45#include "gnc-account-sel.h"
46#include "gnc-amount-edit.h"
47#include "gnc-date.h"
48#include <gnc-date-edit.h>
49#include "gnc-engine.h"
50#include "gnc-numeric.h"
51#include "gnc-numeric.hpp"
52#include "gnc-prefs.h"
53#include "gnc-component-manager.h"
54#include "gnc-date-edit.h"
56#include "gnc-ui-util.h"
57#include "gnc-main-window.h"
59#include "gnc-split-reg.h"
60
61static QofLogModule log_module = GNC_MOD_ASSISTANT;
62
81extern "C"
82{
83// These functions are the GtkAssistant primary button callbacks. They're
84// connected to their signals in assistant-stock-transaction.glade so they
85// mustn't be name-mangled.
86void stock_assistant_prepare_cb (GtkAssistant *assistant, GtkWidget *page,
87 gpointer user_data);
88void stock_assistant_finish_cb (GtkAssistant *assistant, gpointer user_data);
89void stock_assistant_cancel_cb (GtkAssistant *gtkassistant, gpointer user_data);
90}
91
92static const char* GNC_PREFS_GROUP = "dialogs.stock-assistant";
93static const char* ASSISTANT_STOCK_TRANSACTION_CM_CLASS = "assistant-stock-transaction";
94
95static const char* DIVIDEND_KVP_TAG = "stock-dividends";
96static const char* CAPGAINS_KVP_TAG = "stock-capgains";
97static const char* PROCEEDS_KVP_TAG = "stock-cash-proceeds";
98static const char* FEES_KVP_TAG = "stock-broker-fees";
99
102enum class FieldMask : unsigned
103{
104 DISABLED = 0,
105 ENABLED_DEBIT = 1,
106 ENABLED_CREDIT = 1 << 1,
107 AMOUNT_DEBIT = 1 << 2, // stock only
108 AMOUNT_CREDIT = 1 << 3, // stock only
109 INPUT_NEW_BALANCE = 1 << 4, // stock_amt only: instead of amount, get new balance
110 ALLOW_ZERO = 1 << 5,
111 ALLOW_NEGATIVE = 1 << 6,
112 CAPITALIZE_DEFAULT = 1 << 7, // fees only: capitalize by default into stock acct
113 CAPGAINS_IN_STOCK = 1 << 8, // capg only: add a balancing split in stock acct
114 MARKER_SPLIT = 1 << 9, // stock only, place a no-amount, no-value split in the
115 // stock account to associate the income.
116};
117
118static FieldMask
119operator |(FieldMask lhs, FieldMask rhs)
120{
121 return static_cast<FieldMask> (static_cast<unsigned>(lhs) |
122 static_cast<unsigned>(rhs));
123};
124
125static bool
126operator &(FieldMask lhs, FieldMask rhs)
127{
128 return (static_cast<unsigned>(lhs) & static_cast<unsigned>(rhs));
129};
130
138{
139 FieldMask stock_amount;
140 FieldMask cash_value;
141 FieldMask fees_value;
142 FieldMask dividend_value;
143 FieldMask capgains_value;
144 const char* friendly_name;
145 const char* explanation;
146};
147
148using StringVec = std::vector<std::string>;
149using TxnTypeVec = std::vector<TxnTypeInfo>;
150using AccountVec = std::vector<Account*>;
151
152static const TxnTypeVec starting_types
153{
154
155 {
156 FieldMask::ENABLED_DEBIT | FieldMask::AMOUNT_DEBIT, // stock_amt
157 FieldMask::ENABLED_CREDIT, // cash_amt
158 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT, // fees_amt
159 FieldMask::DISABLED, // dividend_amt
160 FieldMask::DISABLED, // capg_amt
161 // Translators: this is a stock transaction describing an
162 // Initial stock long purchase
163 N_("Open buy"),
164 N_("Initial stock long purchase.")
165 },
166 {
167 FieldMask::ENABLED_CREDIT | FieldMask::AMOUNT_CREDIT, // stock_amt
168 FieldMask::ENABLED_DEBIT, // cash_amt
169 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT, // fees_amt
170 FieldMask::DISABLED, // dividend_amt
171 FieldMask::DISABLED, // capg_amt
172 // Translators: this is a stock transaction describing an
173 // initial stock short sale
174 N_("Open short"),
175 N_("Initial stock short sale.")
176 }
177};
178
179static const TxnTypeVec long_types
180{
181 {
182 FieldMask::ENABLED_DEBIT | FieldMask::AMOUNT_DEBIT, // stock_amt
183 FieldMask::ENABLED_CREDIT, // cash_amt
184 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT, // fees_amt
185 FieldMask::DISABLED, // dividend_amt
186 FieldMask::DISABLED, // capg_amt
187 // Translators: this is a stock transaction describing
188 // new purchase of stock.
189 N_("Buy"),
190 N_("Buying stock long.")
191 },
192 {
193 FieldMask::ENABLED_CREDIT | FieldMask::AMOUNT_CREDIT, // stock_amt
194 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO, // cash_amt
195 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO, // fees_amt
196 FieldMask::DISABLED, // dividend_amt
197 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO | FieldMask::ALLOW_NEGATIVE | FieldMask::CAPGAINS_IN_STOCK, // capgains_amt
198 // Translators: this is a stock transaction describing new
199 // sale of stock, and recording capital gain/loss
200 N_("Sell"),
201 N_("Selling stock long, and record capital gain/loss."
202 "\n\nIf you are unable to calculate capital gains you can enter a "
203 "placeholder amount and correct it in the transaction later.")
204 },
205 {
206 FieldMask::MARKER_SPLIT, // stock_amt
207 FieldMask::ENABLED_DEBIT, // cash_amt
208 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO, // fees_amt
209 FieldMask::ENABLED_CREDIT, // dividend_amt
210 FieldMask::DISABLED, // capg_amt
211 // Translators: this is a stock transaction describing
212 // dividends issued to holder
213 N_("Dividend"),
214 N_("Company issues cash dividends to holder.\n\nAny dividend being "
215 "reinvested must be subsequently recorded as a regular stock purchase.")
216 },
217 {
218 FieldMask::ENABLED_CREDIT, // stock_amt
219 FieldMask::ENABLED_DEBIT, // cash_amt
220 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT, // fees_amt
221 FieldMask::DISABLED, // dividend_amt
222 FieldMask::DISABLED, // capg_amt
223 // Translators: this is a stock transaction describing return
224 // of capital
225 N_("Return of capital"),
226 N_("Company returns capital, reducing the cost basis without affecting # units.")
227 },
228 {
229 FieldMask::ENABLED_CREDIT, // stock_amt
230 FieldMask::DISABLED, // cash_amt
231 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT, // fees_amt
232 FieldMask::ENABLED_DEBIT, // dividend_amt
233 FieldMask::DISABLED, // capg_amt
234 // Translators: this is a stock transaction describing return
235 // of capital, reclassifying a dividend into return of capital
236 N_("Return of capital (reclassification)"),
237 N_("Company returns capital, reducing the cost basis without affecting # units. "
238 "A distribution previously recorded as a dividend is reclassified to return "
239 "of capital, often due to end-of-year tax information.")
240 },
241 {
242 FieldMask::ENABLED_DEBIT, // stock_amt
243 FieldMask::DISABLED, // cash_amt
244 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO, // fees_amt
245 FieldMask::ENABLED_CREDIT, // dividend_amt
246 FieldMask::DISABLED, // capg_amt
247 // Translators: this is a stock transaction describing a
248 // notional distribution recorded as dividend
249 N_("Notional distribution (dividend)"),
250 N_("Company issues a notional distribution, which is recorded as dividend "
251 "income and increases the cost basis without affecting # units.")
252 },
253 {
254 FieldMask::ENABLED_DEBIT, // stock_amt
255 FieldMask::DISABLED, // cash_amt
256 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO, // fees_amt
257 FieldMask::DISABLED, // dividend_amt
258 FieldMask::ENABLED_CREDIT, // capg_amt
259 // Translators: this is a stock transaction describing a
260 // notional distribution recorded as capital gain
261 N_("Notional distribution (capital gain)"),
262 N_("Company issues a notional distribution, which is recorded as capital gain "
263 "and increases the cost basis without affecting # units.")
264 },
265 {
266 FieldMask::AMOUNT_DEBIT | FieldMask::INPUT_NEW_BALANCE, // stock_amt
267 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO, // cash_amt
268 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT, // fees_amt
269 FieldMask::DISABLED, // dividend_amt
270 FieldMask::DISABLED, // capg_amt
271 // Translators: this is a stock transaction describing a stock
272 // split
273 N_("Stock split"),
274 N_("Company issues additional units, thereby reducing the stock price by a divisor, "
275 "while keeping the total monetary value of the overall investment constant.\n\nIf "
276 "the split results in a cash in lieu for remainder units, please "
277 "record the sale using the Stock Transaction Assistant first, then record the split.")
278 },
279 {
280 FieldMask::AMOUNT_CREDIT | FieldMask::INPUT_NEW_BALANCE, // stock_amt
281 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO, // cash_amt
282 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT, // fees_amt
283 FieldMask::DISABLED, // dividend_amt
284 FieldMask::DISABLED, // capg_amt
285 // Translators: this is a stock transaction describing a reverse split
286 N_("Reverse split"),
287 N_("Company redeems units, thereby increasing the stock price by a multiple, while "
288 "keeping the total monetary value of the overall investment constant.\n\nIf the "
289 "reverse split results in a cash in lieu for remainder units, please record the "
290 "sale using the Stock Transaction Assistant first, then record the reverse split.")
291 }
292};
293
294static const TxnTypeVec short_types
295{
296 {
297 FieldMask::ENABLED_CREDIT | FieldMask::AMOUNT_CREDIT, // stock_amt
298 FieldMask::ENABLED_DEBIT, // cash_amt
299 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT, // fees_amt
300 FieldMask::DISABLED, // dividend_amt
301 FieldMask::DISABLED, // capg_amt
302 // Translators: this is a stock transaction describing
303 // shorting of stock.
304 N_("Short sell"),
305 N_("Selling stock short.")
306 },
307 {
308 FieldMask::ENABLED_DEBIT | FieldMask::AMOUNT_DEBIT, // stock_amt
309 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO, // cash_amt
310 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO, // fees_amt
311 FieldMask::DISABLED, // dividend_amt
312 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO | FieldMask::ALLOW_NEGATIVE | FieldMask::CAPGAINS_IN_STOCK, // capg_amt
313 // Translators: this is a stock transaction describing cover
314 // buying stock, and recording capital gain/loss
315 N_("Buy to cover short"),
316 N_("Buy back stock to cover short position, and record capital gain/loss.\n\nIf "
317 "you are unable to calculate capital gains you can enter a placeholder "
318 "amount and correct it in the transaction later.")
319 },
320 {
321 FieldMask::MARKER_SPLIT, // stock_amt
322 FieldMask::ENABLED_CREDIT, // cash_amt
323 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO, // fees_amt
324 FieldMask::ENABLED_DEBIT, // dividend_amt
325 FieldMask::DISABLED, // capg_amt
326 // Translators: this is a stock transaction describing
327 // dividends retrieved from holder when shorting stock
328 N_("Compensatory dividend"),
329 N_("Company issues dividends, and the short stock holder must make a compensatory "
330 "payment for the dividend.")
331 },
332 {
333 FieldMask::ENABLED_DEBIT, // stock_amt
334 FieldMask::ENABLED_CREDIT, // cash_amt
335 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT, // fees_amt
336 FieldMask::DISABLED, // dividend_amt
337 FieldMask::DISABLED, // capg_amt
338 // Translators: this is a stock transaction describing return
339 // of capital retrieved from holder when shorting stock
340 N_("Compensatory return of capital"),
341 N_("Company returns capital, and the short stock holder must make a compensatory "
342 "payment for the returned capital. This reduces the cost basis (less negative, "
343 "towards 0.00 value) without affecting # units.")
344 },
345 {
346 FieldMask::ENABLED_DEBIT, // stock_amt
347 FieldMask::DISABLED, // cash_amt
348 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT, // fees_amt
349 FieldMask::ENABLED_CREDIT, // dividend_amt
350 FieldMask::DISABLED, // capg_amt
351 // Translators: this is a stock transaction describing
352 // reclassifying a compensatory dividend into compensatory
353 // return of capital when shorting stock
354 N_("Compensatory return of capital (reclassification)"),
355 N_("Company returns capital, and the short stock holder must make a compensatory "
356 "payment for the returned capital. This reduces the cost basis (less negative, "
357 "towards 0.00 value) without affecting # units. A distribution previously recorded "
358 "as a compensatory dividend is reclassified to compensatory return of capital, "
359 "often due to end-of-year tax information.")
360 },
361 {
362 FieldMask::ENABLED_CREDIT, // stock_amt
363 FieldMask::DISABLED, // cash_amt
364 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO, // fees_amt
365 FieldMask::ENABLED_DEBIT, // dividend_amt
366 FieldMask::DISABLED, // capg_amt
367 // Translators: this is a stock transaction describing a
368 // notional distribution recorded as dividend when shorting
369 // stock
370 N_("Compensatory notional distribution (dividend)"),
371 N_("Company issues a notional distribution, and the short stock holder must make a "
372 "compensatory payment for the notional distribution. This is recorded as a "
373 "loss/negative dividend income amount, and increases the cost basis (more "
374 "negative, away from 0.00 value) without affecting # units.")
375 },
376 {
377 FieldMask::ENABLED_CREDIT, // stock_amt
378 FieldMask::DISABLED, // cash_amt
379 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO, // fees_amt
380 FieldMask::DISABLED, // dividend_amt
381 FieldMask::ENABLED_DEBIT, // capg_amt
382 // Translators: this is a stock transaction describing a
383 // notional distribution recorded as capital gain when
384 // shorting stock
385 N_("Compensatory notional distribution (capital gain)"),
386 N_("Company issues a notional distribution, and the short stock holder must make "
387 "a compensatory payment for the notional distribution. This is recorded as a "
388 "capital loss amount, and increases the cost basis (more negative, away from "
389 "0.00 value) without affecting # units.")
390 },
391 {
392 FieldMask::AMOUNT_CREDIT | FieldMask::INPUT_NEW_BALANCE, // stock_amt
393 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO, // cash_amt
394 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT, // fees_amt
395 FieldMask::DISABLED, // dividend_amt
396 FieldMask::DISABLED, // capg_amt
397 // Translators: this is a stock transaction describing a stock
398 // split when shorting stock
399 N_("Stock split"),
400 N_("Company issues additional units, thereby reducing the stock price by a divisor, "
401 "while keeping the total monetary value of the overall investment constant.\n\nIf "
402 "the split results in a cash in lieu for remainder units, please "
403 "record the cover buy using the Stock Transaction Assistant first, then record the split.")
404 },
405 {
406 FieldMask::AMOUNT_DEBIT | FieldMask::INPUT_NEW_BALANCE, // stock_amt
407 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO, // cash_amt
408 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT, // fees_amt
409 FieldMask::DISABLED, // dividend_amt
410 FieldMask::DISABLED, // capg_amt
411 // Translators: this is a stock transaction describing a
412 // reverse split when shorting stock.
413 N_("Reverse split"),
414 N_("Company redeems units, thereby increasing the stock price by a multiple, while "
415 "keeping the total monetary value of the overall investment constant.\n\nIf the "
416 "reverse split results in a cash in lieu for remainder units, please record the "
417 "cover buy using the Stock Transaction Assistant first, then record the reverse split.")
418 }
419};
420
421enum class LogMsgType
422{
423 info,
424 warning,
425 error
426};
427
429{
430 LogMsgType m_type;
431 const std::string m_message;
432public:
433 LogMessage(LogMsgType type, std::string&& message) :
434 m_type{type}, m_message(std::move(message)) {}
435 LogMessage(LogMsgType type, const char* message) :
436 m_type{type}, m_message(message) {}
437 LogMessage(const LogMessage&) = default;
438 LogMessage(LogMessage&&) = default;
439 ~LogMessage() = default;
440 LogMsgType type() { return m_type; }
441 const std::string& message() { return m_message; }
442};
443
450using Log = std::vector<LogMessage>;
451
453{
454 Log m_log;
455public: // compiler generated ctors & dtor are fine
456 void info(const char* message) { m_log.emplace_back(LogMsgType::info, message); }
457 void warn(const char* message) { m_log.emplace_back(LogMsgType::warning, message); }
458 void error(const char* message) { m_log.emplace_back(LogMsgType::error, message); }
459 void clear() { m_log.clear(); }
460 bool has_errors();
461 bool has_warnings();
462 void write_log(std::stringstream& stream, LogMsgType type);
463 void infos(std::stringstream& stream) { return write_log(stream, LogMsgType::info); }
464 void warnings(std::stringstream& stream) { return write_log(stream, LogMsgType::warning); }
465 void errors(std::stringstream& stream) { return write_log(stream, LogMsgType::error); }
471 std::string report();
472};
473
474void
475Logger::write_log(std::stringstream& stream, LogMsgType type)
476{
477 std::for_each(m_log.begin(), m_log.end(),
478 [&](auto& msg){
479 if (msg.type() == type)
480 stream << "\n * " << msg.message();
481 });
482}
483
484bool
485Logger::has_warnings()
486{
487 return std::any_of(m_log.begin(), m_log.end(),
488 [](auto& msg){ return msg.type() == LogMsgType::warning;
489 });
490}
491
492bool
493Logger::has_errors()
494{
495 return std::any_of(m_log.begin(), m_log.end(),
496 [](auto& msg){ return msg.type() == LogMsgType::error;
497 });
498}
499
500std::string
502{
503 std::stringstream summary;
504 if (!has_errors())
505 {
506 summary << _("No errors found. Click Apply to create transaction.");
507 infos(summary);
508 }
509 else
510 {
511 summary << _("The following errors must be fixed:");
512 errors(summary);
513 }
514 if (has_warnings())
515 {
516 summary << "\n\n" << _("The following warnings exist:");
517 warnings(summary);
518 }
519 return summary.str();
520}
521
522/* StockTransactionEntry QofEventHandler for accounts.
523 * Nulls the tranaction entry's account member if the account gets destroyed on
524 * us.
525 */
526static void account_destroyed_handler(QofInstance *inst, QofEventId event,
527 void* handler_data, [[maybe_unused]]void* event_data);
528
538{
539protected:
540 bool m_enabled;
541 bool m_debit_side;
542 bool m_allow_zero;
543 bool m_allow_negative;
544 bool m_input_new_balance = false;
545 Account *m_account;
546 gnc_numeric m_value;
547 const char* m_memo;
548 const char* m_action;
549 gnc_numeric m_balance = gnc_numeric_zero();
550 const char* m_kvp_tag;
551 int m_qof_event_handler;
552public:
554 m_enabled{false}, m_debit_side{false}, m_allow_zero{false}, m_account{nullptr},
555 m_value{gnc_numeric_error(GNC_ERROR_ARG)}, m_memo{nullptr}, m_action{nullptr},
556 m_kvp_tag{nullptr}, m_qof_event_handler{qof_event_register_handler(account_destroyed_handler, this)} {}
557 StockTransactionEntry(const char* action, const char* kvp_tag) :
558 m_enabled{false}, m_debit_side{false}, m_allow_zero{false}, m_account{nullptr},
559 m_value{gnc_numeric_error(GNC_ERROR_ARG)}, m_memo{nullptr}, m_action{action},
560 m_kvp_tag{kvp_tag}, m_qof_event_handler{qof_event_register_handler(account_destroyed_handler, this)} {}
562 virtual ~StockTransactionEntry() { qof_event_unregister_handler(m_qof_event_handler); }
567 virtual void set_fieldmask(FieldMask mask);
568 virtual bool enabled() const { return m_enabled; }
569 virtual bool debit_side() const { return m_debit_side; }
570 virtual void set_capitalize(bool capitalize) {}
571 virtual bool input_new_balance() const { return m_input_new_balance; }
572 virtual bool do_capitalize() const { return false; }
573 virtual void set_account(Account* account) { m_account = account; }
574 virtual Account* account() const { return m_account; }
575 virtual const char* print_account() const;
576 virtual void set_memo(const char* memo) { m_memo = memo; }
577 virtual const char* get_kvp_tag () { return m_kvp_tag; }
578 virtual const char* memo() const { return m_memo; }
579 virtual void set_value(gnc_numeric amount);
580 virtual GncNumeric value() { return (gnc_numeric_check(m_value) ? GncNumeric{} : GncNumeric(m_value)); }
581 virtual void set_amount(gnc_numeric) {}
582 virtual gnc_numeric amount() const { return m_value; }
583 virtual bool has_amount() const { return false; }
584 virtual bool marker_split() const { return false; }
585 /* Validates that the value and for stock entry the amount meet
586 * the criteria set for the entry by the field mask.
587 *
588 * @param logger collects any emitted diagnostics.
589 */
590 virtual void validate_amount(Logger&) const;
591 virtual void set_balance(gnc_numeric balance) { m_balance = balance; }
592 virtual gnc_numeric get_balance() const { return m_balance; }
593 /* Creates a GnuCash split from the data in the entry and adds it
594 * to the transaction, adding the account to the account vector so
595 * that it can be committed when the transaction is completed.
596 *
597 * @param trans the transaction to which to add the split
598 * @param commits the list of accounts to have edits committed later.
599 */
600 virtual void create_split(Transaction* trans, AccountVec& commits) const;
604 virtual const char* print_value() const;
608 virtual const char* print_amount(gnc_numeric amt) const;
616 virtual std::string amount_str_for_display() const { return ""; }
623 virtual gnc_numeric calculate_price() const { return gnc_numeric_error(GNC_ERROR_ARG); }
627 virtual const char* print_price() const;
628};
629
630static void
631account_destroyed_handler(QofInstance *inst, QofEventId event,
632 void* handler_data, [[maybe_unused]]void* event_data)
633{
634 auto entry{static_cast<StockTransactionEntry*>(handler_data)};
635 if ((inst && inst != QOF_INSTANCE(entry->account())) || (event & QOF_EVENT_DESTROY) == 0)
636 return;
637 entry->set_account(nullptr);
638}
639
640using StockTransactionEntryPtr = std::unique_ptr<StockTransactionEntry>;
641
642void
644{
645 m_enabled = mask != FieldMask::DISABLED;
646 m_debit_side = mask & FieldMask::ENABLED_DEBIT;
647 m_allow_zero = mask & FieldMask::ALLOW_ZERO;
648 m_allow_negative = mask & FieldMask::ALLOW_NEGATIVE;
649}
650
651const char *
652StockTransactionEntry::print_account() const
653{
654 auto acct_required = m_enabled &&
655 !(m_allow_zero && (gnc_numeric_zero_p(m_value) ||
656 gnc_numeric_check(m_value)));
657 return m_account ? xaccAccountGetName(m_account) :
658 acct_required ? _("missing") : "";
659}
660
661void
662StockTransactionEntry::set_value(gnc_numeric amount)
663{
664 if (gnc_numeric_check (amount))
665 {
667 return;
668 }
669
670 if (gnc_numeric_negative_p (amount))
671 {
672 m_value = gnc_numeric_neg(amount);
673 m_debit_side = !m_debit_side;
674 }
675 else
676 {
677 m_value = amount;
678 }
679 PINFO("Set %s value to %" PRId64 "/%" PRId64, m_action, m_value.num, m_value.denom);
680}
681
682void
683StockTransactionEntry::validate_amount(Logger& logger) const
684{
685 auto add_error = [&logger](const char* format_str, const char* arg)
686 {
687 char *buf = g_strdup_printf (_(format_str),
688 g_dpgettext2 (nullptr, "Stock Assistant: Page name", arg));
689 logger.error(buf);
690 g_free (buf);
691 };
692
693
694 if (gnc_numeric_check (m_value))
695 {
696 if (!m_allow_zero)
697 add_error (N_("Amount for %s is missing."), m_action);
698 return;
699 }
700
701 if (gnc_numeric_negative_p (m_value) && !m_allow_negative && m_allow_zero)
702 add_error (N_("Amount for %s must not be negative."), m_action);
703
704 if (!m_allow_zero && !gnc_numeric_positive_p (m_value))
705 add_error (N_("Amount for %s must be positive."), m_action);
706
707 if (!gnc_numeric_zero_p(m_value) && !m_account)
708 add_error(N_("The %s amount has no associated account."), m_action);
709}
710
711const char *
713{
714 if (!m_enabled || (gnc_numeric_check(m_value) && m_allow_zero))
715 return nullptr;
716
717 if ((gnc_numeric_check(m_value) || gnc_numeric_zero_p(m_value))
718 && !m_allow_zero)
719 return _("missing");
720
721 /* Don't combine this with the first if, it would prevent showing
722 * "missing" when the value is required.
723 */
724 if (!m_account)
725 return nullptr;
726
727 auto currency{gnc_account_get_currency_or_parent(m_account)};
728 auto pinfo{gnc_commodity_print_info(currency, TRUE)};
729 return xaccPrintAmount(m_value, pinfo);
730}
731
732const char *
734{
735 if (!m_account || gnc_numeric_check(amt))
736 return nullptr;
737 auto commodity{xaccAccountGetCommodity(m_account)};
738 auto pinfo{gnc_commodity_print_info(commodity, TRUE)};
739 return xaccPrintAmount(amt, pinfo);
740}
741
742void
743StockTransactionEntry::create_split(Transaction *trans, AccountVec &account_commits) const
744{
745 g_return_if_fail(trans);
746 if (!m_account || gnc_numeric_check(m_value))
747 return;
748 auto split = xaccMallocSplit(qof_instance_get_book(trans));
749 xaccSplitSetParent(split, trans);
750 xaccAccountBeginEdit(m_account);
751 account_commits.push_back(m_account);
752 xaccSplitSetAccount(split, m_account);
753 xaccSplitSetMemo(split, m_memo);
754 if (m_enabled)
755 xaccSplitSetValue(split, m_debit_side ? m_value : gnc_numeric_neg(m_value));
756 xaccSplitSetAmount(split, amount());
757 PINFO("creating %s split in Acct(%s): Val(%s), Amt(%s) => Val(%s), Amt(%s)",
758 m_action, m_account ? xaccAccountGetName (m_account) : "Empty!",
759 gnc_num_dbg_to_string(m_value),
760 gnc_num_dbg_to_string(amount()),
763 gnc_set_num_action(nullptr, split, nullptr,
764 g_dpgettext2(nullptr, "Stock Assistant: Action field",
765 m_action));
766}
767
768const char *
770{
771 auto price{calculate_price()};
772 if (gnc_numeric_check(price))
773//Translators: "N/A" here means that a commodity doesn't have a valid price.
774 return _("N/A");
775 auto currency{gnc_account_get_currency_or_parent(m_account)};
776 auto pinfo{gnc_price_print_info(currency, TRUE)};
777 return xaccPrintAmount(price, pinfo);
778}
779
789{
790 bool m_amount_enabled;
791 gnc_numeric m_amount;
792 bool m_marker = false;
793public:
796 {
797 PINFO("Stock Entry");
798 }
799 StockTransactionStockEntry(const char* action) :
800 StockTransactionEntry{action, nullptr}, m_amount{gnc_numeric_error(GNC_ERROR_ARG)}
801 {
802 PINFO("Stock Entry");
803 }
804 void set_fieldmask(FieldMask mask) override;
805 void set_amount(gnc_numeric amount) override;
806 gnc_numeric amount() const override { return m_amount; }
807 bool has_amount() const override { return m_amount_enabled; }
808 void validate_amount(Logger& logger) const override;
809 void create_split(Transaction *trans, AccountVec &account_commits) const override;
810 std::string amount_str_for_display() const override;
811 gnc_numeric calculate_price() const override;
812 bool marker_split() const override { return m_marker; }
813};
814
815void
817{
819 m_enabled = mask & (FieldMask::ENABLED_CREDIT | FieldMask::ENABLED_DEBIT);
820 m_amount_enabled = mask & (FieldMask::AMOUNT_CREDIT | FieldMask::AMOUNT_DEBIT);
821 m_debit_side = mask & (FieldMask::ENABLED_DEBIT | FieldMask::AMOUNT_DEBIT);
822 m_input_new_balance = mask & FieldMask::INPUT_NEW_BALANCE;
823 m_marker = mask & FieldMask::MARKER_SPLIT;
824}
825
826
827void
828StockTransactionStockEntry::set_amount(gnc_numeric amount)
829{
830 if (!m_amount_enabled || gnc_numeric_check(amount))
831 {
833 return;
834 }
835
836 if (m_input_new_balance)
837 {
838 if (m_debit_side)
839 m_amount = gnc_numeric_sub_fixed(amount, m_balance);
840 else
841 m_amount = gnc_numeric_sub_fixed(m_balance, amount);
842
843 PINFO("%s set amount for new balance %s", m_memo, print_amount(m_amount));
844 }
845 else
846 {
847 m_amount = amount;
848 PINFO("%s set amount %s", m_memo, print_amount(m_amount));
849 }
850}
851
852void
853StockTransactionStockEntry::validate_amount(Logger& logger) const
854{
855 if (m_enabled)
856 StockTransactionEntry::validate_amount(logger);
857
858 if (!m_amount_enabled)
859 return;
860
861 auto add_error_str = [&logger]
862 (const char* str) { logger.error (_(str)); };
863
864 if (gnc_numeric_check(m_amount) || gnc_numeric_zero_p(m_amount))
865 {
866 add_error_str(_("Amount for stock value is missing."));
867 return;
868 }
869
870 if (m_input_new_balance)
871 {
872 auto amount = gnc_numeric_add_fixed(m_debit_side ? m_amount : gnc_numeric_neg(m_amount), m_balance);
873 auto delta = gnc_numeric_sub_fixed(amount, m_balance);
874 auto ratio = gnc_numeric_div(amount, m_balance,
875 GNC_DENOM_AUTO, GNC_HOW_DENOM_REDUCE);
876
877 if (gnc_numeric_check(ratio) || !gnc_numeric_positive_p(ratio))
878 add_error_str(N_("Invalid stock new balance."));
879 else if (gnc_numeric_negative_p(delta) && m_debit_side)
880 add_error_str(N_("New balance must be higher than old balance."));
881 else if (gnc_numeric_positive_p(delta) && !m_debit_side)
882 add_error_str(N_("New balance must be lower than old balance."));
883
884 PINFO("Delta %" PRId64 "/%" PRId64 ", Ratio %" PRId64 "/%" PRId64, delta.num, delta.denom, ratio.num, ratio.denom);
885 return;
886 }
887
888 if (!gnc_numeric_positive_p(m_amount))
889 add_error_str(N_("Stock amount must be positive."));
890
891 auto new_bal = gnc_numeric_add_fixed(m_balance, m_amount);
892 if (gnc_numeric_positive_p(m_balance) && gnc_numeric_negative_p(new_bal))
893 add_error_str(N_("Cannot sell more units than owned."));
894 else if (gnc_numeric_negative_p(m_balance) && gnc_numeric_positive_p(new_bal))
895 add_error_str(N_("Cannot cover buy more units than owed."));
896}
897
898std::string
900{
901 std::string rv{""};
902
903 if (gnc_numeric_check (m_amount))
904 return rv;
905
906 if (m_input_new_balance)
907 {
908 auto amount = gnc_numeric_add(m_debit_side ? m_amount : gnc_numeric_neg(m_amount), m_balance,
910 auto ratio = gnc_numeric_div (amount, m_balance,
912 PINFO("Computed ratio %" PRId64 "/%" PRId64 "; amount %" PRId64
913 "/%" PRId64 " and balance %" PRId64 "/%" PRId64,
914 ratio.num, ratio.denom, amount.num, amount.denom, m_balance.num, m_balance.denom);
915 if (gnc_numeric_check (ratio) || !gnc_numeric_positive_p (ratio))
916 return rv;
917
918 std::ostringstream ret;
919 ret << ratio.num << ':' << ratio.denom;
920 rv = ret.str();
921 }
922 else
923 {
924 auto amount = m_debit_side ? m_amount : gnc_numeric_neg (m_amount);
925 amount = gnc_numeric_add_fixed (amount, m_balance);
926 rv = print_amount(amount);
927 }
928
929 return rv;
930};
931
932
933void
934StockTransactionStockEntry::create_split(Transaction *trans, AccountVec &account_commits) const
935{
936 g_return_if_fail(trans);
937 if (!m_account)
938 return;
939 auto split = xaccMallocSplit(qof_instance_get_book(trans));
940 xaccSplitSetParent(split, trans);
941 xaccAccountBeginEdit(m_account);
942 account_commits.push_back(m_account);
943 xaccSplitSetAccount(split, m_account);
944 xaccSplitSetMemo(split, m_memo);
945 if (m_enabled)
946 xaccSplitSetValue(split, m_debit_side ? m_value : gnc_numeric_neg(m_value));
947 if (m_amount_enabled)
948 xaccSplitSetAmount(split, m_debit_side ? m_amount : gnc_numeric_neg(m_amount));
949 if (m_amount_enabled && !m_enabled) // It's a stock split
951 PINFO("creating %s split in Acct(%s): Val(%s), Amt(%s) => Val(%s), Amt(%s)",
952 m_action, m_account ? xaccAccountGetName (m_account) : "Empty!",
953 gnc_num_dbg_to_string(m_value),
954 gnc_num_dbg_to_string(amount()),
957 gnc_set_num_action(nullptr, split, nullptr,
958 g_dpgettext2(nullptr, "Stock Assistant: Action field",
959 m_action));
960}
961
962gnc_numeric
964{
965 if (m_input_new_balance ||
966 !m_amount_enabled || gnc_numeric_check(m_amount) ||
967 !m_enabled || gnc_numeric_check(m_value) ||
968 gnc_numeric_zero_p(m_amount) || gnc_numeric_zero_p(m_value))
970
971 auto price = gnc_numeric_div(m_value, m_amount,
973
974 auto comm{xaccAccountGetCommodity(m_account)};
975 auto curr{gnc_account_get_currency_or_parent(m_account)};
976 auto ainfo{gnc_commodity_print_info (comm, true)};
977 auto pinfo{gnc_price_print_info (curr, true)};
978 auto vinfo{gnc_commodity_print_info (curr, true)};
979
980 PINFO("Calculated price %s from value %s and amount %s",
981 xaccPrintAmount(price, pinfo), xaccPrintAmount(m_value, vinfo),
982 xaccPrintAmount(m_amount, ainfo));
983 return price;
984}
985
993{
994public:
996 const StockTransactionEntry* stk_entry);
997 gnc_numeric amount() const { return gnc_numeric_zero(); }
998};
999
1000StockTransactionStockCapGainsEntry::StockTransactionStockCapGainsEntry(const StockTransactionEntry *cg_entry,
1001 const StockTransactionEntry *stk_entry) :
1002 StockTransactionEntry(*cg_entry)
1003{
1004 m_debit_side = !m_debit_side;
1005 m_account = stk_entry->account();
1006}
1007
1013{
1014 bool m_capitalize;
1015public:
1016 StockTransactionFeesEntry() : StockTransactionEntry{}, m_capitalize{false} {}
1017 StockTransactionFeesEntry(const char* action, const char *tag) : StockTransactionEntry{action, tag}, m_capitalize{false} {}
1018 void set_fieldmask(FieldMask mask) override;
1019 void set_capitalize(bool capitalize) override { m_capitalize = capitalize; }
1020 bool do_capitalize() const override { return m_capitalize; }
1021 void validate_amount(Logger &logger) const override;
1022 void create_split(Transaction *trans, AccountVec &commits) const override;
1023};
1024
1025void
1027{
1029 m_capitalize = mask & FieldMask::CAPITALIZE_DEFAULT;
1030}
1031
1032void
1033StockTransactionFeesEntry::validate_amount(Logger& logger) const
1034{
1035 auto add_error = [&logger](const char* format_str, const char* arg)
1036 {
1037 char *buf = g_strdup_printf (_(format_str),
1038 g_dpgettext2 (nullptr, "Stock Assistant: Page name", arg));
1039 logger.error(buf);
1040 g_free (buf);
1041 };
1042
1043
1044 if (gnc_numeric_check (m_value))
1045 {
1046 if (!m_allow_zero)
1047 add_error (N_("Amount for %s is missing."), m_action);
1048 return;
1049 }
1050
1051 if (gnc_numeric_negative_p (m_value) && !m_allow_negative && m_allow_zero)
1052 add_error (N_("Amount for %s must not be negative."), m_action);
1053
1054 if (!m_allow_zero && !gnc_numeric_positive_p (m_value))
1055 add_error (N_("Amount for %s must be positive."), m_action);
1056
1057 if (!gnc_numeric_zero_p(m_value) && !m_account && !m_capitalize)
1058 add_error(N_("The %s amount has no associated account."), m_action);
1059}
1060
1061void
1062StockTransactionFeesEntry::create_split(Transaction* trans, AccountVec& commits) const
1063{
1064 g_return_if_fail(trans);
1065 if ((!m_account && !m_capitalize) || gnc_numeric_check(m_value))
1066 return;
1067 auto split = xaccMallocSplit(qof_instance_get_book(trans));
1068 xaccSplitSetParent(split, trans);
1069 if (m_capitalize)
1070 {
1071 xaccSplitSetAccount(split, commits[0]); // Should be the stock account
1072 }
1073 else
1074 {
1075 xaccAccountBeginEdit(m_account);
1076 commits.push_back(m_account);
1077 xaccSplitSetAccount(split, m_account);
1078 xaccSplitSetAmount(split, amount());
1079 }
1080 xaccSplitSetMemo(split, m_memo);
1081 xaccSplitSetValue(split, m_debit_side ? m_value : gnc_numeric_neg(m_value));
1082 PINFO("creating %s split in Acct(%s): Val(%s), Amt(%s) => Val(%s), Amt(%s)",
1083 m_action, m_account ? xaccAccountGetName (m_account) : "Empty!",
1084 gnc_num_dbg_to_string(m_value),
1085 gnc_num_dbg_to_string(amount()),
1088 gnc_set_num_action(nullptr, split, nullptr,
1089 g_dpgettext2(nullptr, "Stock Assistant: Action field",
1090 m_action));
1091}
1092
1093using EntryVec = std::vector<StockTransactionEntry*>;
1094
1095static void stock_assistant_model_date_changed_cb(GtkWidget*, void*);
1096static void stock_assistant_model_description_changed_cb(GtkWidget *, void *);
1097
1105{
1106 Account* m_acct;
1107 gnc_commodity* m_currency;
1108 time64 m_transaction_date;
1109 const char* m_transaction_description;
1110 std::optional<TxnTypeVec> m_txn_types;
1111
1112 std::optional<TxnTypeInfo> m_txn_type;
1113
1114 StockTransactionEntryPtr m_stock_entry;
1115 StockTransactionEntryPtr m_cash_entry;
1116 StockTransactionEntryPtr m_fees_entry;
1117 StockTransactionEntryPtr m_dividend_entry;
1118 StockTransactionEntryPtr m_capgains_entry;
1119 StockTransactionEntryPtr m_stock_cg_entry; // Required at this level for lifetime management
1120 Logger m_logger;
1121
1122 std::optional<time64> m_txn_types_date;
1123 bool m_ready_to_create = false;
1124
1125 EntryVec m_list_of_splits;
1126
1127public:
1129 : m_acct{account}, m_currency{gnc_account_get_currency_or_parent(
1130 account)},
1131 m_stock_entry{std::make_unique<StockTransactionStockEntry>(
1132 NC_("Stock Assistant: Page name", "Stock"))},
1133 m_cash_entry{std::make_unique<StockTransactionEntry>(
1134 NC_("Stock Assistant: Page name", "Cash"), PROCEEDS_KVP_TAG)},
1135 m_fees_entry{std::make_unique<StockTransactionFeesEntry>(
1136 NC_("Stock Assistant: Page name", "Fees"), FEES_KVP_TAG)},
1137 m_dividend_entry{std::make_unique<StockTransactionEntry>(
1138 NC_("Stock Assistant: Page name", "Dividend"), DIVIDEND_KVP_TAG)},
1139 m_capgains_entry{std::make_unique<StockTransactionEntry>(
1140 NC_("Stock Assistant: Page name", "Capital Gains"),
1141 CAPGAINS_KVP_TAG)} {
1142 DEBUG ("StockAssistantModel constructor\n");
1143 m_stock_entry->set_account(m_acct);
1144 };
1145
1147 {
1148 DEBUG ("StockAssistantModel destructor\n");
1149 };
1150
1157 bool maybe_reset_txn_types ();
1162 const std::optional<TxnTypeVec>& get_txn_types() { return m_txn_types; }
1168 bool set_txn_type (guint type_idx);
1173 bool txn_type_valid() { return m_txn_type.has_value(); }
1178 void set_transaction_date(time64 date) { m_transaction_date = date;}
1183 void set_transaction_desc(const char* desc) { m_transaction_description = desc; }
1188 const std::optional<TxnTypeInfo>& txn_type() { return m_txn_type; }
1193 std::string get_new_amount_str () const;
1198 StockTransactionEntry* stock_entry() { return m_stock_entry.get(); }
1203 StockTransactionEntry* cash_entry() { return m_cash_entry.get(); }
1208 StockTransactionEntry* fees_entry() { return m_fees_entry.get(); }
1213 StockTransactionEntry* dividend_entry() { return m_dividend_entry.get(); }
1218 StockTransactionEntry* capgains_entry() { return m_capgains_entry.get(); }
1223 Logger& logger() { return m_logger; }
1234 std::tuple<bool, std::string, EntryVec> generate_list_of_splits ();
1240 std::tuple<bool, Transaction*> create_transaction ();
1241 Account* account() { return m_acct; }
1242private:
1246 void add_price (QofBook *book);
1247};
1248
1249bool
1251{
1252 auto old_bal = m_stock_entry->get_balance();
1253 auto new_bal = xaccAccountGetBalanceAsOfDate
1254 (m_acct, gnc_time64_get_day_end (m_transaction_date));
1255 if (m_txn_types_date && m_txn_types_date == m_transaction_date &&
1256 gnc_numeric_equal (old_bal, new_bal))
1257 return false;
1258 m_stock_entry->set_balance(new_bal);
1259 m_txn_types_date = m_transaction_date;
1260 m_txn_types = gnc_numeric_zero_p (new_bal) ? starting_types
1261 : gnc_numeric_positive_p (new_bal) ? long_types
1262 : short_types;
1263 return true;
1264};
1265
1266bool
1268{
1269 if (!m_txn_types_date || m_txn_types_date != m_transaction_date)
1270 {
1271 PERR ("transaction_date has changed. rerun maybe_reset_txn_types!");
1272 return false;
1273 }
1274 try
1275 {
1276 m_txn_type = m_txn_types->at (type_idx);
1277 }
1278 catch (const std::out_of_range&)
1279 {
1280 PERR ("out of range type_idx=%d", type_idx);
1281 return false;
1282 }
1283
1284 m_stock_entry->set_fieldmask(m_txn_type->stock_amount);
1285 m_fees_entry->set_fieldmask(m_txn_type->fees_value);
1286 m_capgains_entry->set_fieldmask(m_txn_type->capgains_value);
1287 m_dividend_entry->set_fieldmask(m_txn_type->dividend_value);
1288 m_cash_entry->set_fieldmask(m_txn_type->cash_value);
1289 return true;
1290};
1291
1292static void
1293check_txn_date(const Split* last_split, time64 txn_date, Logger& logger)
1294{
1295 auto last_split_date = xaccTransGetDate(xaccSplitGetParent(last_split));
1296 if (txn_date <= last_split_date) {
1297 auto last_split_date_str = qof_print_date(last_split_date);
1298 auto new_date_str = qof_print_date(txn_date);
1299 // Translators: the first %s is the new transaction date;
1300 // the second %s is the current stock account's latest
1301 // transaction date.
1302 auto warn_txt = g_strdup_printf(
1303 _("You will enter a transaction "
1304 "with date %s which is earlier than the latest transaction in this account, "
1305 "dated %s. Doing so may affect the cost basis, and therefore capital gains, "
1306 "of transactions dated after the new entry. Please review all transactions "
1307 "to ensure proper recording."),
1308 new_date_str, last_split_date_str);
1309 logger.warn(warn_txt);
1310 g_free(warn_txt);
1311 g_free(new_date_str);
1312 g_free(last_split_date_str);
1313 }
1314}
1315
1316std::tuple<bool, std::string, EntryVec>
1318 if (!m_txn_types || !m_txn_type)
1319 return { false, "Error: txn_type not initialized", {} };
1320
1321 m_logger.clear();
1322 m_list_of_splits.clear();
1323
1324 GncNumeric debit{};
1325 GncNumeric credit{};
1326
1327 // check the stock transaction date. If there are existing stock
1328 // transactions dated after the date specified, it is very likely
1329 // the later stock transactions will be invalidated. warn the user
1330 // to review them.
1331 if (const auto& splits = xaccAccountGetSplits (m_acct); !splits.empty())
1332 check_txn_date(splits.back(), m_transaction_date, m_logger);
1333
1334 if (m_stock_entry->enabled() || m_stock_entry->has_amount())
1335 {
1336 m_stock_entry->validate_amount(m_logger);
1337 m_list_of_splits.push_back(m_stock_entry.get());
1338
1339 auto price{m_stock_entry->calculate_price()};
1340 if (!gnc_numeric_check(price))
1341 {
1342 // Translators: %s refer to: stock mnemonic, broker currency,
1343 // date of transaction.
1344 auto tmpl = N_("A price of 1 %s = %s on %s will be recorded.");
1345 auto date_str = qof_print_date (m_transaction_date);
1346 auto price_msg = g_strdup_printf
1347 (_(tmpl),
1349 m_stock_entry->print_price(), date_str);
1350 m_logger.info(price_msg);
1351 g_free (date_str);
1352 g_free (price_msg);
1353 }
1354 }
1355
1356 if (m_stock_entry->marker_split())
1357 m_list_of_splits.push_back(m_stock_entry.get());
1358
1359 if (m_cash_entry->enabled())
1360 {
1361 m_cash_entry->validate_amount(m_logger);
1362 m_list_of_splits.push_back (m_cash_entry.get());
1363 }
1364
1365 if (m_fees_entry->enabled())
1366 {
1367 m_fees_entry->validate_amount(m_logger);
1368 if (m_fees_entry->do_capitalize())
1369 m_fees_entry->set_account(m_acct);
1370 m_list_of_splits.push_back (m_fees_entry.get());
1371 }
1372
1373 if (m_dividend_entry->enabled())
1374 {
1375 m_dividend_entry->validate_amount(m_logger);
1376 m_list_of_splits.push_back (m_dividend_entry.get());
1377 }
1378
1379 if (m_capgains_entry->enabled())
1380 {
1381 m_stock_cg_entry =
1382 std::make_unique<StockTransactionStockCapGainsEntry>(m_capgains_entry.get(),
1383 m_stock_entry.get());
1384 m_stock_cg_entry->validate_amount(m_logger);
1385 m_capgains_entry->validate_amount(m_logger);
1386 m_list_of_splits.push_back(m_stock_cg_entry.get());
1387 m_list_of_splits.push_back (m_capgains_entry.get());
1388 }
1389
1390 std::for_each(m_list_of_splits.begin(), m_list_of_splits.end(),
1391 [&debit, &credit](auto& entry) {
1392 if (entry->debit_side())
1393 debit += entry->value();
1394 else
1395 credit += entry->value();
1396 });
1397
1398 if (gnc_numeric_check(debit) || gnc_numeric_check(credit) ||!gnc_numeric_equal (debit, credit))
1399 {
1400 const char *err_act = NULL, *err_reason = NULL;
1401 if (gnc_numeric_check(debit))
1402 {
1403 err_act = "debit";
1405 }
1406 else if (gnc_numeric_check(credit))
1407 {
1408 err_act = "credit";
1410 }
1411
1412 if (err_act)
1413 {
1414 auto err_str = g_strdup_printf (N_("Transaction can't balance, %s is error value %s"), err_act, err_reason);
1415 m_logger.error(err_str);
1416 g_free (err_str);
1417 }
1418 else
1419 {
1420 auto imbalance_str = N_("Total Debits of %s does not balance with total Credits of %s.");
1421 auto pinfo{gnc_commodity_print_info (m_currency, true)};
1422 auto debit_str = g_strdup (xaccPrintAmount (debit, pinfo));
1423 auto credit_str = g_strdup (xaccPrintAmount (credit, pinfo));
1424 auto error_str = g_strdup_printf (_(imbalance_str), debit_str, credit_str);
1425 m_logger.error (error_str);
1426 g_free (error_str);
1427 g_free (credit_str);
1428 g_free (debit_str);
1429 }
1430 }
1431
1432 // generate final summary message. Collates a header, the errors
1433 // and warnings. Then allow completion if errors is empty.
1434 m_ready_to_create = !m_logger.has_errors();
1435 return { m_ready_to_create, m_logger.report(), m_list_of_splits };
1436}
1437
1438std::tuple<bool, Transaction*>
1440{
1441 if (!m_ready_to_create)
1442 {
1443 PERR ("errors exist. cannot create transaction.");
1444 m_list_of_splits.clear();
1445 return {false, nullptr};
1446 }
1447 auto book = qof_instance_get_book (m_acct);
1448 auto trans = xaccMallocTransaction (book);
1449 xaccTransBeginEdit (trans);
1450 xaccTransSetCurrency (trans, m_currency);
1451 xaccTransSetDescription (trans, m_transaction_description);
1452 xaccTransSetDatePostedSecsNormalized (trans, m_transaction_date);
1453 AccountVec accounts;
1454 std::for_each (m_list_of_splits.begin(), m_list_of_splits.end(),
1455 [&](auto& entry)
1456 {
1457 entry->create_split (trans, accounts);
1458 if (entry->get_kvp_tag() && entry->account())
1459 xaccAccountSetAssociatedAccount (m_acct, entry->get_kvp_tag(), entry->account());
1460 });
1461 add_price (book);
1462 xaccTransCommitEdit (trans);
1463 std::for_each (accounts.begin(), accounts.end(), xaccAccountCommitEdit);
1464 m_list_of_splits.clear();
1465 m_ready_to_create = false;
1466 return {true, trans};
1467}
1468
1469void
1470StockAssistantModel::add_price (QofBook *book)
1471{
1472 auto stock_price{m_stock_entry->calculate_price()};
1473 if (gnc_numeric_check(stock_price))
1474 return;
1475
1476 auto price = gnc_price_create (book);
1477 gnc_price_begin_edit (price);
1478 gnc_price_set_commodity (price, xaccAccountGetCommodity (m_acct));
1479 gnc_price_set_currency (price, m_currency);
1480 gnc_price_set_time64 (price, m_transaction_date);
1481 gnc_price_set_source (price, PRICE_SOURCE_STOCK_TRANSACTION);
1482 gnc_price_set_typestr (price, PRICE_TYPE_UNK);
1483 gnc_price_set_value (price, stock_price);
1484 gnc_price_commit_edit (price);
1485
1486 auto pdb = gnc_pricedb_get_db (book);
1487 if (!gnc_pricedb_add_price (pdb, price))
1488 PWARN ("error adding price");
1489
1490 gnc_price_unref (price);
1491}
1492
1493static void
1494stock_assistant_model_date_changed_cb(GtkWidget* widget, void* data)
1495{
1496 auto model{static_cast<StockAssistantModel*>(data)};
1497 model->set_transaction_date(gnc_date_edit_get_date_end(GNC_DATE_EDIT(widget)));
1498}
1499
1500static void
1501stock_assistant_model_description_changed_cb(GtkWidget* widget, void* data)
1502{
1503 auto model{static_cast<StockAssistantModel*>(data)};
1504 model->set_transaction_desc(gtk_entry_get_text(GTK_ENTRY(widget)));
1505}
1506
1507/* ********************* View Classes ************************/
1508
1509/* ***************** Generic Event Callbacks ****************/
1510static void
1511text_entry_changed_cb (GtkWidget *widget, StockTransactionEntry* entry)
1512{
1513 entry->set_memo(gtk_entry_get_text (GTK_ENTRY (widget)));
1514}
1515
1516
1517static inline GtkWidget*
1518get_widget (GtkBuilder *builder, const gchar * ID)
1519{
1520 g_return_val_if_fail (builder && ID, nullptr);
1521 auto obj = gtk_builder_get_object (builder, ID);
1522 if (!obj)
1523 PWARN ("get_widget ID '%s' not found. it may be a typo?", ID);
1524 return GTK_WIDGET (obj);
1525}
1526
1532{
1533 GtkWidget *m_edit;
1534public:
1535 GncDateEdit(GtkBuilder *builder) :
1536 m_edit{gnc_date_edit_new(gnc_time(nullptr), FALSE, FALSE)} {}
1537 void attach(GtkBuilder *builder, const char *table_ID, const char *label_ID,
1538 int row);
1539 time64 get_date_time() { return gnc_date_edit_get_date_end(GNC_DATE_EDIT(m_edit)); }
1540 void connect(GCallback, gpointer);
1541};
1542
1543void
1544GncDateEdit::attach(GtkBuilder *builder, const char *table_ID,
1545 const char *label_ID, int row)
1546{
1547 auto table = get_widget(builder, table_ID);
1548 auto label = get_widget (builder, label_ID);
1549 gtk_grid_attach(GTK_GRID(table), m_edit, 1, row, 1, 1);
1550 gtk_widget_show(m_edit);
1551 gnc_date_make_mnemonic_target (GNC_DATE_EDIT(m_edit), label);
1552}
1553
1554void
1555GncDateEdit::connect(GCallback cb, gpointer data)
1556{
1557 g_signal_connect(m_edit, "date_changed", cb, data);
1558}
1559
1564{
1565 GtkWidget *m_edit;
1566public:
1567 GncAmountEdit (GtkBuilder *builder, gnc_commodity *commodity);
1568 void attach (GtkBuilder *builder, const char *table_id,
1569 const char *label_ID, int row);
1570 GtkWidget* widget() {
1571 return gnc_amount_edit_gtk_entry(GNC_AMOUNT_EDIT(m_edit));
1572 }
1573 gnc_numeric get ();
1574 void connect (GCallback cb, gpointer data);
1575 void set_owner (gpointer obj);
1576};
1577
1578static void
1579value_changed_cb (GtkWidget* widget, StockTransactionEntry* entry)
1580{
1581 g_return_if_fail(GNC_IS_AMOUNT_EDIT(widget));
1582 gnc_numeric value;
1583 auto invalid{gnc_amount_edit_expr_is_valid(GNC_AMOUNT_EDIT(widget),
1584 &value, true, nullptr)};
1585 entry->set_value(invalid ? gnc_numeric_error(GNC_ERROR_ARG) : value);
1586}
1587
1588GncAmountEdit::GncAmountEdit (GtkBuilder *builder, gnc_commodity *commodity) :
1589 m_edit{gnc_amount_edit_new()}
1590{
1591 // shares amount
1592 auto info = gnc_commodity_print_info(commodity, true);
1593 gnc_amount_edit_set_evaluate_on_enter(GNC_AMOUNT_EDIT(m_edit), TRUE);
1594 gnc_amount_edit_set_print_info(GNC_AMOUNT_EDIT(m_edit), info);
1595}
1596
1597void
1598GncAmountEdit::attach (GtkBuilder *builder, const char *table_ID,
1599 const char* label_ID, int row)
1600{
1601 auto table = get_widget(builder, table_ID);
1602 auto label = get_widget(builder, label_ID);
1603 gtk_grid_attach(GTK_GRID(table), m_edit, 1, row, 1, 1);
1604 gtk_widget_show(m_edit);
1605 gnc_amount_edit_make_mnemonic_target(GNC_AMOUNT_EDIT(m_edit), label);
1606}
1607
1608gnc_numeric
1609GncAmountEdit::get ()
1610{
1611 gnc_numeric amt;
1612 if (!gnc_amount_edit_expr_is_valid (GNC_AMOUNT_EDIT(m_edit), &amt, true, nullptr))
1613 return amt;
1615}
1616
1617void
1618GncAmountEdit::connect (GCallback cb, gpointer data)
1619{
1620 g_signal_connect(m_edit, "changed", cb, data);
1621}
1622
1623void
1624GncAmountEdit::set_owner(gpointer obj)
1625{
1626 g_object_set_data(G_OBJECT (m_edit), "owner", obj);
1627}
1628
1629using AccountTypeList = std::vector<GNCAccountType>;
1630
1635{
1636 GtkWidget* m_selector;
1637public:
1638 GncAccountSelector (GtkBuilder *builder, AccountTypeList types,
1639 gnc_commodity *currency, Account *default_acct);
1640 void attach (GtkBuilder *builder, const char *table_id,
1641 const char *label_ID, int row);
1642 void connect (StockTransactionEntry*);
1643 void set (Account *acct) { gnc_account_sel_set_account (GNC_ACCOUNT_SEL (m_selector), acct, TRUE); }
1644 void set_sensitive(bool sensitive);
1645 Account *get () { return gnc_account_sel_get_account (GNC_ACCOUNT_SEL (m_selector)); }
1646};
1647
1648static void
1649gnc_account_sel_changed_cb (GtkWidget* widget, StockTransactionEntry* entry)
1650{
1651 g_return_if_fail (GNC_IS_ACCOUNT_SEL (widget));
1652 entry->set_account(gnc_account_sel_get_account (GNC_ACCOUNT_SEL (widget)));
1653}
1654
1655GncAccountSelector::GncAccountSelector (GtkBuilder *builder, AccountTypeList types,
1656 gnc_commodity *currency, Account *default_acct) :
1657 m_selector{gnc_account_sel_new ()}
1658{
1659 auto accum = [](auto a, auto b) { return g_list_prepend(a, (gpointer)b); };
1660 auto null_glist = static_cast<GList *>(nullptr);
1661 auto acct_list = std::accumulate(types.begin(), types.end(), null_glist, accum);
1662 auto curr_list = accum(null_glist, currency);
1663 gnc_account_sel_set_new_account_ability(GNC_ACCOUNT_SEL(m_selector), true);
1664 gnc_account_sel_set_acct_filters(GNC_ACCOUNT_SEL(m_selector), acct_list, curr_list);
1665 gnc_account_sel_set_default_new_commodity(GNC_ACCOUNT_SEL(m_selector), currency);
1666 gnc_account_sel_set_new_account_modal (GNC_ACCOUNT_SEL(m_selector), true);
1667 if (default_acct)
1668 gnc_account_sel_set_account (GNC_ACCOUNT_SEL(m_selector), default_acct, true);
1669 g_list_free(acct_list);
1670 g_list_free(curr_list);
1671}
1672
1673void
1674GncAccountSelector::attach (GtkBuilder *builder, const char *table_ID,
1675 const char *label_ID, int row)
1676{
1677 auto table = get_widget(builder, table_ID);
1678 auto label = get_widget(builder, label_ID);
1679 gtk_grid_attach(GTK_GRID(table), m_selector, 1, row, 1, 1);
1680 gtk_widget_show(m_selector);
1681 gtk_label_set_mnemonic_widget(GTK_LABEL(label), m_selector);
1682}
1683
1684void
1685GncAccountSelector::connect (StockTransactionEntry* entry)
1686{
1687 g_signal_connect(m_selector, "account_sel_changed", G_CALLBACK (gnc_account_sel_changed_cb), entry);
1688}
1689
1690void
1691GncAccountSelector::set_sensitive(bool sensitive)
1692{
1693 gtk_widget_set_sensitive(m_selector, sensitive);
1694}
1695
1696
1708static void
1709assistant_page_set_focus(GtkWidget* page, [[maybe_unused]]GtkDirectionType type, GtkWidget* entry)
1710{
1711 gtk_widget_grab_focus(entry);
1712 g_signal_handlers_disconnect_by_data(page, entry);
1713}
1745 // transaction type page
1746 GtkWidget * m_page;
1747 GtkWidget * m_type;
1748 GtkWidget * m_explanation;
1749public:
1750 PageTransType(GtkBuilder *builder);
1751 void prepare(StockAssistantModel* model);
1752 int get_transaction_type_index ();
1753 void set_transaction_types (const TxnTypeVec& txn_types);
1760 void set_txn_type_explanation (const gchar *txt);
1761 void connect (StockAssistantModel *model);
1762 void change_txn_type (StockAssistantModel *model);
1763};
1764
1765PageTransType::PageTransType(GtkBuilder *builder)
1766 : m_page(get_widget(builder, "transaction_type_page")),
1767 m_type(get_widget(builder, "transaction_type_page_combobox")),
1768 m_explanation(get_widget(builder, "transaction_type_page_explanation"))
1769{
1770 g_object_set_data(G_OBJECT(m_type), "owner", this);
1771}
1772
1773static void
1774page_trans_type_changed_cb (GtkWidget* widget, StockAssistantModel *model)
1775{
1776 auto me = static_cast<PageTransType *>(g_object_get_data (G_OBJECT (widget), "owner"));
1777 g_return_if_fail (me);
1778 me->change_txn_type (model);
1779}
1780
1781void
1782PageTransType::prepare(StockAssistantModel *model)
1783{
1784 const auto& txn_types{model->get_txn_types()};
1785 if (!txn_types)
1786 return;
1787
1788 set_transaction_types(txn_types.value());
1789 change_txn_type (model);
1790 g_signal_connect(m_page, "focus", G_CALLBACK(assistant_page_set_focus), m_type);
1791}
1792
1793int
1794PageTransType::get_transaction_type_index ()
1795{
1796 return gtk_combo_box_get_active (GTK_COMBO_BOX (m_type));
1797}
1798
1799void
1800PageTransType::set_transaction_types (const TxnTypeVec& txn_types)
1801{
1802 auto combo = GTK_COMBO_BOX_TEXT (m_type);
1803 gtk_combo_box_text_remove_all (combo);
1804 std::for_each (txn_types.begin(), txn_types.end(),
1805 [&combo](const auto& it)
1806 { gtk_combo_box_text_append_text (combo, _(it.friendly_name)); });
1807 gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
1808}
1809
1810void
1812{
1813 gtk_label_set_text (GTK_LABEL (this->m_explanation), txt);
1814}
1815
1816void
1817PageTransType::change_txn_type (StockAssistantModel *model)
1818{
1819 auto type_idx = get_transaction_type_index();
1820 if (type_idx < 0) // combo isn't initialized yet.
1821 return;
1822
1823 if (!model->set_txn_type(type_idx))
1824 return;
1825 auto txn_type{model->txn_type()};
1826 set_txn_type_explanation (_(txn_type->explanation));
1827}
1828
1829void
1830PageTransType::connect(StockAssistantModel *model)
1831{
1832 g_signal_connect(m_type, "changed",
1833 G_CALLBACK (page_trans_type_changed_cb), model);
1834}
1835
1842{
1843 // transaction details page
1844 GtkWidget *m_page;
1845 GncDateEdit m_date;
1846 GtkWidget *m_description;
1847public:
1848 PageTransDeets (GtkBuilder *builder);
1849 time64 get_date_time () { return m_date.get_date_time(); }
1850 const char* get_description () { return gtk_entry_get_text (GTK_ENTRY (m_description)); }
1851 void connect (StockAssistantModel*);
1852 void prepare(StockAssistantModel*);
1853};
1854
1855PageTransDeets::PageTransDeets (GtkBuilder *builder) :
1856 m_page (get_widget (builder, "transaction_details_page")),
1857 m_date (builder),
1858 m_description (get_widget (builder, "transaction_description_entry"))
1859{
1860 m_date.attach(builder, "transaction_details_table", "transaction_date_label", 0);
1861}
1862
1863void
1864PageTransDeets::connect(StockAssistantModel* model)
1865{
1866 m_date.connect(G_CALLBACK (stock_assistant_model_date_changed_cb),
1867 static_cast<void*>(model));
1868 g_signal_connect(m_description, "changed",
1869 G_CALLBACK (stock_assistant_model_description_changed_cb),
1870 static_cast<void*>(model));
1871}
1872
1873void
1874PageTransDeets::prepare(StockAssistantModel* model)
1875{
1876 model->set_transaction_date(get_date_time());
1877 model->set_transaction_desc(get_description());
1878 g_signal_connect(m_page, "focus", G_CALLBACK(assistant_page_set_focus), m_description);
1879}
1880
1891{
1892 // stock amount page
1893 GtkWidget * m_page;
1894 GtkWidget * m_title;
1895 GtkWidget * m_prev_amount;
1896 GtkWidget * m_next_amount;
1897 GtkWidget * m_next_amount_label;
1898 GncAmountEdit m_amount;
1899 GtkWidget * m_amount_label;
1900public:
1901 PageStockAmount (GtkBuilder *builder, Account* account);
1902 void prepare (StockTransactionEntry*);
1903 gnc_numeric get_stock_amount () { return m_amount.get(); }
1904 void set_stock_amount (std::string new_amount_str);
1905 void connect(StockTransactionEntry* entry);
1906};
1907
1908PageStockAmount::PageStockAmount (GtkBuilder *builder, Account* account) :
1909 m_page (get_widget (builder, "stock_amount_page")),
1910 m_title (get_widget (builder, "stock_amount_title")),
1911 m_prev_amount (get_widget (builder, "prev_balance_amount")),
1912 m_next_amount (get_widget (builder, "next_balance_amount")),
1913 m_next_amount_label (get_widget (builder, "next_balance_label")),
1914 m_amount (builder, xaccAccountGetCommodity(account)),
1915 m_amount_label (get_widget (builder, "stock_amount_label"))
1916{
1917 m_amount.attach (builder, "stock_amount_table", "stock_amount_label", 1);
1918}
1919
1920void
1921PageStockAmount::prepare (StockTransactionEntry* entry)
1922{
1923 gtk_label_set_text_with_mnemonic
1924 (GTK_LABEL (m_amount_label),
1925 entry->input_new_balance() ? _("Ne_w Balance") : _("_Shares"));
1926 gtk_label_set_text
1927 (GTK_LABEL (m_next_amount_label),
1928 entry->input_new_balance() ? _("Ratio") : _("Next Balance"));
1929 gtk_label_set_text
1930 (GTK_LABEL (m_title),
1931 entry->input_new_balance() ?
1932 _("Enter the new balance of shares after the stock split.") :
1933 _("Enter the number of shares you gained or lost in the transaction."));
1934 gtk_label_set_text (GTK_LABEL (m_prev_amount), entry->print_amount(entry->get_balance()));
1935 if (!gnc_numeric_check(get_stock_amount()))
1936 entry->set_amount(get_stock_amount());
1937 set_stock_amount(entry->amount_str_for_display());
1938 g_signal_connect(m_page, "focus", G_CALLBACK(assistant_page_set_focus), m_amount.widget());
1939}
1940
1941static void
1942page_stock_amount_changed_cb(GtkWidget *widget, StockTransactionEntry* entry)
1943{
1944 auto me = static_cast<PageStockAmount*>(g_object_get_data (G_OBJECT (widget), "owner"));
1945 entry->set_amount(me->get_stock_amount());
1946 me->set_stock_amount(entry->amount_str_for_display());
1947}
1948
1949void
1950PageStockAmount::connect(StockTransactionEntry* entry)
1951{
1952 m_amount.connect(G_CALLBACK (page_stock_amount_changed_cb), entry);
1953 m_amount.set_owner(static_cast<gpointer>(this));
1954}
1955
1956void
1957PageStockAmount::set_stock_amount (std::string new_amount_str)
1958{
1959 gtk_label_set_text (GTK_LABEL(m_next_amount), new_amount_str.c_str());
1960}
1961
1967{
1968 // stock value page
1969 GtkWidget * m_page;
1970 GncAmountEdit m_value;
1971 GtkWidget * m_price;
1972 GtkWidget * m_memo;
1973public:
1974 PageStockValue (GtkBuilder *builder, Account* account);
1975 const char* get_memo ();
1976 void connect(StockTransactionEntry* entry);
1977 void prepare(StockTransactionEntry* entry);
1978 GncAmountEdit& value_edit() { return m_value; }
1979 void set_price(const gchar *val);
1980};
1981
1982static void
1983page_stock_value_changed_cb(GtkWidget *widget, StockTransactionEntry* entry)
1984{
1985 auto me = static_cast<PageStockValue*>(g_object_get_data (G_OBJECT (widget), "owner"));
1986 entry->set_value (me->value_edit().get());
1987 me->set_price(entry->print_price());
1988}
1989
1990PageStockValue::PageStockValue(GtkBuilder *builder, Account* account)
1991 : m_page(get_widget(builder, "stock_value_page")),
1992 m_value(builder, gnc_account_get_currency_or_parent(account)),
1993 m_price(get_widget(builder, "stock_price_amount")),
1994 m_memo(get_widget(builder, "stock_memo_entry"))
1995{
1996 m_value.attach(builder, "stock_value_table", "stock_value_label", 0);
1997}
1998
1999void
2000PageStockValue::connect(StockTransactionEntry* entry)
2001{
2002 m_value.connect(G_CALLBACK (page_stock_value_changed_cb), entry);
2003 m_value.set_owner (static_cast<gpointer>(this));
2004 g_signal_connect (m_memo, "changed", G_CALLBACK(text_entry_changed_cb), entry);
2005}
2006
2007void
2008PageStockValue::prepare(StockTransactionEntry* entry)
2009{
2010 entry->set_memo(get_memo());
2011 if (!gnc_numeric_check(m_value.get()))
2012 entry->set_value(m_value.get());
2013 set_price(entry->print_price());
2014 g_signal_connect(m_page, "focus", G_CALLBACK(assistant_page_set_focus), m_value.widget());
2015}
2016
2017const char *
2018PageStockValue::get_memo()
2019{
2020 return gtk_entry_get_text(GTK_ENTRY (m_memo));
2021}
2022
2023void
2024PageStockValue::set_price (const gchar *val)
2025{
2026 gtk_label_set_text(GTK_LABEL(this->m_price), val);
2027};
2028
2035{
2036 // cash page
2037 GtkWidget * m_page;
2038 GncAccountSelector m_account;
2039 GtkWidget * m_memo;
2040 GncAmountEdit m_value;
2041public:
2042 PageCash (GtkBuilder *builder, Account* account);
2043 void connect(StockTransactionEntry* entry);
2044 void prepare(StockTransactionEntry* entry);
2045 const char* get_memo();
2046};
2047
2048PageCash::PageCash(GtkBuilder *builder, Account* account)
2049 : m_page(get_widget(builder, "cash_details_page")),
2050 m_account(builder, {ACCT_TYPE_ASSET, ACCT_TYPE_BANK},
2052 xaccAccountGetAssociatedAccount (account, PROCEEDS_KVP_TAG)),
2053 m_memo(get_widget(builder, "cash_memo_entry")),
2054 m_value(builder, gnc_account_get_currency_or_parent(account))
2055{
2056 m_account.attach (builder, "cash_table", "cash_account_label", 0);
2057 m_value.attach (builder, "cash_table", "cash_label", 1);
2058}
2059
2060void
2061PageCash::connect(StockTransactionEntry* entry)
2062{
2063 m_account.connect(entry);
2064 g_signal_connect(m_memo, "changed", G_CALLBACK(text_entry_changed_cb), entry);
2065 m_value.connect(G_CALLBACK(value_changed_cb), entry);
2066}
2067
2068void
2069PageCash::prepare(StockTransactionEntry* entry)
2070{
2071 entry->set_memo(get_memo());
2072 if (!gnc_numeric_check(m_value.get()))
2073 entry->set_value(m_value.get());
2074 entry->set_account(m_account.get());
2075 g_signal_connect(m_page, "focus", G_CALLBACK(assistant_page_set_focus), m_value.widget());
2076}
2077
2078const char *
2079PageCash::get_memo()
2080{
2081 return gtk_entry_get_text(GTK_ENTRY (m_memo));
2082}
2083
2090{
2091 // fees page
2092 GtkWidget * m_page;
2093 GtkWidget * m_capitalize;
2094 GncAccountSelector m_account;
2095 GtkWidget * m_memo;
2096 GncAmountEdit m_value;
2097 Account* m_stock_account;
2098public:
2099 PageFees (GtkBuilder *builder, Account* account);
2100 void connect(StockTransactionEntry*);
2101 bool get_capitalize_fees ();
2102 const char* get_memo();
2103 void set_capitalize_fees (bool state);
2104 void set_account (Account *acct) { m_account.set(acct); }
2105 Account* stock_account() { return m_stock_account; }
2106 void update_fees_acct_sensitive (bool sensitive);
2107 void prepare(StockTransactionEntry*);
2108};
2109
2110PageFees::PageFees(GtkBuilder *builder, Account* account)
2111 : m_page(get_widget(builder, "fees_details_page")),
2112 m_capitalize(
2113 get_widget(builder, "capitalize_fees_checkbutton")),
2114 m_account(builder, {ACCT_TYPE_EXPENSE}, gnc_account_get_currency_or_parent(account),
2115 xaccAccountGetAssociatedAccount (account, FEES_KVP_TAG)),
2116 m_memo(get_widget(builder, "fees_memo_entry")),
2117 m_value(builder, gnc_account_get_currency_or_parent(account)),
2118 m_stock_account(account)
2119{
2120 m_account.attach (builder, "fees_table", "fees_account_label", 1);
2121 m_value.attach(builder, "fees_table", "fees_label", 2);
2122}
2123
2124bool
2125PageFees::get_capitalize_fees()
2126{
2127 return gtk_toggle_button_get_active(
2128 GTK_TOGGLE_BUTTON(m_capitalize));
2129}
2130
2131const char *
2132PageFees::get_memo()
2133{
2134 return gtk_entry_get_text(GTK_ENTRY (m_memo));
2135}
2136
2137void
2138PageFees::set_capitalize_fees(bool state)
2139{
2140 gtk_toggle_button_set_active(
2141 GTK_TOGGLE_BUTTON(m_capitalize), state);
2142}
2143
2144void
2145PageFees::update_fees_acct_sensitive(bool sensitive)
2146{
2147 m_account.set_sensitive(sensitive);
2148}
2149
2150static void
2151capitalize_fees_toggled_cb (GtkWidget *widget, StockTransactionEntry *entry)
2152{
2153 g_return_if_fail (entry);
2154 auto me = static_cast<PageFees *>(g_object_get_data (G_OBJECT (widget), "owner"));
2155 g_return_if_fail (me);
2156 bool cap = me->get_capitalize_fees();
2157 entry->set_capitalize(cap);
2158 if (cap)
2159 entry->set_account(me->stock_account());
2160 me->update_fees_acct_sensitive(!cap);
2161}
2162
2163void
2164PageFees::connect(StockTransactionEntry* entry)
2165{
2166 m_account.connect(entry);
2167 g_signal_connect(m_memo, "changed", G_CALLBACK(text_entry_changed_cb), entry);
2168 m_value.connect(G_CALLBACK(value_changed_cb), entry);
2169 g_object_set_data(G_OBJECT (m_capitalize), "owner", this);
2170 g_signal_connect (m_capitalize, "toggled", G_CALLBACK (capitalize_fees_toggled_cb), entry);
2171}
2172
2173void
2174PageFees::prepare(StockTransactionEntry* entry)
2175{
2176 set_capitalize_fees (entry->do_capitalize());
2177 entry->set_memo(get_memo());
2178 if (!gnc_numeric_check(m_value.get()))
2179 entry->set_value (m_value.get());
2180 entry->set_account(m_account.get());
2181 g_signal_connect(m_page, "focus", G_CALLBACK(assistant_page_set_focus), m_value.widget());
2182}
2183
2187{
2188 // dividend page
2189 GtkWidget *m_page;
2190 GncAccountSelector m_account;
2191 GtkWidget *m_memo;
2192 GncAmountEdit m_value;
2193public:
2194 PageDividend (GtkBuilder *builder, Account* account);
2195 void connect(StockTransactionEntry*);
2196 void prepare(StockTransactionEntry*);
2197 const char* get_memo();
2198};
2199
2200PageDividend::PageDividend(GtkBuilder *builder, Account* account)
2201 : m_page(get_widget(builder, "dividend_details_page")),
2202 m_account(builder, {ACCT_TYPE_INCOME}, gnc_account_get_currency_or_parent(account),
2203 xaccAccountGetAssociatedAccount (account, DIVIDEND_KVP_TAG)),
2204 m_memo(get_widget(builder, "dividend_memo_entry")),
2205 m_value(builder, gnc_account_get_currency_or_parent(account))
2206{
2207 m_account.attach(builder, "dividend_table", "dividend_account_label", 0);
2208 m_value.attach(builder, "dividend_table", "dividend_label", 1);
2209}
2210
2211
2212void
2213PageDividend::connect(StockTransactionEntry* entry)
2214{
2215 m_account.connect(entry);
2216 g_signal_connect(m_memo, "changed", G_CALLBACK(text_entry_changed_cb), entry);
2217 m_value.connect(G_CALLBACK(value_changed_cb), entry);
2218}
2219
2220void
2221PageDividend::prepare(StockTransactionEntry* entry)
2222{
2223 entry->set_memo(get_memo());
2224 if (!gnc_numeric_check(m_value.get()))
2225 entry->set_value(m_value.get());
2226 entry->set_account(m_account.get());
2227 g_signal_connect(m_page, "focus", G_CALLBACK(assistant_page_set_focus), m_value.widget());
2228}
2229
2230const char *
2231PageDividend::get_memo()
2232{
2233 return gtk_entry_get_text(GTK_ENTRY (m_memo));
2234}
2235
2237{
2238 // capgains page
2239 GtkWidget * m_page;
2240 GncAccountSelector m_account;
2241 GtkWidget * m_memo;
2242 GncAmountEdit m_value;
2243public:
2244 PageCapGain (GtkBuilder *builder, Account* account);
2245 void connect(StockTransactionEntry* entry);
2246 void prepare(StockTransactionEntry* entry);
2247 const char* get_memo();
2248};
2249
2250PageCapGain::PageCapGain (GtkBuilder *builder, Account* account) :
2251 m_page (get_widget (builder, "capgains_details_page")),
2252 m_account (builder, { ACCT_TYPE_INCOME }, gnc_account_get_currency_or_parent(account),
2253 xaccAccountGetAssociatedAccount (account, CAPGAINS_KVP_TAG)),
2254 m_memo (get_widget (builder, "capgains_memo_entry")),
2255 m_value (builder, gnc_account_get_currency_or_parent(account))
2256{
2257 m_account.attach(builder, "capgains_table", "capgains_account_label", 0);
2258 m_value.attach(builder, "capgains_table", "capgains_label", 1);
2259}
2260
2261const char *
2262PageCapGain::get_memo()
2263{
2264 return gtk_entry_get_text(GTK_ENTRY (m_memo));
2265}
2266
2267
2268void
2269PageCapGain::connect(StockTransactionEntry*entry)
2270{
2271 m_account.connect(entry);
2272 g_signal_connect(m_memo, "changed", G_CALLBACK(text_entry_changed_cb), entry);
2273 m_value.connect(G_CALLBACK(value_changed_cb), entry);
2274}
2275
2276void
2277PageCapGain::prepare(StockTransactionEntry* entry)
2278{
2279 entry->set_memo(get_memo());
2280 if (gnc_numeric_check(m_value.get()))
2281 entry->set_value(m_value.get());
2282 entry->set_account(m_account.get());
2283 g_signal_connect(m_page, "focus", G_CALLBACK(assistant_page_set_focus), m_value.widget());
2284}
2285
2286
2287enum split_cols
2288{
2289 SPLIT_COL_ACCOUNT = 0,
2290 SPLIT_COL_MEMO,
2291 SPLIT_COL_TOOLTIP,
2292 SPLIT_COL_DEBIT,
2293 SPLIT_COL_CREDIT,
2294 SPLIT_COL_UNITS,
2295 SPLIT_COL_UNITS_COLOR,
2296 NUM_SPLIT_COLS
2297};
2298
2299/* Displays a summary of the transactions as a list. */
2301{
2302 GtkWidget *m_treeview;
2303public:
2304 GncFinishTreeview(GtkBuilder *builder);
2309 void load(const EntryVec& list_of_splits);
2310};
2311
2312GncFinishTreeview::GncFinishTreeview (GtkBuilder *builder) :
2313 m_treeview{get_widget (builder, "transaction_view")}
2314{
2315 auto view = GTK_TREE_VIEW (m_treeview);
2316 gtk_tree_view_set_grid_lines (GTK_TREE_VIEW(view), gnc_tree_view_get_grid_lines_pref ());
2317
2318 auto store = gtk_list_store_new (NUM_SPLIT_COLS, G_TYPE_STRING, G_TYPE_STRING,
2319 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
2320 G_TYPE_STRING, G_TYPE_STRING);
2321 gtk_tree_view_set_model(view, GTK_TREE_MODEL(store));
2322 gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view),
2323 GTK_SELECTION_NONE);
2324 g_object_unref(store);
2325
2326 auto renderer = gtk_cell_renderer_text_new();
2327 auto column = gtk_tree_view_column_new_with_attributes
2328 (_("Account"), renderer, "text", SPLIT_COL_ACCOUNT, nullptr);
2329 gtk_tree_view_append_column(view, column);
2330
2331 renderer = gtk_cell_renderer_text_new();
2332 g_object_set (renderer, "ellipsize", PANGO_ELLIPSIZE_END, nullptr);
2333 column = gtk_tree_view_column_new_with_attributes
2334 (_("Memo"), renderer, "text", SPLIT_COL_MEMO, nullptr);
2335 gtk_tree_view_column_set_expand (column, true);
2336 gtk_tree_view_append_column(view, column);
2337
2338 renderer = gtk_cell_renderer_text_new();
2339 gtk_cell_renderer_set_alignment (renderer, 1.0, 0.5);
2340 gtk_cell_renderer_set_padding (renderer, 5, 0);
2341 column = gtk_tree_view_column_new_with_attributes
2342 (_("Debit"), renderer, "text", SPLIT_COL_DEBIT, nullptr);
2343 gtk_tree_view_append_column(view, column);
2344
2345 renderer = gtk_cell_renderer_text_new();
2346 gtk_cell_renderer_set_alignment (renderer, 1.0, 0.5);
2347 gtk_cell_renderer_set_padding (renderer, 5, 0);
2348 column = gtk_tree_view_column_new_with_attributes
2349 (_("Credit"), renderer, "text", SPLIT_COL_CREDIT, nullptr);
2350 gtk_tree_view_append_column(view, column);
2351
2352 renderer = gtk_cell_renderer_text_new();
2353 gtk_cell_renderer_set_alignment (renderer, 1.0, 0.5);
2354 gtk_cell_renderer_set_padding (renderer, 5, 0);
2355 column = gtk_tree_view_column_new_with_attributes
2356 (_("Units"), renderer,
2357 "text", SPLIT_COL_UNITS,
2358 "foreground", SPLIT_COL_UNITS_COLOR,
2359 nullptr);
2360 gtk_tree_view_append_column(view, column);
2361 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(m_treeview),
2362 SPLIT_COL_TOOLTIP);}
2363
2364void
2365GncFinishTreeview::load(const EntryVec& list_of_splits)
2366{
2367 auto gtv = GTK_TREE_VIEW(m_treeview);
2368 bool negative_in_red = gnc_prefs_get_bool (GNC_PREFS_GROUP_GENERAL,
2369 GNC_PREF_NEGATIVE_IN_RED);
2370 auto list = GTK_LIST_STORE(gtk_tree_view_get_model(gtv));
2371 gtk_list_store_clear(list);
2372 for (const auto &entry : list_of_splits) {
2373 GtkTreeIter iter;
2374 auto memo{entry->memo()};
2375 auto tooltip = (memo && *memo ?
2376 g_markup_escape_text(memo, -1) : strdup(""));
2377 /* print_value and print_amount rely on xaccPrintAmount that
2378 * uses static memory so the result needs to be copied
2379 * immediately or the second call overwrites the results of
2380 * the first one.
2381 */
2382 auto char2str{[](const char* str) -> std::string {
2383 return std::string{ str ? str : "" }; }};
2384 auto amount{char2str(entry->print_value())};
2385 auto units{char2str(entry->has_amount() ?
2386 entry->print_amount(entry->debit_side() ? entry->amount() :
2387 gnc_numeric_neg(entry->amount())) : "")};
2388 auto units_in_red{negative_in_red && !entry->debit_side()};
2389 gtk_list_store_append(list, &iter);
2390 gtk_list_store_set(
2391 list, &iter,
2392 SPLIT_COL_ACCOUNT,
2393 entry->print_account(), SPLIT_COL_MEMO,
2394 entry->memo(), SPLIT_COL_TOOLTIP, tooltip, SPLIT_COL_DEBIT,
2395 entry->debit_side() ? amount.c_str() : nullptr,
2396 SPLIT_COL_CREDIT,
2397 entry->debit_side() ? nullptr : amount.c_str(),
2398 SPLIT_COL_UNITS, units.c_str(),
2399 SPLIT_COL_UNITS_COLOR, units_in_red ? "red" : nullptr, -1);
2400 g_free(tooltip);
2401 }
2402}
2403
2408{
2409 // finish page
2410 GtkWidget * m_page;
2411 GncFinishTreeview m_view;
2412 GtkWidget * m_summary;
2413public:
2414 PageFinish (GtkBuilder *builder);
2415 void prepare (GtkWidget *window, StockAssistantModel *model);
2416};
2417
2418PageFinish::PageFinish (GtkBuilder *builder) :
2419 m_page (get_widget (builder, "finish_page")), m_view (builder),
2420 m_summary (get_widget (builder, "finish_summary")) {}
2421
2422
2423void
2424PageFinish::prepare (GtkWidget *window, StockAssistantModel *model)
2425{
2426 auto [success, summary, list_of_splits] = model->generate_list_of_splits ();
2427 m_view.load(list_of_splits);
2428 gtk_label_set_text(GTK_LABEL(m_summary), summary.c_str());
2429 gtk_assistant_set_page_complete(GTK_ASSISTANT(window), m_page, success);
2430}
2431
2432enum assistant_pages
2433{
2434 PAGE_INTRO = 0,
2435 PAGE_TRANSACTION_DETAILS,
2436 PAGE_TRANSACTION_TYPE,
2437 PAGE_STOCK_AMOUNT,
2438 PAGE_STOCK_VALUE,
2439 PAGE_CASH,
2440 PAGE_FEES,
2441 PAGE_DIVIDEND,
2442 PAGE_CAPGAINS,
2443 PAGE_FINISH
2444};
2445
2448 GtkWidget * m_window;
2449
2450 PageTransType m_type_page;
2451 PageTransDeets m_deets_page;
2452 PageStockAmount m_stock_amount_page;
2453 PageStockValue m_stock_value_page;
2454 PageCash m_cash_page;
2455 PageFees m_fees_page;
2456 PageDividend m_dividend_page;
2457 PageCapGain m_capgain_page;
2458 PageFinish m_finish_page;
2459public:
2460 StockAssistantView(GtkBuilder *builder, Account* account, GtkWidget *parent);
2474 void prepare(int page, StockAssistantModel*);
2475 GtkWidget* window() { return m_window; }
2476};
2477
2478StockAssistantView::StockAssistantView (GtkBuilder *builder, Account* account, GtkWidget *parent) :
2479 m_window (get_widget (builder, "stock_transaction_assistant")), m_type_page(builder), m_deets_page(builder),
2480 m_stock_amount_page (builder, account), m_stock_value_page (builder, account), m_cash_page (builder, account),
2481 m_fees_page (builder, account), m_dividend_page (builder, account), m_capgain_page (builder, account),
2482 m_finish_page (builder)
2483{
2484 // Set the name for this assistant so it can be easily manipulated with css
2485 gtk_widget_set_name (GTK_WIDGET(m_window), "gnc-id-assistant-stock-transaction");
2486 gtk_window_set_transient_for (GTK_WINDOW (m_window), GTK_WINDOW(parent));
2487 gnc_window_adjust_for_screen (GTK_WINDOW(m_window));
2488 gnc_restore_window_size (GNC_PREFS_GROUP, GTK_WINDOW(m_window),
2489 GTK_WINDOW(parent));
2490 gtk_widget_show_all (m_window);
2491 DEBUG ("StockAssistantView constructor\n");
2492};
2493
2494StockAssistantView::~StockAssistantView()
2495{
2496 gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(m_window));
2497 gtk_widget_destroy (m_window);
2498 DEBUG ("StockAssistantView destructor\n");
2499};
2500
2507static gint
2508forward_page_func (gint current_page, void* data)
2509{
2510 auto model{static_cast<StockAssistantModel*>(data)};
2511 current_page++;
2512 if (!model->txn_type_valid())
2513 return current_page;
2514
2515 if (!model->stock_entry()->has_amount() && current_page == PAGE_STOCK_AMOUNT)
2516 current_page++;
2517 if (!model->stock_entry()->enabled() && current_page == PAGE_STOCK_VALUE)
2518 current_page++;
2519 if (!model->cash_entry()->enabled() && current_page == PAGE_CASH)
2520 current_page++;
2521 if (!model->fees_entry()->enabled() && current_page == PAGE_FEES)
2522 current_page++;
2523 if (!model->dividend_entry()->enabled() && current_page == PAGE_DIVIDEND)
2524 current_page++;
2525 if (!model->capgains_entry()->enabled() && current_page == PAGE_CAPGAINS)
2526 current_page++;
2527
2528 return current_page;
2529}
2530
2531void
2533{
2534 m_type_page.connect(model);
2535 m_deets_page.connect(model);
2536 m_stock_amount_page.connect(model->stock_entry());
2537 m_stock_value_page.connect(model->stock_entry());
2538 m_cash_page.connect(model->cash_entry());
2539 m_fees_page.connect(model->fees_entry());
2540 m_dividend_page.connect(model->dividend_entry());
2541 m_capgain_page.connect(model->capgains_entry());
2542
2543 gtk_assistant_set_forward_page_func (GTK_ASSISTANT(m_window),
2544 (GtkAssistantPageFunc)forward_page_func,
2545 model, nullptr);
2546}
2547
2548void
2550{
2551 g_return_if_fail (page < PAGE_STOCK_AMOUNT || model->txn_type_valid());
2552 switch (page)
2553 {
2554 case PAGE_TRANSACTION_TYPE:
2555 if (!model->maybe_reset_txn_types())
2556 break;
2557 m_type_page.prepare(model);
2558 break;
2559 case PAGE_TRANSACTION_DETAILS:
2560 m_deets_page.prepare(model);
2561 break;
2562 case PAGE_STOCK_AMOUNT:
2563 {
2564 m_stock_amount_page.prepare(model->stock_entry());
2565 break;
2566 }
2567 case PAGE_STOCK_VALUE:
2568 m_stock_value_page.prepare(model->stock_entry());
2569 break;
2570 case PAGE_CASH:
2571 m_cash_page.prepare(model->cash_entry());
2572 break;
2573 case PAGE_FEES:
2574 {
2575 m_fees_page.prepare(model->fees_entry());
2576 break;
2577 }
2578 case PAGE_DIVIDEND:
2579 m_dividend_page.prepare(model->dividend_entry());
2580 break;
2581 case PAGE_CAPGAINS:
2582 {
2583 m_capgain_page.prepare(model->capgains_entry());
2584 break;
2585 }
2586 case PAGE_FINISH:
2587 {
2588 m_finish_page.prepare (m_window, model);
2589 break;
2590 }
2591 default:
2592 break;
2593 }
2594}
2595
2601static void stock_account_destroyed_handler(QofInstance *inst, QofEventId event,
2602 void* handler_data, [[maybe_unused]]void* event_data);
2603
2605{
2606 std::unique_ptr<StockAssistantModel> m_model;
2607 StockAssistantView m_view;
2608 bool m_destroying = false;
2609 int m_qof_event_handler;
2610public:
2611 StockAssistantController (GtkWidget *parent, GtkBuilder* builder, Account* acct)
2612 : m_model{std::make_unique<StockAssistantModel>(acct)},
2613 m_view{builder, acct, parent},
2614 m_qof_event_handler{qof_event_register_handler(stock_account_destroyed_handler, this)}
2615 {
2616 connect_signals (builder);
2617 DEBUG ("StockAssistantController constructor\n");
2618 };
2620 void connect_signals(GtkBuilder *builder);
2621 void prepare(GtkAssistant* assistant, GtkWidget *page);
2622 void finish();
2623 bool destroying() { return m_destroying; }
2624 Account* model_account() { return m_model->account(); }
2625};
2626
2627static void stock_assistant_window_destroy_cb(GtkWidget *object, gpointer user_data);
2628static void close_handler (gpointer user_data);
2629
2630StockAssistantController::~StockAssistantController()
2631{
2632 m_destroying = true;
2633 gnc_unregister_gui_component_by_data (ASSISTANT_STOCK_TRANSACTION_CM_CLASS, this);
2634 qof_event_unregister_handler(m_qof_event_handler);
2635}
2636
2637void
2638StockAssistantController::connect_signals (GtkBuilder *builder)
2639{
2640 m_view.connect(m_model.get());
2641 gtk_builder_connect_signals (builder, this); //Stock Assistant View: cancel, close, prepare
2642 g_signal_connect (m_view.window(), "destroy",
2643 G_CALLBACK (stock_assistant_window_destroy_cb), this);
2644
2645
2646 auto component_id = gnc_register_gui_component
2647 (ASSISTANT_STOCK_TRANSACTION_CM_CLASS, nullptr, close_handler, this);
2648 gnc_gui_component_watch_entity_type (component_id, GNC_ID_ACCOUNT,
2649 QOF_EVENT_MODIFY | QOF_EVENT_DESTROY);
2650}
2651
2652void
2653StockAssistantController::prepare(GtkAssistant* assistant, GtkWidget* page)
2654{
2655 auto currentpage = gtk_assistant_get_current_page(assistant);
2656 m_view.prepare(currentpage, m_model.get());
2657}
2658
2659void
2660StockAssistantController::finish()
2661{
2662 g_return_if_fail (m_model->txn_type_valid());
2663
2664 gnc_suspend_gui_refresh ();
2665 auto [success, trans] = m_model->create_transaction();
2666 gnc_resume_gui_refresh ();
2667
2668 if (success && trans)
2669 {
2670 auto split = xaccTransFindSplitByAccount (trans, m_model->account());
2671 if (split)
2672 {
2673 auto page = gnc_plugin_page_register_new (m_model->account(), FALSE);
2674 gnc_main_window_open_page (nullptr, page);
2675 auto gsr = gnc_plugin_page_register_get_gsr (page);
2676 gnc_split_reg_raise (gsr);
2677
2678 if (gnc_split_reg_clear_filter_for_split (gsr, split))
2680
2681 gnc_split_reg_jump_to_split (gsr, split);
2682 }
2683 }
2684
2685 gnc_close_gui_component_by_data (ASSISTANT_STOCK_TRANSACTION_CM_CLASS, this);
2686}
2687
2688static void
2689stock_account_destroyed_handler(QofInstance *inst, QofEventId event,
2690 void* handler_data, [[maybe_unused]]void* event_data)
2691{
2692 auto controller{static_cast<StockAssistantController*>(handler_data)};
2693 if ((inst && inst != QOF_INSTANCE(controller->model_account())) || (event & QOF_EVENT_DESTROY) == 0 ||
2694 controller->destroying())
2695 return;
2696 delete controller;
2697}
2698
2699// These callbacks must be registered with the GtkAssistant so they can't be member functions.
2700/* The StockAssistantController manages the event handlers and user input. */
2701void
2702stock_assistant_prepare_cb (GtkAssistant *assistant, GtkWidget *page,
2703 gpointer user_data)
2704{
2705 auto info = static_cast<StockAssistantController*>(user_data);
2706 info->prepare(assistant, page);
2707}
2708
2709
2710static void
2711stock_assistant_window_destroy_cb (GtkWidget *object, gpointer user_data) //crashes before this gets called.
2712{
2713 auto controller = static_cast<StockAssistantController*>(user_data);
2714 if (controller->destroying())
2715 return;
2716
2717 gnc_close_gui_component_by_data (ASSISTANT_STOCK_TRANSACTION_CM_CLASS, controller);
2718}
2719
2720
2721void
2722stock_assistant_finish_cb (GtkAssistant *assistant, gpointer user_data)
2723{
2724 auto controller = static_cast<StockAssistantController*>(user_data);
2725 controller->finish();
2726}
2727
2728
2729void
2730stock_assistant_cancel_cb (GtkAssistant *assistant, gpointer user_data)
2731{
2732 auto controller = static_cast<StockAssistantController*>(user_data);
2733 if (controller->destroying())
2734 return;
2735 gnc_close_gui_component_by_data (ASSISTANT_STOCK_TRANSACTION_CM_CLASS, controller);
2736}
2737
2738
2739static void
2740close_handler (gpointer user_data)
2741{
2742 auto controller = static_cast<StockAssistantController*>(user_data);
2743 if (controller->destroying())
2744 return;
2745 delete controller;
2746}
2747
2748/********************************************************************\
2749 * gnc_stock_transaction_assistant *
2750 * opens up a assistant to record a stock transaction *
2751 * *
2752 * Args: parent - the parent ofthis window *
2753 * initial - the initial account to use *
2754 * Return: nothing *
2755\********************************************************************/
2756void
2757gnc_stock_transaction_assistant (GtkWidget *parent, Account *account)
2758{
2759 auto builder = gtk_builder_new();
2760 gnc_builder_add_from_file(builder, "assistant-stock-transaction.glade",
2761 "stock_transaction_assistant");
2762
2763 [[maybe_unused]] auto info = new StockAssistantController(parent, builder, account);
2764 g_object_unref(builder);
2765}
Account handling public routines.
Account public routines (C++ api)
API for Transactions and Splits (journal entries)
C++ wrapper for GncAccounSel, see gnucash/gnome-utils/gnc-account-sel.h.
C++ wrapper for GncAmountEdit, see gnucash/gnome-utils/gnc-amount-edit.h.
C++ wrapper for the GncDateEdit control (see gnucash/gnome-utils/gnc-date-edit.h).
void load(const EntryVec &list_of_splits)
Extract the information from the StockTransactionEntries in the vector created by the model's make_li...
The primary numeric class for representing amounts and values.
messages for later display to the user.
std::string report()
Compose all of the logged messages into a bullet list, errors first, then warnings,...
The Cash page collects the cash account (usually corresponds the broker's cash management account),...
Dividend page, collects an amount, an INCOME account, and a memo.
Transaction Details page.
Page classes generate the several pages of the assistant.
void set_txn_type_explanation(const gchar *txt)
Sets the explanation text for the selected transaction type, allowing the user to make sure that the ...
available transaction types based on the state of the account, the collection and validation of input...
bool maybe_reset_txn_types()
Selects a TxnTypevec for the user to pick from depending on whether the account has a positive,...
const std::optional< TxnTypeVec > & get_txn_types()
Accessor function.
void set_transaction_desc(const char *desc)
Setter.
std::string get_new_amount_str() const
Accessor.
StockTransactionEntry * dividend_entry()
Accessor.
StockTransactionEntry * cash_entry()
Accessor.
std::tuple< bool, std::string, EntryVec > generate_list_of_splits()
Generate the proposed list of splits.
const std::optional< TxnTypeInfo > & txn_type()
Accessor.
StockTransactionEntry * stock_entry()
Accessor.
StockTransactionEntry * fees_entry()
Accessor.
bool set_txn_type(guint type_idx)
Setter.
std::tuple< bool, Transaction * > create_transaction()
Generate a GnuCash transaction from the active entries.
void set_transaction_date(time64 date)
Setter.
StockTransactionEntry * capgains_entry()
Accessor.
Contains the pages and manages displaying them one at a time.
void prepare(int page, StockAssistantModel *)
Calls the specified page's prepare function.
void connect(StockAssistantModel *)
Calls each page's connect function.
Holds the configuration information from the fieldmask and the data to create a single split.
virtual const char * print_amount(gnc_numeric amt) const
virtual const char * print_value() const
virtual void set_fieldmask(FieldMask mask)
Set up the state variables from the FieldMask.
virtual std::string amount_str_for_display() const
Generate a string representation of the value.
virtual gnc_numeric calculate_price() const
Calculate the price (amount/value) for non-currency accounts.
virtual const char * print_price() const
Specialized Entry for fees, taxes, commissions, and so on.
void set_fieldmask(FieldMask mask) override
Set up the state variables from the FieldMask.
Specialized Entry for the stock account's capital gains split.
Specialized StockTransactionEntry for the stock split.
void set_fieldmask(FieldMask mask) override
Set up the state variables from the FieldMask.
gnc_numeric calculate_price() const override
Calculate the price (amount/value) for non-currency accounts.
std::string amount_str_for_display() const override
Generate a string representation of the value.
Date and Time handling routines.
All type declarations for the whole Gnucash engine.
Functions for adding content to a window.
An exact-rational-number library for gnucash.
Functions providing a register page for the GnuCash UI.
Generic api to store and retrieve preferences.
GtkTreeView implementation for gnucash account tree.
utility functions for the GnuCash UI
const char * xaccAccountGetName(const Account *acc)
Get the account's name.
Definition Account.cpp:3289
void xaccAccountCommitEdit(Account *acc)
ThexaccAccountCommitEdit() subroutine is the second phase of a two-phase-commit wrapper for account u...
Definition Account.cpp:1516
void xaccAccountBeginEdit(Account *acc)
The xaccAccountBeginEdit() subroutine is the first phase of a two-phase-commit wrapper for account up...
Definition Account.cpp:1475
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.
Definition Account.cpp:3415
gnc_numeric xaccAccountGetBalanceAsOfDate(Account *acc, time64 date)
Get the balance of the account at the end of the day before the date specified.
Definition Account.cpp:3533
Account * xaccAccountGetAssociatedAccount(const Account *acc, const char *tag)
Get the account's associated account e.g.
Definition Account.cpp:3387
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account's commodity
Definition Account.cpp:3408
@ ACCT_TYPE_INCOME
Income accounts are used to denote income.
Definition Account.h:140
@ ACCT_TYPE_EXPENSE
Expense accounts are used to denote expenses.
Definition Account.h:143
@ ACCT_TYPE_BANK
The bank account type denotes a savings or checking account held at a bank.
Definition Account.h:107
@ ACCT_TYPE_ASSET
asset (and liability) accounts indicate generic, generalized accounts that are none of the above.
Definition Account.h:116
const char * gnc_commodity_get_mnemonic(const gnc_commodity *cm)
Retrieve the mnemonic for the specified commodity.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
Definition gnc-date.h:87
char * qof_print_date(time64 t)
Convenience; calls through to qof_print_date_dmy_buff().
Definition gnc-date.cpp:610
time64 gnc_time64_get_day_end(time64 time_val)
The gnc_time64_get_day_end() routine will take the given time in seconds and adjust it to the last se...
time64 gnc_time(time64 *tbuf)
get the current time
Definition gnc-date.cpp:262
void xaccTransSetDescription(Transaction *trans, const char *desc)
Sets the transaction Description.
void xaccTransCommitEdit(Transaction *trans)
The xaccTransCommitEdit() method indicates that the changes to the transaction and its splits are com...
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.
void xaccTransBeginEdit(Transaction *trans)
The xaccTransBeginEdit() method must be called before any changes are made to a transaction or any of...
time64 xaccTransGetDate(const Transaction *trans)
Retrieve the posted date of the transaction.
void qof_event_unregister_handler(gint handler_id)
Unregister an event handler.
Definition qofevent.cpp:103
gint qof_event_register_handler(QofEventHandler handler, gpointer user_data)
Register a handler for events.
Definition qofevent.cpp:73
gint QofEventId
Define the type of events allowed.
Definition qofevent.h:45
void gnc_main_window_open_page(GncMainWindow *window, GncPluginPage *page)
Display a data plugin page in a window.
const char * xaccPrintAmount(gnc_numeric val, GNCPrintAmountInfo info)
Make a string representation of a gnc_numeric.
QofBook * qof_instance_get_book(gconstpointer inst)
Return the book pointer.
#define PINFO(format, args...)
Print an informational note.
Definition qoflog.h:256
#define DEBUG(format, args...)
Print a debugging message.
Definition qoflog.h:264
#define PERR(format, args...)
Log a serious error.
Definition qoflog.h:244
#define PWARN(format, args...)
Log a warning.
Definition qoflog.h:250
const char * gnc_numeric_errorCode_to_string(GNCNumericErrorCode error_code)
Returns a string representation of the given GNCNumericErrorCode.
#define GNC_DENOM_AUTO
Values that can be passed as the 'denom' argument.
gnc_numeric gnc_numeric_div(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
Division.
gnc_numeric gnc_numeric_error(GNCNumericErrorCode error_code)
Create a gnc_numeric object that signals the error condition noted by error_code, rather than a numbe...
GNCNumericErrorCode gnc_numeric_check(gnc_numeric in)
Check for error signal in value.
gboolean gnc_numeric_negative_p(gnc_numeric a)
Returns 1 if a < 0, otherwise returns 0.
gboolean gnc_numeric_zero_p(gnc_numeric a)
Returns 1 if the given gnc_numeric is 0 (zero), else returns 0.
gnc_numeric gnc_numeric_neg(gnc_numeric a)
Returns a newly created gnc_numeric that is the negative of the given gnc_numeric value.
gboolean gnc_numeric_equal(gnc_numeric a, gnc_numeric b)
Equivalence predicate: Returns TRUE (1) if a and b represent the same number.
gchar * gnc_num_dbg_to_string(gnc_numeric n)
Convert to string.
gboolean gnc_numeric_positive_p(gnc_numeric a)
Returns 1 if a > 0, otherwise returns 0.
gnc_numeric gnc_numeric_add(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
Return a+b.
@ GNC_ERROR_ARG
Argument is not a valid number.
@ GNC_HOW_DENOM_REDUCE
Reduce the result value by common factor elimination, using the smallest possible value for the denom...
@ GNC_HOW_DENOM_EXACT
Use any denominator which gives an exactly correct ratio of numerator to denominator.
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
GNCPriceDB * gnc_pricedb_get_db(QofBook *book)
Return the pricedb associated with the book.
gboolean gnc_pricedb_add_price(GNCPriceDB *db, GNCPrice *p)
Add a price to the pricedb.
GNCPrice * gnc_price_create(QofBook *book)
gnc_price_create - returns a newly allocated and initialized price with a reference count of 1.
void gnc_price_unref(GNCPrice *p)
gnc_price_unref - indicate you're finished with a price (i.e.
void gnc_plugin_page_register_clear_current_filter(GncPluginPage *plugin_page)
This function clears the registers current filter.
GncPluginPage * gnc_plugin_page_register_new(Account *account, gboolean subaccounts)
Create a new "register" plugin page, given a pointer to an account.
GNCSplitReg * gnc_plugin_page_register_get_gsr(GncPluginPage *plugin_page)
Get the GNCSplitReg data structure associated with this register page.
Transaction * xaccSplitGetParent(const Split *split)
Returns the parent transaction of the split.
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...
gnc_numeric xaccSplitGetValue(const Split *split)
Returns the value of this split in the transaction's commodity.
gnc_numeric xaccSplitGetAmount(const Split *split)
Returns the amount of the split in the account's commodity.
void xaccSplitMakeStockSplit(Split *s)
Mark a split to be of type stock split - after this, you shouldn't modify the value anymore,...
Definition Split.cpp:2063
STRUCTS.
QofBook reference.
Definition qofbook-p.hpp:47
class TxnTypeinfo has no functions.