28 #include <glib/gi18n.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" 48 #include <gnc-date-edit.h> 51 #include "gnc-numeric.hpp" 53 #include "gnc-component-manager.h" 54 #include "gnc-date-edit.h" 59 #include "gnc-split-reg.h" 61 static QofLogModule log_module = GNC_MOD_ASSISTANT;
86 void stock_assistant_prepare_cb (GtkAssistant *assistant, GtkWidget *page,
88 void stock_assistant_finish_cb (GtkAssistant *assistant, gpointer user_data);
89 void stock_assistant_cancel_cb (GtkAssistant *gtkassistant, gpointer user_data);
92 static const char* GNC_PREFS_GROUP =
"dialogs.stock-assistant";
93 static const char* ASSISTANT_STOCK_TRANSACTION_CM_CLASS =
"assistant-stock-transaction";
95 static const char* DIVIDEND_KVP_TAG =
"stock-dividends";
96 static const char* CAPGAINS_KVP_TAG =
"stock-capgains";
97 static const char* PROCEEDS_KVP_TAG =
"stock-cash-proceeds";
98 static const char* FEES_KVP_TAG =
"stock-broker-fees";
102 enum class FieldMask : unsigned
106 ENABLED_CREDIT = 1 << 1,
107 AMOUNT_DEBIT = 1 << 2,
108 AMOUNT_CREDIT = 1 << 3,
109 INPUT_NEW_BALANCE = 1 << 4,
111 ALLOW_NEGATIVE = 1 << 6,
112 CAPITALIZE_DEFAULT = 1 << 7,
113 CAPGAINS_IN_STOCK = 1 << 8,
114 MARKER_SPLIT = 1 << 9,
119 operator |(FieldMask lhs, FieldMask rhs)
121 return static_cast<FieldMask
> (
static_cast<unsigned>(lhs) |
122 static_cast<unsigned>(rhs));
126 operator &(FieldMask lhs, FieldMask rhs)
128 return (static_cast<unsigned>(lhs) & static_cast<unsigned>(rhs));
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;
148 using StringVec = std::vector<std::string>;
149 using TxnTypeVec = std::vector<TxnTypeInfo>;
150 using AccountVec = std::vector<Account*>;
152 static const TxnTypeVec starting_types
156 FieldMask::ENABLED_DEBIT | FieldMask::AMOUNT_DEBIT,
157 FieldMask::ENABLED_CREDIT,
158 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
164 N_(
"Initial stock long purchase.")
167 FieldMask::ENABLED_CREDIT | FieldMask::AMOUNT_CREDIT,
168 FieldMask::ENABLED_DEBIT,
169 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
175 N_(
"Initial stock short sale.")
179 static const TxnTypeVec long_types
182 FieldMask::ENABLED_DEBIT | FieldMask::AMOUNT_DEBIT,
183 FieldMask::ENABLED_CREDIT,
184 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
190 N_(
"Buying stock long.")
193 FieldMask::ENABLED_CREDIT | FieldMask::AMOUNT_CREDIT,
194 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
195 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
197 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO | FieldMask::ALLOW_NEGATIVE | FieldMask::CAPGAINS_IN_STOCK,
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.")
206 FieldMask::MARKER_SPLIT,
207 FieldMask::ENABLED_DEBIT,
208 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
209 FieldMask::ENABLED_CREDIT,
214 N_(
"Company issues cash dividends to holder.\n\nAny dividend being " 215 "reinvested must be subsequently recorded as a regular stock purchase.")
218 FieldMask::ENABLED_CREDIT,
219 FieldMask::ENABLED_DEBIT,
220 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
225 N_(
"Return of capital"),
226 N_(
"Company returns capital, reducing the cost basis without affecting # units.")
229 FieldMask::ENABLED_CREDIT,
231 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
232 FieldMask::ENABLED_DEBIT,
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.")
242 FieldMask::ENABLED_DEBIT,
244 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
245 FieldMask::ENABLED_CREDIT,
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.")
254 FieldMask::ENABLED_DEBIT,
256 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
258 FieldMask::ENABLED_CREDIT,
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.")
266 FieldMask::AMOUNT_DEBIT | FieldMask::INPUT_NEW_BALANCE,
267 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO,
268 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
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.")
280 FieldMask::AMOUNT_CREDIT | FieldMask::INPUT_NEW_BALANCE,
281 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO,
282 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
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.")
294 static const TxnTypeVec short_types
297 FieldMask::ENABLED_CREDIT | FieldMask::AMOUNT_CREDIT,
298 FieldMask::ENABLED_DEBIT,
299 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
305 N_(
"Selling stock short.")
308 FieldMask::ENABLED_DEBIT | FieldMask::AMOUNT_DEBIT,
309 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO,
310 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
312 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO | FieldMask::ALLOW_NEGATIVE | FieldMask::CAPGAINS_IN_STOCK,
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.")
321 FieldMask::MARKER_SPLIT,
322 FieldMask::ENABLED_CREDIT,
323 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
324 FieldMask::ENABLED_DEBIT,
328 N_(
"Compensatory dividend"),
329 N_(
"Company issues dividends, and the short stock holder must make a compensatory " 330 "payment for the dividend.")
333 FieldMask::ENABLED_DEBIT,
334 FieldMask::ENABLED_CREDIT,
335 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
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.")
346 FieldMask::ENABLED_DEBIT,
348 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
349 FieldMask::ENABLED_CREDIT,
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.")
362 FieldMask::ENABLED_CREDIT,
364 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
365 FieldMask::ENABLED_DEBIT,
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.")
377 FieldMask::ENABLED_CREDIT,
379 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
381 FieldMask::ENABLED_DEBIT,
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.")
392 FieldMask::AMOUNT_CREDIT | FieldMask::INPUT_NEW_BALANCE,
393 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO,
394 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
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.")
406 FieldMask::AMOUNT_DEBIT | FieldMask::INPUT_NEW_BALANCE,
407 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO,
408 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
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.")
421 enum class LogMsgType
431 const std::string m_message;
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) {}
440 LogMsgType type() {
return m_type; }
441 const std::string& message() {
return m_message; }
450 using Log = std::vector<LogMessage>;
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(); }
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); }
475 Logger::write_log(std::stringstream& stream, LogMsgType type)
477 std::for_each(m_log.begin(), m_log.end(),
479 if (msg.type() == type)
480 stream <<
"\n * " << msg.message();
485 Logger::has_warnings()
487 return std::any_of(m_log.begin(), m_log.end(),
488 [](
auto& msg){
return msg.type() == LogMsgType::warning;
495 return std::any_of(m_log.begin(), m_log.end(),
496 [](
auto& msg){
return msg.type() == LogMsgType::error;
503 std::stringstream summary;
506 summary << _(
"No errors found. Click Apply to create transaction.");
511 summary << _(
"The following errors must be fixed:");
516 summary <<
"\n\n" << _(
"The following warnings exist:");
519 return summary.str();
527 void* handler_data, [[maybe_unused]]
void* event_data);
543 bool m_allow_negative;
544 bool m_input_new_balance =
false;
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;
554 m_enabled{
false}, m_debit_side{
false}, m_allow_zero{
false}, m_account{
nullptr},
558 m_enabled{
false}, m_debit_side{
false}, m_allow_zero{
false}, m_account{
nullptr},
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);
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; }
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; }
600 virtual void create_split(Transaction* trans, AccountVec& commits)
const;
608 virtual const char*
print_amount(gnc_numeric amt)
const;
632 void* handler_data, [[maybe_unused]]
void* event_data)
635 if ((inst && inst != QOF_INSTANCE(entry->account())) || (
event & QOF_EVENT_DESTROY) == 0)
637 entry->set_account(
nullptr);
640 using StockTransactionEntryPtr = std::unique_ptr<StockTransactionEntry>;
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;
652 StockTransactionEntry::print_account()
const 654 auto acct_required = m_enabled &&
658 acct_required ? _(
"missing") :
"";
662 StockTransactionEntry::set_value(gnc_numeric amount)
673 m_debit_side = !m_debit_side;
679 PINFO(
"Set %s value to %" PRId64
"/%" PRId64, m_action, m_value.num, m_value.denom);
683 StockTransactionEntry::validate_amount(
Logger& logger)
const 685 auto add_error = [&logger](
const char* format_str,
const char* arg)
687 char *buf = g_strdup_printf (_(format_str),
688 g_dpgettext2 (
nullptr,
"Stock Assistant: Page name", arg));
697 add_error (N_(
"Amount for %s is missing."), m_action);
702 add_error (N_(
"Amount for %s must not be negative."), m_action);
705 add_error (N_(
"Amount for %s must be positive."), m_action);
708 add_error(N_(
"The %s amount has no associated account."), m_action);
728 auto pinfo{gnc_commodity_print_info(currency, TRUE)};
738 auto pinfo{gnc_commodity_print_info(commodity, TRUE)};
743 StockTransactionEntry::create_split(Transaction *trans, AccountVec &account_commits)
const 745 g_return_if_fail(trans);
749 xaccSplitSetParent(split, trans);
751 account_commits.push_back(m_account);
752 xaccSplitSetAccount(split, m_account);
757 PINFO(
"creating %s split in Acct(%s): Val(%s), Amt(%s) => Val(%s), Amt(%s)",
763 gnc_set_num_action(
nullptr, split,
nullptr,
764 g_dpgettext2(
nullptr,
"Stock Assistant: Action field",
776 auto pinfo{gnc_price_print_info(currency, TRUE)};
790 bool m_amount_enabled;
791 gnc_numeric m_amount;
792 bool m_marker =
false;
797 PINFO(
"Stock Entry");
802 PINFO(
"Stock Entry");
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;
812 bool marker_split()
const override {
return m_marker; }
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;
828 StockTransactionStockEntry::set_amount(gnc_numeric amount)
836 if (m_input_new_balance)
839 m_amount = gnc_numeric_sub_fixed(amount, m_balance);
841 m_amount = gnc_numeric_sub_fixed(m_balance, amount);
853 StockTransactionStockEntry::validate_amount(
Logger& logger)
const 856 StockTransactionEntry::validate_amount(logger);
858 if (!m_amount_enabled)
861 auto add_error_str = [&logger]
862 (
const char* str) { logger.error (_(str)); };
866 add_error_str(_(
"Amount for stock value is missing."));
870 if (m_input_new_balance)
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);
878 add_error_str(N_(
"Invalid stock new balance."));
880 add_error_str(N_(
"New balance must be higher than old balance."));
882 add_error_str(N_(
"New balance must be lower than old balance."));
884 PINFO(
"Delta %" PRId64
"/%" PRId64
", Ratio %" PRId64
"/%" PRId64, delta.num, delta.denom, ratio.num, ratio.denom);
889 add_error_str(N_(
"Stock amount must be positive."));
891 auto new_bal = gnc_numeric_add_fixed(m_balance, m_amount);
893 add_error_str(N_(
"Cannot sell more units than owned."));
895 add_error_str(N_(
"Cannot cover buy more units than owed."));
906 if (m_input_new_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);
918 std::ostringstream ret;
919 ret << ratio.num <<
':' << ratio.denom;
925 amount = gnc_numeric_add_fixed (amount, m_balance);
934 StockTransactionStockEntry::create_split(Transaction *trans, AccountVec &account_commits)
const 936 g_return_if_fail(trans);
940 xaccSplitSetParent(split, trans);
942 account_commits.push_back(m_account);
943 xaccSplitSetAccount(split, m_account);
947 if (m_amount_enabled)
949 if (m_amount_enabled && !m_enabled)
951 PINFO(
"creating %s split in Acct(%s): Val(%s), Amt(%s) => Val(%s), Amt(%s)",
957 gnc_set_num_action(
nullptr, split,
nullptr,
958 g_dpgettext2(
nullptr,
"Stock Assistant: Action field",
965 if (m_input_new_balance ||
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)};
980 PINFO(
"Calculated price %s from value %s and amount %s",
997 gnc_numeric amount()
const {
return gnc_numeric_zero(); }
1000 StockTransactionStockCapGainsEntry::StockTransactionStockCapGainsEntry(
const StockTransactionEntry *cg_entry,
1004 m_debit_side = !m_debit_side;
1005 m_account = stk_entry->account();
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;
1029 m_capitalize = mask & FieldMask::CAPITALIZE_DEFAULT;
1033 StockTransactionFeesEntry::validate_amount(
Logger& logger)
const 1035 auto add_error = [&logger](
const char* format_str,
const char* arg)
1037 char *buf = g_strdup_printf (_(format_str),
1038 g_dpgettext2 (
nullptr,
"Stock Assistant: Page name", arg));
1047 add_error (N_(
"Amount for %s is missing."), m_action);
1052 add_error (N_(
"Amount for %s must not be negative."), m_action);
1055 add_error (N_(
"Amount for %s must be positive."), m_action);
1058 add_error(N_(
"The %s amount has no associated account."), m_action);
1062 StockTransactionFeesEntry::create_split(Transaction* trans, AccountVec& commits)
const 1064 g_return_if_fail(trans);
1068 xaccSplitSetParent(split, trans);
1071 xaccSplitSetAccount(split, commits[0]);
1076 commits.push_back(m_account);
1077 xaccSplitSetAccount(split, m_account);
1082 PINFO(
"creating %s split in Acct(%s): Val(%s), Amt(%s) => Val(%s), Amt(%s)",
1088 gnc_set_num_action(
nullptr, split,
nullptr,
1089 g_dpgettext2(
nullptr,
"Stock Assistant: Action field",
1093 using EntryVec = std::vector<StockTransactionEntry*>;
1095 static void stock_assistant_model_date_changed_cb(GtkWidget*,
void*);
1096 static void stock_assistant_model_description_changed_cb(GtkWidget *,
void *);
1107 gnc_commodity* m_currency;
1108 time64 m_transaction_date;
1109 const char* m_transaction_description;
1110 std::optional<TxnTypeVec> m_txn_types;
1112 std::optional<TxnTypeInfo> m_txn_type;
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;
1122 std::optional<time64> m_txn_types_date;
1123 bool m_ready_to_create =
false;
1125 EntryVec m_list_of_splits;
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);
1148 DEBUG (
"StockAssistantModel destructor\n");
1188 const std::optional<TxnTypeInfo>&
txn_type() {
return m_txn_type; }
1241 Account* account() {
return m_acct; }
1246 void add_price (
QofBook *book);
1252 auto old_bal = m_stock_entry->get_balance();
1255 if (m_txn_types_date && m_txn_types_date == m_transaction_date &&
1258 m_stock_entry->set_balance(new_bal);
1259 m_txn_types_date = m_transaction_date;
1269 if (!m_txn_types_date || m_txn_types_date != m_transaction_date)
1271 PERR (
"transaction_date has changed. rerun maybe_reset_txn_types!");
1276 m_txn_type = m_txn_types->at (type_idx);
1278 catch (
const std::out_of_range&)
1280 PERR (
"out of range type_idx=%d", type_idx);
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);
1293 check_txn_date(
const Split* last_split,
time64 txn_date,
Logger& logger)
1296 if (txn_date <= last_split_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);
1311 g_free(new_date_str);
1312 g_free(last_split_date_str);
1316 std::tuple<bool, std::string, EntryVec>
1318 if (!m_txn_types || !m_txn_type)
1319 return {
false,
"Error: txn_type not initialized", {} };
1322 m_list_of_splits.clear();
1331 if (
const auto& splits = xaccAccountGetSplits (m_acct); !splits.empty())
1332 check_txn_date(splits.back(), m_transaction_date, m_logger);
1334 if (m_stock_entry->enabled() || m_stock_entry->has_amount())
1336 m_stock_entry->validate_amount(m_logger);
1337 m_list_of_splits.push_back(m_stock_entry.get());
1339 auto price{m_stock_entry->calculate_price()};
1344 auto tmpl = N_(
"A price of 1 %s = %s on %s will be recorded.");
1346 auto price_msg = g_strdup_printf
1349 m_stock_entry->print_price(), date_str);
1350 m_logger.info(price_msg);
1356 if (m_stock_entry->marker_split())
1357 m_list_of_splits.push_back(m_stock_entry.get());
1359 if (m_cash_entry->enabled())
1361 m_cash_entry->validate_amount(m_logger);
1362 m_list_of_splits.push_back (m_cash_entry.get());
1365 if (m_fees_entry->enabled())
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());
1373 if (m_dividend_entry->enabled())
1375 m_dividend_entry->validate_amount(m_logger);
1376 m_list_of_splits.push_back (m_dividend_entry.get());
1379 if (m_capgains_entry->enabled())
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());
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();
1395 credit += entry->value();
1400 const char *err_act = NULL, *err_reason = NULL;
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);
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)};
1424 auto error_str = g_strdup_printf (_(imbalance_str), debit_str, credit_str);
1425 m_logger.error (error_str);
1427 g_free (credit_str);
1434 m_ready_to_create = !m_logger.has_errors();
1435 return { m_ready_to_create, m_logger.
report(), m_list_of_splits };
1438 std::tuple<bool, Transaction*>
1441 if (!m_ready_to_create)
1443 PERR (
"errors exist. cannot create transaction.");
1444 m_list_of_splits.clear();
1445 return {
false,
nullptr};
1453 AccountVec accounts;
1454 std::for_each (m_list_of_splits.begin(), m_list_of_splits.end(),
1457 entry->create_split (trans, accounts);
1458 if (entry->get_kvp_tag() && entry->account())
1464 m_list_of_splits.clear();
1465 m_ready_to_create =
false;
1466 return {
true, trans};
1470 StockAssistantModel::add_price (
QofBook *book)
1472 auto stock_price{m_stock_entry->calculate_price()};
1477 gnc_price_begin_edit (price);
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);
1488 PWARN (
"error adding price");
1494 stock_assistant_model_date_changed_cb(GtkWidget* widget,
void* data)
1501 stock_assistant_model_description_changed_cb(GtkWidget* widget,
void* data)
1513 entry->set_memo(gtk_entry_get_text (GTK_ENTRY (widget)));
1517 static inline GtkWidget*
1518 get_widget (GtkBuilder *builder,
const gchar * ID)
1520 g_return_val_if_fail (builder && ID,
nullptr);
1521 auto obj = gtk_builder_get_object (builder, ID);
1523 PWARN (
"get_widget ID '%s' not found. it may be a typo?", ID);
1524 return GTK_WIDGET (obj);
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,
1539 time64 get_date_time() {
return gnc_date_edit_get_date_end(GNC_DATE_EDIT(m_edit)); }
1540 void connect(GCallback, gpointer);
1544 GncDateEdit::attach(GtkBuilder *builder,
const char *table_ID,
1545 const char *label_ID,
int row)
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);
1555 GncDateEdit::connect(GCallback cb, gpointer data)
1557 g_signal_connect(m_edit,
"date_changed", cb, data);
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));
1574 void connect (GCallback cb, gpointer data);
1575 void set_owner (gpointer obj);
1581 g_return_if_fail(GNC_IS_AMOUNT_EDIT(widget));
1583 auto invalid{gnc_amount_edit_expr_is_valid(GNC_AMOUNT_EDIT(widget),
1584 &value,
true,
nullptr)};
1588 GncAmountEdit::GncAmountEdit (GtkBuilder *builder, gnc_commodity *commodity) :
1589 m_edit{gnc_amount_edit_new()}
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);
1598 GncAmountEdit::attach (GtkBuilder *builder,
const char *table_ID,
1599 const char* label_ID,
int row)
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);
1609 GncAmountEdit::get ()
1612 if (!gnc_amount_edit_expr_is_valid (GNC_AMOUNT_EDIT(m_edit), &amt,
true,
nullptr))
1618 GncAmountEdit::connect (GCallback cb, gpointer data)
1620 g_signal_connect(m_edit,
"changed", cb, data);
1624 GncAmountEdit::set_owner(gpointer obj)
1626 g_object_set_data(G_OBJECT (m_edit),
"owner", obj);
1629 using AccountTypeList = std::vector<GNCAccountType>;
1636 GtkWidget* m_selector;
1639 gnc_commodity *currency,
Account *default_acct);
1640 void attach (GtkBuilder *builder,
const char *table_id,
1641 const char *label_ID,
int row);
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)); }
1651 g_return_if_fail (GNC_IS_ACCOUNT_SEL (widget));
1652 entry->set_account(gnc_account_sel_get_account (GNC_ACCOUNT_SEL (widget)));
1655 GncAccountSelector::GncAccountSelector (GtkBuilder *builder, AccountTypeList types,
1656 gnc_commodity *currency,
Account *default_acct) :
1657 m_selector{gnc_account_sel_new ()}
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);
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);
1674 GncAccountSelector::attach (GtkBuilder *builder,
const char *table_ID,
1675 const char *label_ID,
int row)
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);
1687 g_signal_connect(m_selector,
"account_sel_changed", G_CALLBACK (gnc_account_sel_changed_cb), entry);
1691 GncAccountSelector::set_sensitive(
bool sensitive)
1693 gtk_widget_set_sensitive(m_selector, sensitive);
1709 assistant_page_set_focus(GtkWidget* page, [[maybe_unused]]GtkDirectionType type, GtkWidget* entry)
1711 gtk_widget_grab_focus(entry);
1712 g_signal_handlers_disconnect_by_data(page, entry);
1748 GtkWidget * m_explanation;
1752 int get_transaction_type_index ();
1753 void set_transaction_types (
const TxnTypeVec& txn_types);
1765 PageTransType::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"))
1770 g_object_set_data(G_OBJECT(m_type),
"owner",
this);
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);
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);
1794 PageTransType::get_transaction_type_index ()
1796 return gtk_combo_box_get_active (GTK_COMBO_BOX (m_type));
1800 PageTransType::set_transaction_types (
const TxnTypeVec& txn_types)
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);
1813 gtk_label_set_text (GTK_LABEL (this->m_explanation), txt);
1819 auto type_idx = get_transaction_type_index();
1832 g_signal_connect(m_type,
"changed",
1833 G_CALLBACK (page_trans_type_changed_cb), model);
1846 GtkWidget *m_description;
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)); }
1855 PageTransDeets::PageTransDeets (GtkBuilder *builder) :
1856 m_page (get_widget (builder,
"transaction_details_page")),
1858 m_description (get_widget (builder,
"transaction_description_entry"))
1860 m_date.attach(builder,
"transaction_details_table",
"transaction_date_label", 0);
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));
1878 g_signal_connect(m_page,
"focus", G_CALLBACK(assistant_page_set_focus), m_description);
1894 GtkWidget * m_title;
1895 GtkWidget * m_prev_amount;
1896 GtkWidget * m_next_amount;
1897 GtkWidget * m_next_amount_label;
1899 GtkWidget * m_amount_label;
1903 gnc_numeric get_stock_amount () {
return m_amount.get(); }
1904 void set_stock_amount (std::string new_amount_str);
1908 PageStockAmount::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")),
1915 m_amount_label (get_widget (builder,
"stock_amount_label"))
1917 m_amount.attach (builder,
"stock_amount_table",
"stock_amount_label", 1);
1923 gtk_label_set_text_with_mnemonic
1924 (GTK_LABEL (m_amount_label),
1925 entry->input_new_balance() ? _(
"Ne_w Balance") : _(
"_Shares"));
1927 (GTK_LABEL (m_next_amount_label),
1928 entry->input_new_balance() ? _(
"Ratio") : _(
"Next Balance"));
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()));
1936 entry->set_amount(get_stock_amount());
1938 g_signal_connect(m_page,
"focus", G_CALLBACK(assistant_page_set_focus), m_amount.widget());
1944 auto me =
static_cast<PageStockAmount*
>(g_object_get_data (G_OBJECT (widget),
"owner"));
1945 entry->set_amount(me->get_stock_amount());
1952 m_amount.connect(G_CALLBACK (page_stock_amount_changed_cb), entry);
1953 m_amount.set_owner(static_cast<gpointer>(
this));
1957 PageStockAmount::set_stock_amount (std::string new_amount_str)
1959 gtk_label_set_text (GTK_LABEL(m_next_amount), new_amount_str.c_str());
1971 GtkWidget * m_price;
1975 const char* get_memo ();
1979 void set_price(
const gchar *val);
1985 auto me =
static_cast<PageStockValue*
>(g_object_get_data (G_OBJECT (widget),
"owner"));
1986 entry->set_value (me->value_edit().get());
1990 PageStockValue::PageStockValue(GtkBuilder *builder,
Account* account)
1991 : m_page(get_widget(builder,
"stock_value_page")),
1993 m_price(get_widget(builder,
"stock_price_amount")),
1994 m_memo(get_widget(builder,
"stock_memo_entry"))
1996 m_value.attach(builder,
"stock_value_table",
"stock_value_label", 0);
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);
2010 entry->set_memo(get_memo());
2012 entry->set_value(m_value.get());
2014 g_signal_connect(m_page,
"focus", G_CALLBACK(assistant_page_set_focus), m_value.widget());
2018 PageStockValue::get_memo()
2020 return gtk_entry_get_text(GTK_ENTRY (m_memo));
2024 PageStockValue::set_price (
const gchar *val)
2026 gtk_label_set_text(GTK_LABEL(this->m_price), val);
2045 const char* get_memo();
2048 PageCash::PageCash(GtkBuilder *builder,
Account* account)
2049 : m_page(get_widget(builder,
"cash_details_page")),
2053 m_memo(get_widget(builder,
"cash_memo_entry")),
2056 m_account.attach (builder,
"cash_table",
"cash_account_label", 0);
2057 m_value.attach (builder,
"cash_table",
"cash_label", 1);
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);
2071 entry->set_memo(get_memo());
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());
2079 PageCash::get_memo()
2081 return gtk_entry_get_text(GTK_ENTRY (m_memo));
2093 GtkWidget * m_capitalize;
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);
2110 PageFees::PageFees(GtkBuilder *builder,
Account* account)
2111 : m_page(get_widget(builder,
"fees_details_page")),
2113 get_widget(builder,
"capitalize_fees_checkbutton")),
2116 m_memo(get_widget(builder,
"fees_memo_entry")),
2118 m_stock_account(account)
2120 m_account.attach (builder,
"fees_table",
"fees_account_label", 1);
2121 m_value.attach(builder,
"fees_table",
"fees_label", 2);
2125 PageFees::get_capitalize_fees()
2127 return gtk_toggle_button_get_active(
2128 GTK_TOGGLE_BUTTON(m_capitalize));
2132 PageFees::get_memo()
2134 return gtk_entry_get_text(GTK_ENTRY (m_memo));
2138 PageFees::set_capitalize_fees(
bool state)
2140 gtk_toggle_button_set_active(
2141 GTK_TOGGLE_BUTTON(m_capitalize), state);
2145 PageFees::update_fees_acct_sensitive(
bool sensitive)
2147 m_account.set_sensitive(sensitive);
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);
2159 entry->set_account(me->stock_account());
2160 me->update_fees_acct_sensitive(!cap);
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);
2176 set_capitalize_fees (entry->do_capitalize());
2177 entry->set_memo(get_memo());
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());
2197 const char* get_memo();
2200 PageDividend::PageDividend(GtkBuilder *builder,
Account* account)
2201 : m_page(get_widget(builder,
"dividend_details_page")),
2204 m_memo(get_widget(builder,
"dividend_memo_entry")),
2207 m_account.attach(builder,
"dividend_table",
"dividend_account_label", 0);
2208 m_value.attach(builder,
"dividend_table",
"dividend_label", 1);
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);
2223 entry->set_memo(get_memo());
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());
2231 PageDividend::get_memo()
2233 return gtk_entry_get_text(GTK_ENTRY (m_memo));
2247 const char* get_memo();
2250 PageCapGain::PageCapGain (GtkBuilder *builder,
Account* account) :
2251 m_page (get_widget (builder,
"capgains_details_page")),
2254 m_memo (get_widget (builder,
"capgains_memo_entry")),
2257 m_account.attach(builder,
"capgains_table",
"capgains_account_label", 0);
2258 m_value.attach(builder,
"capgains_table",
"capgains_label", 1);
2262 PageCapGain::get_memo()
2264 return gtk_entry_get_text(GTK_ENTRY (m_memo));
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);
2279 entry->set_memo(get_memo());
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());
2289 SPLIT_COL_ACCOUNT = 0,
2295 SPLIT_COL_UNITS_COLOR,
2302 GtkWidget *m_treeview;
2309 void load(
const EntryVec& list_of_splits);
2312 GncFinishTreeview::GncFinishTreeview (GtkBuilder *builder) :
2313 m_treeview{get_widget (builder,
"transaction_view")}
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 ());
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);
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);
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);
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);
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);
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,
2360 gtk_tree_view_append_column(view, column);
2361 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(m_treeview),
2362 SPLIT_COL_TOOLTIP);}
2367 auto gtv = GTK_TREE_VIEW(m_treeview);
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) {
2374 auto memo{entry->memo()};
2375 auto tooltip = (memo && *memo ?
2376 g_markup_escape_text(memo, -1) : strdup(
""));
2382 auto char2str{[](
const char* str) -> std::string {
2383 return std::string{ str ? str :
"" }; }};
2385 auto units{char2str(entry->has_amount() ?
2386 entry->
print_amount(entry->debit_side() ? entry->amount() :
2388 auto units_in_red{negative_in_red && !entry->debit_side()};
2389 gtk_list_store_append(list, &iter);
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,
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);
2412 GtkWidget * m_summary;
2418 PageFinish::PageFinish (GtkBuilder *builder) :
2419 m_page (get_widget (builder,
"finish_page")), m_view (builder),
2420 m_summary (get_widget (builder,
"finish_summary")) {}
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);
2432 enum assistant_pages
2435 PAGE_TRANSACTION_DETAILS,
2436 PAGE_TRANSACTION_TYPE,
2448 GtkWidget * m_window;
2475 GtkWidget* window() {
return m_window; }
2478 StockAssistantView::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)
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");
2494 StockAssistantView::~StockAssistantView()
2496 gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(m_window));
2497 gtk_widget_destroy (m_window);
2498 DEBUG (
"StockAssistantView destructor\n");
2508 forward_page_func (gint current_page,
void* data)
2513 return current_page;
2515 if (!model->
stock_entry()->has_amount() && current_page == PAGE_STOCK_AMOUNT)
2517 if (!model->
stock_entry()->enabled() && current_page == PAGE_STOCK_VALUE)
2519 if (!model->
cash_entry()->enabled() && current_page == PAGE_CASH)
2521 if (!model->
fees_entry()->enabled() && current_page == PAGE_FEES)
2523 if (!model->
dividend_entry()->enabled() && current_page == PAGE_DIVIDEND)
2525 if (!model->
capgains_entry()->enabled() && current_page == PAGE_CAPGAINS)
2528 return current_page;
2534 m_type_page.connect(model);
2535 m_deets_page.connect(model);
2536 m_stock_amount_page.connect(model->
stock_entry());
2543 gtk_assistant_set_forward_page_func (GTK_ASSISTANT(m_window),
2544 (GtkAssistantPageFunc)forward_page_func,
2551 g_return_if_fail (page < PAGE_STOCK_AMOUNT || model->txn_type_valid());
2554 case PAGE_TRANSACTION_TYPE:
2557 m_type_page.prepare(model);
2559 case PAGE_TRANSACTION_DETAILS:
2560 m_deets_page.prepare(model);
2562 case PAGE_STOCK_AMOUNT:
2564 m_stock_amount_page.prepare(model->
stock_entry());
2567 case PAGE_STOCK_VALUE:
2588 m_finish_page.prepare (m_window, model);
2602 void* handler_data, [[maybe_unused]]
void* event_data);
2606 std::unique_ptr<StockAssistantModel> m_model;
2608 bool m_destroying =
false;
2609 int m_qof_event_handler;
2612 : m_model{std::make_unique<StockAssistantModel>(acct)},
2613 m_view{builder, acct, parent},
2616 connect_signals (builder);
2617 DEBUG (
"StockAssistantController constructor\n");
2620 void connect_signals(GtkBuilder *builder);
2621 void prepare(GtkAssistant* assistant, GtkWidget *page);
2623 bool destroying() {
return m_destroying; }
2624 Account* model_account() {
return m_model->account(); }
2627 static void stock_assistant_window_destroy_cb(GtkWidget *
object, gpointer user_data);
2628 static void close_handler (gpointer user_data);
2630 StockAssistantController::~StockAssistantController()
2632 m_destroying =
true;
2633 gnc_unregister_gui_component_by_data (ASSISTANT_STOCK_TRANSACTION_CM_CLASS,
this);
2638 StockAssistantController::connect_signals (GtkBuilder *builder)
2640 m_view.
connect(m_model.get());
2641 gtk_builder_connect_signals (builder,
this);
2642 g_signal_connect (m_view.window(),
"destroy",
2643 G_CALLBACK (stock_assistant_window_destroy_cb),
this);
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);
2653 StockAssistantController::prepare(GtkAssistant* assistant, GtkWidget* page)
2655 auto currentpage = gtk_assistant_get_current_page(assistant);
2656 m_view.
prepare(currentpage, m_model.get());
2660 StockAssistantController::finish()
2662 g_return_if_fail (m_model->txn_type_valid());
2664 gnc_suspend_gui_refresh ();
2665 auto [success, trans] = m_model->create_transaction();
2666 gnc_resume_gui_refresh ();
2668 if (success && trans)
2670 auto split = xaccTransFindSplitByAccount (trans, m_model->account());
2676 gnc_split_reg_raise (gsr);
2678 if (gnc_split_reg_clear_filter_for_split (gsr, split))
2681 gnc_split_reg_jump_to_split (gsr, split);
2685 gnc_close_gui_component_by_data (ASSISTANT_STOCK_TRANSACTION_CM_CLASS,
this);
2690 void* handler_data, [[maybe_unused]]
void* event_data)
2693 if ((inst && inst != QOF_INSTANCE(controller->model_account())) || (
event & QOF_EVENT_DESTROY) == 0 ||
2694 controller->destroying())
2702 stock_assistant_prepare_cb (GtkAssistant *assistant, GtkWidget *page,
2706 info->prepare(assistant, page);
2711 stock_assistant_window_destroy_cb (GtkWidget *
object, gpointer user_data)
2714 if (controller->destroying())
2717 gnc_close_gui_component_by_data (ASSISTANT_STOCK_TRANSACTION_CM_CLASS, controller);
2722 stock_assistant_finish_cb (GtkAssistant *assistant, gpointer user_data)
2725 controller->finish();
2730 stock_assistant_cancel_cb (GtkAssistant *assistant, gpointer user_data)
2733 if (controller->destroying())
2735 gnc_close_gui_component_by_data (ASSISTANT_STOCK_TRANSACTION_CM_CLASS, controller);
2740 close_handler (gpointer user_data)
2743 if (controller->destroying())
2757 gnc_stock_transaction_assistant (GtkWidget *parent,
Account *account)
2759 auto builder = gtk_builder_new();
2760 gnc_builder_add_from_file(builder,
"assistant-stock-transaction.glade",
2761 "stock_transaction_assistant");
2764 g_object_unref(builder);
messages for later display to the user.
void xaccSplitSetValue(Split *split, gnc_numeric val)
The xaccSplitSetValue() method sets the value of this split in the transaction's commodity.
GNCPrice * gnc_price_create(QofBook *book)
gnc_price_create - returns a newly allocated and initialized price with a reference count of 1...
GncPluginPage * gnc_plugin_page_register_new(Account *account, gboolean subaccounts)
Create a new "register" plugin page, given a pointer to an account.
bool txn_type_valid()
Accessor.
Transaction * xaccMallocTransaction(QofBook *book)
The xaccMallocTransaction() will malloc memory and initialize it.
gboolean gnc_numeric_equal(gnc_numeric a, gnc_numeric b)
Equivalence predicate: Returns TRUE (1) if a and b represent the same number.
std::string report()
Compose all of the logged messages into a bullet list, errors first, then warnings, infos last.
void xaccTransSetDatePostedSecsNormalized(Transaction *trans, time64 time)
This function sets the posted date of the transaction, specified by a time64 (see ctime(3))...
Contains the pages and manages displaying them one at a time.
gchar * gnc_num_dbg_to_string(gnc_numeric n)
Convert to string.
void xaccSplitMakeStockSplit(Split *s)
Mark a split to be of type stock split - after this, you shouldn't modify the value anymore...
Page classes generate the several pages of the assistant.
time64 xaccTransGetDate(const Transaction *trans)
Retrieve the posted date of the transaction.
Date and Time handling routines.
const char * gnc_commodity_get_mnemonic(const gnc_commodity *cm)
Retrieve the mnemonic for the specified commodity.
void xaccAccountSetAssociatedAccount(Account *acc, const char *tag, const Account *assoc_acct)
Set the account's associated account e.g.
virtual const char * print_value() const
QofBook * qof_instance_get_book(gconstpointer inst)
Return the book pointer.
utility functions for the GnuCash UI
Expense accounts are used to denote expenses.
StockTransactionEntry * fees_entry()
Accessor.
#define PINFO(format, args...)
Print an informational note.
gnc_numeric gnc_numeric_neg(gnc_numeric a)
Returns a newly created gnc_numeric that is the negative of the given gnc_numeric value...
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 ...
std::tuple< bool, Transaction * > create_transaction()
Generate a GnuCash transaction from the active entries.
An exact-rational-number library for gnucash.
gnc_numeric calculate_price() const override
Calculate the price (amount/value) for non-currency accounts.
void gnc_price_unref(GNCPrice *p)
gnc_price_unref - indicate you're finished with a price (i.e.
The Cash page collects the cash account (usually corresponds the broker's cash management account)...
const std::optional< TxnTypeInfo > & txn_type()
Accessor.
#define DEBUG(format, args...)
Print a debugging message.
gboolean gnc_pricedb_add_price(GNCPriceDB *db, GNCPrice *p)
Add a price to the pricedb.
const char * xaccPrintAmount(gnc_numeric val, GNCPrintAmountInfo info)
Make a string representation of a gnc_numeric.
void xaccTransSetDescription(Transaction *trans, const char *desc)
Sets the transaction Description.
gnc_numeric gnc_numeric_add(gnc_numeric a, gnc_numeric b, gint64 denom, gint how)
Return a+b.
C++ wrapper for GncAmountEdit, see gnucash/gnome-utils/gnc-amount-edit.h.
gboolean gnc_numeric_zero_p(gnc_numeric a)
Returns 1 if the given gnc_numeric is 0 (zero), else returns 0.
Specialized Entry for the stock account's capital gains split.
The primary numeric class for representing amounts and values.
Transaction * xaccSplitGetParent(const Split *split)
Returns the parent transaction of the split.
Use any denominator which gives an exactly correct ratio of numerator to denominator.
virtual gnc_numeric calculate_price() const
Calculate the price (amount/value) for non-currency accounts.
#define PERR(format, args...)
Log a serious error.
C++ wrapper for the GncDateEdit control (see gnucash/gnome-utils/gnc-date-edit.h).
void set_transaction_date(time64 date)
Setter.
StockTransactionEntry * dividend_entry()
Accessor.
GNCPriceDB * gnc_pricedb_get_db(QofBook *book)
Return the pricedb associated with the book.
void gnc_main_window_open_page(GncMainWindow *window, GncPluginPage *page)
Display a data plugin page in a window.
Functions for adding content to a window.
gboolean gnc_numeric_negative_p(gnc_numeric a)
Returns 1 if a < 0, otherwise returns 0.
Logger & logger()
Accessor.
void xaccTransSetCurrency(Transaction *trans, gnc_commodity *curr)
Set a new currency on a transaction.
gint qof_event_register_handler(QofEventHandler handler, gpointer user_data)
Register a handler for events.
#define PWARN(format, args...)
Log a warning.
char * qof_print_date(time64 secs)
Convenience; calls through to qof_print_date_dmy_buff().
void xaccSplitSetAmount(Split *split, gnc_numeric amt)
The xaccSplitSetAmount() method sets the amount in the account's commodity that the split should have...
Functions providing a register page for the GnuCash UI.
Account handling public routines.
gint QofEventId
Define the type of events allowed.
Reduce the result value by common factor elimination, using the smallest possible value for the denom...
GtkTreeView implementation for gnucash account tree.
Income accounts are used to denote income.
Account public routines (C++ api)
Transaction Details page.
class TxnTypeinfo has no functions.
const char * gnc_numeric_errorCode_to_string(GNCNumericErrorCode error_code)
Returns a string representation of the given GNCNumericErrorCode.
void xaccSplitSetMemo(Split *split, const char *memo)
The memo is an arbitrary string associated with a split.
void connect(StockAssistantModel *)
Calls each page's connect function.
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...
bool maybe_reset_txn_types()
Selects a TxnTypevec for the user to pick from depending on whether the account has a positive...
Holds the configuration information from the fieldmask and the data to create a single split...
void prepare(int page, StockAssistantModel *)
Calls the specified page's prepare function.
The bank account type denotes a savings or checking account held at a bank.
void qof_event_unregister_handler(gint handler_id)
Unregister an event handler.
StockTransactionEntry * capgains_entry()
Accessor.
Argument is not a valid number.
available transaction types based on the state of the account, the collection and validation of input...
void xaccTransCommitEdit(Transaction *trans)
The xaccTransCommitEdit() method indicates that the changes to the transaction and its splits are com...
gnc_numeric gnc_numeric_div(gnc_numeric x, gnc_numeric y, gint64 denom, gint how)
Division.
void load(const EntryVec &list_of_splits)
Extract the information from the StockTransactionEntries in the vector created by the model's make_li...
virtual const char * print_price() const
void set_fieldmask(FieldMask mask) override
Set up the state variables from the FieldMask.
StockTransactionEntry * cash_entry()
Accessor.
void xaccTransBeginEdit(Transaction *trans)
The xaccTransBeginEdit() method must be called before any changes are made to a transaction or any of...
asset (and liability) accounts indicate generic, generalized accounts that are none of the above...
std::tuple< bool, std::string, EntryVec > generate_list_of_splits()
Generate the proposed list of splits.
StockTransactionEntry * stock_entry()
Accessor.
void set_transaction_desc(const char *desc)
Setter.
gnc_numeric xaccAccountGetBalanceAsOfDate(Account *acc, time64 date)
Get the balance of the account at the end of the day before the date specified.
All type declarations for the whole Gnucash engine.
gboolean gnc_numeric_positive_p(gnc_numeric a)
Returns 1 if a > 0, otherwise returns 0.
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.
Account * xaccAccountGetAssociatedAccount(const Account *acc, const char *tag)
Get the account's associated account e.g.
virtual const char * print_amount(gnc_numeric amt) const
Split * xaccMallocSplit(QofBook *book)
Constructor.
bool set_txn_type(guint type_idx)
Setter.
Generic api to store and retrieve preferences.
Specialized StockTransactionEntry for the stock split.
gnc_numeric xaccSplitGetValue(const Split *split)
Returns the value of this split in the transaction's commodity.
void xaccAccountBeginEdit(Account *acc)
The xaccAccountBeginEdit() subroutine is the first phase of a two-phase-commit wrapper for account up...
const std::optional< TxnTypeVec > & get_txn_types()
Accessor function.
gnc_commodity * xaccAccountGetCommodity(const Account *acc)
Get the account's commodity.
gboolean gnc_prefs_get_bool(const gchar *group, const gchar *pref_name)
Get a boolean value from the preferences backend.
virtual void set_fieldmask(FieldMask mask)
Set up the state variables from the FieldMask.
std::string amount_str_for_display() const override
Generate a string representation of the value.
time64 gnc_time(time64 *tbuf)
get the current time
GNCNumericErrorCode gnc_numeric_check(gnc_numeric a)
Check for error signal in value.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
void gnc_plugin_page_register_clear_current_filter(GncPluginPage *plugin_page)
This function clears the registers current filter.
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...
const char * xaccAccountGetName(const Account *acc)
Get the account's name.
#define GNC_DENOM_AUTO
Values that can be passed as the 'denom' argument.
API for Transactions and Splits (journal entries)
GNCSplitReg * gnc_plugin_page_register_get_gsr(GncPluginPage *plugin_page)
Get the GNCSplitReg data structure associated with this register page.
C++ wrapper for GncAccounSel, see gnucash/gnome-utils/gnc-account-sel.h.
void xaccAccountCommitEdit(Account *acc)
ThexaccAccountCommitEdit() subroutine is the second phase of a two-phase-commit wrapper for account u...
virtual std::string amount_str_for_display() const
Generate a string representation of the value.
Specialized Entry for fees, taxes, commissions, and so on.
void set_fieldmask(FieldMask mask) override
Set up the state variables from the FieldMask.
std::string get_new_amount_str() const
Accessor.
gnc_numeric xaccSplitGetAmount(const Split *split)
Returns the amount of the split in the account's commodity.
Dividend page, collects an amount, an INCOME account, and a memo.