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" 58 static QofLogModule log_module = GNC_MOD_ASSISTANT;
83 void stock_assistant_prepare_cb (GtkAssistant *assistant, GtkWidget *page,
85 void stock_assistant_finish_cb (GtkAssistant *assistant, gpointer user_data);
86 void stock_assistant_cancel_cb (GtkAssistant *gtkassistant, gpointer user_data);
89 static const char* GNC_PREFS_GROUP =
"dialogs.stock-assistant";
90 static const char* ASSISTANT_STOCK_TRANSACTION_CM_CLASS =
"assistant-stock-transaction";
92 static const char* DIVIDEND_KVP_TAG =
"stock-dividends";
93 static const char* CAPGAINS_KVP_TAG =
"stock-capgains";
94 static const char* PROCEEDS_KVP_TAG =
"stock-cash-proceeds";
95 static const char* FEES_KVP_TAG =
"stock-broker-fees";
99 enum class FieldMask : unsigned
103 ENABLED_CREDIT = 1 << 1,
104 AMOUNT_DEBIT = 1 << 2,
105 AMOUNT_CREDIT = 1 << 3,
106 INPUT_NEW_BALANCE = 1 << 4,
108 ALLOW_NEGATIVE = 1 << 6,
109 CAPITALIZE_DEFAULT = 1 << 7,
110 CAPGAINS_IN_STOCK = 1 << 8,
111 MARKER_SPLIT = 1 << 9,
116 operator |(FieldMask lhs, FieldMask rhs)
118 return static_cast<FieldMask
> (
static_cast<unsigned>(lhs) |
119 static_cast<unsigned>(rhs));
123 operator &(FieldMask lhs, FieldMask rhs)
125 return (static_cast<unsigned>(lhs) & static_cast<unsigned>(rhs));
136 FieldMask stock_amount;
137 FieldMask cash_value;
138 FieldMask fees_value;
139 FieldMask dividend_value;
140 FieldMask capgains_value;
141 const char* friendly_name;
142 const char* explanation;
145 using StringVec = std::vector<std::string>;
146 using TxnTypeVec = std::vector<TxnTypeInfo>;
147 using AccountVec = std::vector<Account*>;
149 static const TxnTypeVec starting_types
153 FieldMask::ENABLED_DEBIT | FieldMask::AMOUNT_DEBIT,
154 FieldMask::ENABLED_CREDIT,
155 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
161 N_(
"Initial stock long purchase.")
164 FieldMask::ENABLED_CREDIT | FieldMask::AMOUNT_CREDIT,
165 FieldMask::ENABLED_DEBIT,
166 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
172 N_(
"Initial stock short sale.")
176 static const TxnTypeVec long_types
179 FieldMask::ENABLED_DEBIT | FieldMask::AMOUNT_DEBIT,
180 FieldMask::ENABLED_CREDIT,
181 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
187 N_(
"Buying stock long.")
190 FieldMask::ENABLED_CREDIT | FieldMask::AMOUNT_CREDIT,
191 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
192 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
194 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO | FieldMask::ALLOW_NEGATIVE | FieldMask::CAPGAINS_IN_STOCK,
198 N_(
"Selling stock long, and record capital gain/loss." 199 "\n\nIf you are unable to calculate capital gains you can enter a " 200 "placeholder amount and correct it in the transaction later.")
203 FieldMask::MARKER_SPLIT,
204 FieldMask::ENABLED_DEBIT,
205 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
206 FieldMask::ENABLED_CREDIT,
211 N_(
"Company issues cash dividends to holder.\n\nAny dividend being " 212 "reinvested must be subsequently recorded as a regular stock purchase.")
215 FieldMask::ENABLED_CREDIT,
216 FieldMask::ENABLED_DEBIT,
217 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
222 N_(
"Return of capital"),
223 N_(
"Company returns capital, reducing the cost basis without affecting # units.")
226 FieldMask::ENABLED_CREDIT,
228 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
229 FieldMask::ENABLED_DEBIT,
233 N_(
"Return of capital (reclassification)"),
234 N_(
"Company returns capital, reducing the cost basis without affecting # units. " 235 "A distribution previously recorded as a dividend is reclassified to return " 236 "of capital, often due to end-of-year tax information.")
239 FieldMask::ENABLED_DEBIT,
241 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
242 FieldMask::ENABLED_CREDIT,
246 N_(
"Notional distribution (dividend)"),
247 N_(
"Company issues a notional distribution, which is recorded as dividend " 248 "income and increases the cost basis without affecting # units.")
251 FieldMask::ENABLED_DEBIT,
253 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
255 FieldMask::ENABLED_CREDIT,
258 N_(
"Notional distribution (capital gain)"),
259 N_(
"Company issues a notional distribution, which is recorded as capital gain " 260 "and increases the cost basis without affecting # units.")
263 FieldMask::AMOUNT_DEBIT | FieldMask::INPUT_NEW_BALANCE,
264 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO,
265 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
271 N_(
"Company issues additional units, thereby reducing the stock price by a divisor " 272 ", while keeping the total monetary value of the overall investment constant. " 273 "\n\nIf the split results in a cash in lieu for remainder units, please " 274 "record the sale using the Stock Transaction Assistant first, then record the split.")
277 FieldMask::AMOUNT_CREDIT | FieldMask::INPUT_NEW_BALANCE,
278 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO,
279 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
284 N_(
"Company redeems units, thereby increasing the stock price by a multiple, while " 285 "keeping the total monetary value of the overall investment constant.\n\nIf the " 286 "reverse split results in a cash in lieu for remainder units, please record the " 287 "sale using the Stock Transaction Assistant first, then record the reverse split.")
291 static const TxnTypeVec short_types
294 FieldMask::ENABLED_CREDIT | FieldMask::AMOUNT_CREDIT,
295 FieldMask::ENABLED_DEBIT,
296 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
302 N_(
"Selling stock short.")
305 FieldMask::ENABLED_DEBIT | FieldMask::AMOUNT_DEBIT,
306 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO,
307 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
309 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO | FieldMask::ALLOW_NEGATIVE | FieldMask::CAPGAINS_IN_STOCK,
312 N_(
"Buy to cover short"),
313 N_(
"Buy back stock to cover short position, and record capital gain/loss. " 314 "\n\nIf you are unable to calculate capital gains you can enter a placeholder " 315 "amount and correct it in the transaction later.")
318 FieldMask::MARKER_SPLIT,
319 FieldMask::ENABLED_CREDIT,
320 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
321 FieldMask::ENABLED_DEBIT,
325 N_(
"Compensatory dividend"),
326 N_(
"Company issues dividends, and the short stock holder must make a compensatory " 327 "payment for the dividend.")
330 FieldMask::ENABLED_DEBIT,
331 FieldMask::ENABLED_CREDIT,
332 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
337 N_(
"Compensatory return of capital"),
338 N_(
"Company returns capital, and the short stock holder must make a compensatory " 339 "payment for the returned capital. This reduces the cost basis (less negative, " 340 "towards 0.00 value) without affecting # units.")
343 FieldMask::ENABLED_DEBIT,
345 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
346 FieldMask::ENABLED_CREDIT,
351 N_(
"Compensatory return of capital (reclassification)"),
352 N_(
"Company returns capital, and the short stock holder must make a compensatory " 353 "payment for the returned capital. This reduces the cost basis (less negative, " 354 "towards 0.00 value) without affecting # units. A distribution previously recorded " 355 "as a compensatory dividend is reclassified to compensatory return of capital," 356 "often due to end-of-year tax information.")
359 FieldMask::ENABLED_CREDIT,
361 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
362 FieldMask::ENABLED_DEBIT,
367 N_(
"Compensatory notional distribution (dividend)"),
368 N_(
"Company issues a notional distribution, and the short stock holder must make a " 369 "compensatory payment for the notional distribution. This is recorded as a " 370 "loss/negative dividend income amount, and increases the cost basis (more " 371 "negative, away from 0.00 value) without affecting # units.")
374 FieldMask::ENABLED_CREDIT,
376 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO,
378 FieldMask::ENABLED_DEBIT,
382 N_(
"Compensatory notional distribution (capital gain)"),
383 N_(
"Company issues a notional distribution, and the short stock holder must make " 384 "a compensatory payment for the notional distribution. This is recorded as a " 385 "capital loss amount, and increases the cost basis (more negative, away from " 386 "0.00 value) without affecting # units.")
389 FieldMask::AMOUNT_CREDIT | FieldMask::INPUT_NEW_BALANCE,
390 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO,
391 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
397 N_(
"Company issues additional units, thereby reducing the stock price by a divisor, " 398 "while keeping the total monetary value of the overall investment constant. " 399 "\n\nIf the split results in a cash in lieu for remainder units, please " 400 "record the cover buy using the Stock Transaction Assistant first, then record the split.")
403 FieldMask::AMOUNT_DEBIT | FieldMask::INPUT_NEW_BALANCE,
404 FieldMask::ENABLED_CREDIT | FieldMask::ALLOW_ZERO,
405 FieldMask::ENABLED_DEBIT | FieldMask::ALLOW_ZERO | FieldMask::CAPITALIZE_DEFAULT,
411 N_(
"Company redeems units, thereby increasing the stock price by a multiple, while " 412 "keeping the total monetary value of the overall investment constant.\n\nIf the " 413 "reverse split results in a cash in lieu for remainder units, please record the " 414 "cover buy using the Stock Transaction Assistant first, then record the reverse split.")
418 enum class LogMsgType
428 const std::string m_message;
430 LogMessage(LogMsgType type, std::string&& message) :
431 m_type{type}, m_message(std::move(message)) {}
432 LogMessage(LogMsgType type,
const char* message) :
433 m_type{type}, m_message(message) {}
437 LogMsgType type() {
return m_type; }
438 const std::string& message() {
return m_message; }
447 using Log = std::vector<LogMessage>;
453 void info(
const char* message) { m_log.emplace_back(LogMsgType::info, message); }
454 void warn(
const char* message) { m_log.emplace_back(LogMsgType::warning, message); }
455 void error(
const char* message) { m_log.emplace_back(LogMsgType::error, message); }
456 void clear() { m_log.clear(); }
459 void write_log(std::stringstream& stream, LogMsgType type);
460 void infos(std::stringstream& stream) {
return write_log(stream, LogMsgType::info); }
461 void warnings(std::stringstream& stream) {
return write_log(stream, LogMsgType::warning); }
462 void errors(std::stringstream& stream) {
return write_log(stream, LogMsgType::error); }
472 Logger::write_log(std::stringstream& stream, LogMsgType type)
474 std::for_each(m_log.begin(), m_log.end(),
476 if (msg.type() == type)
477 stream <<
"\n * " << msg.message();
482 Logger::has_warnings()
484 return std::any_of(m_log.begin(), m_log.end(),
485 [](
auto& msg){
return msg.type() == LogMsgType::warning;
492 return std::any_of(m_log.begin(), m_log.end(),
493 [](
auto& msg){
return msg.type() == LogMsgType::error;
500 std::stringstream summary;
503 summary << _(
"No errors found. Click Apply to create transaction.");
508 summary << _(
"The following errors must be fixed:");
513 summary <<
"\n\n" << _(
"The following warnings exist:");
516 return summary.str();
524 void* handler_data, [[maybe_unused]]
void* event_data);
540 bool m_allow_negative;
541 bool m_input_new_balance =
false;
545 const char* m_action;
546 gnc_numeric m_balance = gnc_numeric_zero();
547 const char* m_kvp_tag;
548 int m_qof_event_handler;
551 m_enabled{
false}, m_debit_side{
false}, m_allow_zero{
false}, m_account{
nullptr},
555 m_enabled{
false}, m_debit_side{
false}, m_allow_zero{
false}, m_account{
nullptr},
565 virtual bool enabled()
const {
return m_enabled; }
566 virtual bool debit_side()
const {
return m_debit_side; }
567 virtual void set_capitalize(
bool capitalize) {}
568 virtual bool input_new_balance()
const {
return m_input_new_balance; }
569 virtual bool do_capitalize()
const {
return false; }
570 virtual void set_account(
Account* account) { m_account = account; }
571 virtual Account* account()
const {
return m_account; }
572 virtual const char* print_account()
const;
573 virtual void set_memo(
const char* memo) { m_memo = memo; }
574 virtual const char* get_kvp_tag () {
return m_kvp_tag; }
575 virtual const char* memo()
const {
return m_memo; }
576 virtual void set_value(gnc_numeric amount);
578 virtual void set_amount(gnc_numeric) {}
579 virtual gnc_numeric amount()
const {
return m_value; }
580 virtual bool has_amount()
const {
return false; }
581 virtual bool marker_split()
const {
return false; }
587 virtual void validate_amount(
Logger&)
const;
588 virtual void set_balance(gnc_numeric balance) { m_balance = balance; }
589 virtual gnc_numeric get_balance()
const {
return m_balance; }
597 virtual void create_split(Transaction* trans, AccountVec& commits)
const;
605 virtual const char*
print_amount(gnc_numeric amt)
const;
629 void* handler_data, [[maybe_unused]]
void* event_data)
632 if ((inst && inst != QOF_INSTANCE(entry->account())) || (
event & QOF_EVENT_DESTROY) == 0)
634 entry->set_account(
nullptr);
637 using StockTransactionEntryPtr = std::unique_ptr<StockTransactionEntry>;
642 m_enabled = mask != FieldMask::DISABLED;
643 m_debit_side = mask & FieldMask::ENABLED_DEBIT;
644 m_allow_zero = mask & FieldMask::ALLOW_ZERO;
645 m_allow_negative = mask & FieldMask::ALLOW_NEGATIVE;
649 StockTransactionEntry::print_account()
const 651 auto acct_required = m_enabled &&
655 acct_required ? _(
"missing") :
"";
659 StockTransactionEntry::set_value(gnc_numeric amount)
670 m_debit_side = !m_debit_side;
676 PINFO(
"Set %s value to %" PRId64
"/%" PRId64, m_action, m_value.num, m_value.denom);
680 StockTransactionEntry::validate_amount(
Logger& logger)
const 682 auto add_error = [&logger](
const char* format_str,
const char* arg)
684 char *buf = g_strdup_printf (_(format_str),
685 g_dpgettext2 (
nullptr,
"Stock Assistant: Page name", arg));
694 add_error (N_(
"Amount for %s is missing."), m_action);
699 add_error (N_(
"Amount for %s must not be negative."), m_action);
702 add_error (N_(
"Amount for %s must be positive."), m_action);
705 add_error(N_(
"The %s amount has no associated account."), m_action);
725 auto pinfo{gnc_commodity_print_info(currency, TRUE)};
735 auto pinfo{gnc_commodity_print_info(commodity, TRUE)};
740 StockTransactionEntry::create_split(Transaction *trans, AccountVec &account_commits)
const 742 g_return_if_fail(trans);
746 xaccSplitSetParent(split, trans);
748 account_commits.push_back(m_account);
749 xaccSplitSetAccount(split, m_account);
754 PINFO(
"creating %s split in Acct(%s): Val(%s), Amt(%s) => Val(%s), Amt(%s)",
760 gnc_set_num_action(
nullptr, split,
nullptr,
761 g_dpgettext2(
nullptr,
"Stock Assistant: Action field",
773 auto pinfo{gnc_price_print_info(currency, TRUE)};
787 bool m_amount_enabled;
788 gnc_numeric m_amount;
789 bool m_marker =
false;
794 PINFO(
"Stock Entry");
799 PINFO(
"Stock Entry");
802 void set_amount(gnc_numeric amount)
override;
803 gnc_numeric amount()
const override {
return m_amount; }
804 bool has_amount()
const override {
return m_amount_enabled; }
805 void validate_amount(
Logger& logger)
const override;
806 void create_split(Transaction *trans, AccountVec &account_commits)
const override;
809 bool marker_split()
const override {
return m_marker; }
816 m_enabled = mask & (FieldMask::ENABLED_CREDIT | FieldMask::ENABLED_DEBIT);
817 m_amount_enabled = mask & (FieldMask::AMOUNT_CREDIT | FieldMask::AMOUNT_DEBIT);
818 m_debit_side = mask & (FieldMask::ENABLED_DEBIT | FieldMask::AMOUNT_DEBIT);
819 m_input_new_balance = mask & FieldMask::INPUT_NEW_BALANCE;
820 m_marker = mask & FieldMask::MARKER_SPLIT;
825 StockTransactionStockEntry::set_amount(gnc_numeric amount)
833 if (m_input_new_balance)
836 m_amount = gnc_numeric_sub_fixed(amount, m_balance);
838 m_amount = gnc_numeric_sub_fixed(m_balance, amount);
850 StockTransactionStockEntry::validate_amount(
Logger& logger)
const 853 StockTransactionEntry::validate_amount(logger);
855 if (!m_amount_enabled)
858 auto add_error_str = [&logger]
859 (
const char* str) { logger.error (_(str)); };
863 add_error_str(_(
"Amount for stock value is missing."));
867 if (m_input_new_balance)
869 auto amount = gnc_numeric_add_fixed(m_debit_side ? m_amount :
gnc_numeric_neg(m_amount), m_balance);
870 auto delta = gnc_numeric_sub_fixed(amount, m_balance);
875 add_error_str(N_(
"Invalid stock new balance."));
877 add_error_str(N_(
"New balance must be higher than old balance."));
879 add_error_str(N_(
"New balance must be lower than old balance."));
881 PINFO(
"Delta %" PRId64
"/%" PRId64
", Ratio %" PRId64
"/%" PRId64, delta.num, delta.denom, ratio.num, ratio.denom);
886 add_error_str(N_(
"Stock amount must be positive."));
888 auto new_bal = gnc_numeric_add_fixed(m_balance, m_amount);
890 add_error_str(N_(
"Cannot sell more units than owned."));
892 add_error_str(N_(
"Cannot cover buy more units than owed."));
903 if (m_input_new_balance)
909 PINFO(
"Computed ratio %" PRId64
"/%" PRId64
"; amount %" PRId64
910 "/%" PRId64
" and balance %" PRId64
"/%" PRId64,
911 ratio.num, ratio.denom, amount.num, amount.denom, m_balance.num, m_balance.denom);
915 std::ostringstream ret;
916 ret << ratio.num <<
':' << ratio.denom;
922 amount = gnc_numeric_add_fixed (amount, m_balance);
931 StockTransactionStockEntry::create_split(Transaction *trans, AccountVec &account_commits)
const 933 g_return_if_fail(trans);
937 xaccSplitSetParent(split, trans);
939 account_commits.push_back(m_account);
940 xaccSplitSetAccount(split, m_account);
944 if (m_amount_enabled)
946 if (m_amount_enabled && !m_enabled)
948 PINFO(
"creating %s split in Acct(%s): Val(%s), Amt(%s) => Val(%s), Amt(%s)",
954 gnc_set_num_action(
nullptr, split,
nullptr,
955 g_dpgettext2(
nullptr,
"Stock Assistant: Action field",
962 if (m_input_new_balance ||
973 auto ainfo{gnc_commodity_print_info (comm,
true)};
974 auto pinfo{gnc_price_print_info (curr,
true)};
975 auto vinfo{gnc_commodity_print_info (curr,
true)};
977 PINFO(
"Calculated price %s from value %s and amount %s",
994 gnc_numeric amount()
const {
return gnc_numeric_zero(); }
997 StockTransactionStockCapGainsEntry::StockTransactionStockCapGainsEntry(
const StockTransactionEntry *cg_entry,
1001 m_debit_side = !m_debit_side;
1002 m_account = stk_entry->account();
1016 void set_capitalize(
bool capitalize)
override { m_capitalize = capitalize; }
1017 bool do_capitalize()
const override {
return m_capitalize; }
1018 void validate_amount(
Logger &logger)
const override;
1019 void create_split(Transaction *trans, AccountVec &commits)
const override;
1026 m_capitalize = mask & FieldMask::CAPITALIZE_DEFAULT;
1030 StockTransactionFeesEntry::validate_amount(
Logger& logger)
const 1032 auto add_error = [&logger](
const char* format_str,
const char* arg)
1034 char *buf = g_strdup_printf (_(format_str),
1035 g_dpgettext2 (
nullptr,
"Stock Assistant: Page name", arg));
1044 add_error (N_(
"Amount for %s is missing."), m_action);
1049 add_error (N_(
"Amount for %s must not be negative."), m_action);
1052 add_error (N_(
"Amount for %s must be positive."), m_action);
1055 add_error(N_(
"The %s amount has no associated account."), m_action);
1059 StockTransactionFeesEntry::create_split(Transaction* trans, AccountVec& commits)
const 1061 g_return_if_fail(trans);
1065 xaccSplitSetParent(split, trans);
1068 xaccSplitSetAccount(split, commits[0]);
1073 commits.push_back(m_account);
1074 xaccSplitSetAccount(split, m_account);
1079 PINFO(
"creating %s split in Acct(%s): Val(%s), Amt(%s) => Val(%s), Amt(%s)",
1085 gnc_set_num_action(
nullptr, split,
nullptr,
1086 g_dpgettext2(
nullptr,
"Stock Assistant: Action field",
1090 using EntryVec = std::vector<StockTransactionEntry*>;
1092 static void stock_assistant_model_date_changed_cb(GtkWidget*,
void*);
1093 static void stock_assistant_model_description_changed_cb(GtkWidget *,
void *);
1104 gnc_commodity* m_currency;
1105 time64 m_transaction_date;
1106 const char* m_transaction_description;
1107 std::optional<TxnTypeVec> m_txn_types;
1109 std::optional<TxnTypeInfo> m_txn_type;
1111 StockTransactionEntryPtr m_stock_entry;
1112 StockTransactionEntryPtr m_cash_entry;
1113 StockTransactionEntryPtr m_fees_entry;
1114 StockTransactionEntryPtr m_dividend_entry;
1115 StockTransactionEntryPtr m_capgains_entry;
1116 StockTransactionEntryPtr m_stock_cg_entry;
1119 std::optional<time64> m_txn_types_date;
1120 bool m_ready_to_create =
false;
1122 EntryVec m_list_of_splits;
1128 m_stock_entry{std::make_unique<StockTransactionStockEntry>(
1129 NC_(
"Stock Assistant: Page name",
"Stock"))},
1130 m_cash_entry{std::make_unique<StockTransactionEntry>(
1131 NC_(
"Stock Assistant: Page name",
"Cash"), PROCEEDS_KVP_TAG)},
1132 m_fees_entry{std::make_unique<StockTransactionFeesEntry>(
1133 NC_(
"Stock Assistant: Page name",
"Fees"), FEES_KVP_TAG)},
1134 m_dividend_entry{std::make_unique<StockTransactionEntry>(
1135 NC_(
"Stock Assistant: Page name",
"Dividend"), DIVIDEND_KVP_TAG)},
1136 m_capgains_entry{std::make_unique<StockTransactionEntry>(
1137 NC_(
"Stock Assistant: Page name",
"Capital Gains"),
1138 CAPGAINS_KVP_TAG)} {
1139 DEBUG (
"StockAssistantModel constructor\n");
1140 m_stock_entry->set_account(m_acct);
1145 DEBUG (
"StockAssistantModel destructor\n");
1185 const std::optional<TxnTypeInfo>&
txn_type() {
return m_txn_type; }
1238 Account* account() {
return m_acct; }
1243 void add_price (QofBook *book);
1249 auto old_bal = m_stock_entry->get_balance();
1252 if (m_txn_types_date && m_txn_types_date == m_transaction_date &&
1255 m_stock_entry->set_balance(new_bal);
1256 m_txn_types_date = m_transaction_date;
1266 if (!m_txn_types_date || m_txn_types_date != m_transaction_date)
1268 PERR (
"transaction_date has changed. rerun maybe_reset_txn_types!");
1273 m_txn_type = m_txn_types->at (type_idx);
1275 catch (
const std::out_of_range&)
1277 PERR (
"out of range type_idx=%d", type_idx);
1281 m_stock_entry->set_fieldmask(m_txn_type->stock_amount);
1282 m_fees_entry->set_fieldmask(m_txn_type->fees_value);
1283 m_capgains_entry->set_fieldmask(m_txn_type->capgains_value);
1284 m_dividend_entry->set_fieldmask(m_txn_type->dividend_value);
1285 m_cash_entry->set_fieldmask(m_txn_type->cash_value);
1290 check_txn_date(
const Split* last_split,
time64 txn_date,
Logger& logger)
1293 if (txn_date <= last_split_date) {
1299 auto warn_txt = g_strdup_printf(
1300 _(
"You will enter a transaction " 1301 "with date %s which is earlier than the latest transaction in this account, " 1302 "dated %s. Doing so may affect the cost basis, and therefore capital gains, " 1303 "of transactions dated after the new entry. Please review all transactions " 1304 "to ensure proper recording."),
1305 new_date_str, last_split_date_str);
1306 logger.warn(warn_txt);
1308 g_free(new_date_str);
1309 g_free(last_split_date_str);
1313 std::tuple<bool, std::string, EntryVec>
1315 if (!m_txn_types || !m_txn_type)
1316 return {
false,
"Error: txn_type not initialized", {} };
1319 m_list_of_splits.clear();
1328 if (
const auto& splits = xaccAccountGetSplits (m_acct); !splits.empty())
1329 check_txn_date(splits.back(), m_transaction_date, m_logger);
1331 if (m_stock_entry->enabled() || m_stock_entry->has_amount())
1333 m_stock_entry->validate_amount(m_logger);
1334 m_list_of_splits.push_back(m_stock_entry.get());
1336 auto price{m_stock_entry->calculate_price()};
1341 auto tmpl = N_(
"A price of 1 %s = %s on %s will be recorded.");
1343 auto price_msg = g_strdup_printf
1346 m_stock_entry->print_price(), date_str);
1347 m_logger.info(price_msg);
1353 if (m_stock_entry->marker_split())
1354 m_list_of_splits.push_back(m_stock_entry.get());
1356 if (m_cash_entry->enabled())
1358 m_cash_entry->validate_amount(m_logger);
1359 m_list_of_splits.push_back (m_cash_entry.get());
1362 if (m_fees_entry->enabled())
1364 m_fees_entry->validate_amount(m_logger);
1365 if (m_fees_entry->do_capitalize())
1366 m_fees_entry->set_account(m_acct);
1367 m_list_of_splits.push_back (m_fees_entry.get());
1370 if (m_dividend_entry->enabled())
1372 m_dividend_entry->validate_amount(m_logger);
1373 m_list_of_splits.push_back (m_dividend_entry.get());
1376 if (m_capgains_entry->enabled())
1379 std::make_unique<StockTransactionStockCapGainsEntry>(m_capgains_entry.get(),
1380 m_stock_entry.get());
1381 m_stock_cg_entry->validate_amount(m_logger);
1382 m_capgains_entry->validate_amount(m_logger);
1383 m_list_of_splits.push_back(m_stock_cg_entry.get());
1384 m_list_of_splits.push_back (m_capgains_entry.get());
1387 std::for_each(m_list_of_splits.begin(), m_list_of_splits.end(),
1388 [&debit, &credit](
auto& entry) {
1389 if (entry->debit_side())
1390 debit += entry->value();
1392 credit += entry->value();
1397 const char *err_act = NULL, *err_reason = NULL;
1411 auto err_str = g_strdup_printf (N_(
"Transaction can't balance, %s is error value %s"), err_act, err_reason);
1412 m_logger.error(err_str);
1417 auto imbalance_str = N_(
"Total Debits of %s does not balance with total Credits of %s.");
1418 auto pinfo{gnc_commodity_print_info (m_currency,
true)};
1421 auto error_str = g_strdup_printf (_(imbalance_str), debit_str, credit_str);
1422 m_logger.error (error_str);
1424 g_free (credit_str);
1431 m_ready_to_create = !m_logger.has_errors();
1432 return { m_ready_to_create, m_logger.
report(), m_list_of_splits };
1435 std::tuple<bool, Transaction*>
1438 if (!m_ready_to_create)
1440 PERR (
"errors exist. cannot create transaction.");
1441 m_list_of_splits.clear();
1442 return {
false,
nullptr};
1450 AccountVec accounts;
1451 std::for_each (m_list_of_splits.begin(), m_list_of_splits.end(),
1454 entry->create_split (trans, accounts);
1455 if (entry->get_kvp_tag() && entry->account())
1461 m_list_of_splits.clear();
1462 m_ready_to_create =
false;
1463 return {
true, trans};
1467 StockAssistantModel::add_price (QofBook *book)
1469 auto stock_price{m_stock_entry->calculate_price()};
1474 gnc_price_begin_edit (price);
1476 gnc_price_set_currency (price, m_currency);
1477 gnc_price_set_time64 (price, m_transaction_date);
1478 gnc_price_set_source (price, PRICE_SOURCE_STOCK_TRANSACTION);
1479 gnc_price_set_typestr (price, PRICE_TYPE_UNK);
1480 gnc_price_set_value (price, stock_price);
1481 gnc_price_commit_edit (price);
1485 PWARN (
"error adding price");
1491 stock_assistant_model_date_changed_cb(GtkWidget* widget,
void* data)
1498 stock_assistant_model_description_changed_cb(GtkWidget* widget,
void* data)
1510 entry->set_memo(gtk_entry_get_text (GTK_ENTRY (widget)));
1514 static inline GtkWidget*
1515 get_widget (GtkBuilder *builder,
const gchar * ID)
1517 g_return_val_if_fail (builder && ID,
nullptr);
1518 auto obj = gtk_builder_get_object (builder, ID);
1520 PWARN (
"get_widget ID '%s' not found. it may be a typo?", ID);
1521 return GTK_WIDGET (obj);
1533 m_edit{gnc_date_edit_new(
gnc_time(
nullptr), FALSE, FALSE)} {}
1534 void attach(GtkBuilder *builder,
const char *table_ID,
const char *label_ID,
1536 time64 get_date_time() {
return gnc_date_edit_get_date_end(GNC_DATE_EDIT(m_edit)); }
1537 void connect(GCallback, gpointer);
1541 GncDateEdit::attach(GtkBuilder *builder,
const char *table_ID,
1542 const char *label_ID,
int row)
1544 auto table = get_widget(builder, table_ID);
1545 auto label = get_widget (builder, label_ID);
1546 gtk_grid_attach(GTK_GRID(
table), m_edit, 1, row, 1, 1);
1547 gtk_widget_show(m_edit);
1548 gnc_date_make_mnemonic_target (GNC_DATE_EDIT(m_edit), label);
1552 GncDateEdit::connect(GCallback cb, gpointer data)
1554 g_signal_connect(m_edit,
"date_changed", cb, data);
1564 GncAmountEdit (GtkBuilder *builder, gnc_commodity *commodity);
1565 void attach (GtkBuilder *builder,
const char *table_id,
1566 const char *label_ID,
int row);
1567 GtkWidget* widget() {
1568 return gnc_amount_edit_gtk_entry(GNC_AMOUNT_EDIT(m_edit));
1571 void connect (GCallback cb, gpointer data);
1572 void set_owner (gpointer obj);
1578 g_return_if_fail(GNC_IS_AMOUNT_EDIT(widget));
1580 auto invalid{gnc_amount_edit_expr_is_valid(GNC_AMOUNT_EDIT(widget),
1581 &value,
true,
nullptr)};
1585 GncAmountEdit::GncAmountEdit (GtkBuilder *builder, gnc_commodity *commodity) :
1586 m_edit{gnc_amount_edit_new()}
1589 auto info = gnc_commodity_print_info(commodity,
true);
1590 gnc_amount_edit_set_evaluate_on_enter(GNC_AMOUNT_EDIT(m_edit), TRUE);
1591 gnc_amount_edit_set_print_info(GNC_AMOUNT_EDIT(m_edit), info);
1595 GncAmountEdit::attach (GtkBuilder *builder,
const char *table_ID,
1596 const char* label_ID,
int row)
1598 auto table = get_widget(builder, table_ID);
1599 auto label = get_widget(builder, label_ID);
1600 gtk_grid_attach(GTK_GRID(
table), m_edit, 1, row, 1, 1);
1601 gtk_widget_show(m_edit);
1602 gnc_amount_edit_make_mnemonic_target(GNC_AMOUNT_EDIT(m_edit), label);
1606 GncAmountEdit::get ()
1609 if (!gnc_amount_edit_expr_is_valid (GNC_AMOUNT_EDIT(m_edit), &amt,
true,
nullptr))
1615 GncAmountEdit::connect (GCallback cb, gpointer data)
1617 g_signal_connect(m_edit,
"changed", cb, data);
1621 GncAmountEdit::set_owner(gpointer obj)
1623 g_object_set_data(G_OBJECT (m_edit),
"owner", obj);
1626 using AccountTypeList = std::vector<GNCAccountType>;
1633 GtkWidget* m_selector;
1636 gnc_commodity *currency,
Account *default_acct);
1637 void attach (GtkBuilder *builder,
const char *table_id,
1638 const char *label_ID,
int row);
1640 void set (
Account *acct) { gnc_account_sel_set_account (GNC_ACCOUNT_SEL (m_selector), acct, TRUE); }
1641 void set_sensitive(
bool sensitive);
1642 Account *
get () {
return gnc_account_sel_get_account (GNC_ACCOUNT_SEL (m_selector)); }
1648 g_return_if_fail (GNC_IS_ACCOUNT_SEL (widget));
1649 entry->set_account(gnc_account_sel_get_account (GNC_ACCOUNT_SEL (widget)));
1652 GncAccountSelector::GncAccountSelector (GtkBuilder *builder, AccountTypeList types,
1653 gnc_commodity *currency,
Account *default_acct) :
1654 m_selector{gnc_account_sel_new ()}
1656 auto accum = [](
auto a,
auto b) {
return g_list_prepend(a, (gpointer)b); };
1657 auto null_glist =
static_cast<GList *
>(
nullptr);
1658 auto acct_list = std::accumulate(types.begin(), types.end(), null_glist, accum);
1659 auto curr_list = accum(null_glist, currency);
1660 gnc_account_sel_set_new_account_ability(GNC_ACCOUNT_SEL(m_selector),
true);
1661 gnc_account_sel_set_acct_filters(GNC_ACCOUNT_SEL(m_selector), acct_list, curr_list);
1662 gnc_account_sel_set_default_new_commodity(GNC_ACCOUNT_SEL(m_selector), currency);
1663 gnc_account_sel_set_new_account_modal (GNC_ACCOUNT_SEL(m_selector),
true);
1665 gnc_account_sel_set_account (GNC_ACCOUNT_SEL(m_selector), default_acct,
true);
1666 g_list_free(acct_list);
1667 g_list_free(curr_list);
1671 GncAccountSelector::attach (GtkBuilder *builder,
const char *table_ID,
1672 const char *label_ID,
int row)
1674 auto table = get_widget(builder, table_ID);
1675 auto label = get_widget(builder, label_ID);
1676 gtk_grid_attach(GTK_GRID(
table), m_selector, 1, row, 1, 1);
1677 gtk_widget_show(m_selector);
1678 gtk_label_set_mnemonic_widget(GTK_LABEL(label), m_selector);
1684 g_signal_connect(m_selector,
"account_sel_changed", G_CALLBACK (gnc_account_sel_changed_cb), entry);
1688 GncAccountSelector::set_sensitive(
bool sensitive)
1690 gtk_widget_set_sensitive(m_selector, sensitive);
1706 assistant_page_set_focus(GtkWidget* page, [[maybe_unused]]GtkDirectionType type, GtkWidget* entry)
1708 gtk_widget_grab_focus(entry);
1709 g_signal_handlers_disconnect_by_data(page, entry);
1745 GtkWidget * m_explanation;
1749 int get_transaction_type_index ();
1750 void set_transaction_types (
const TxnTypeVec& txn_types);
1762 PageTransType::PageTransType(GtkBuilder *builder)
1763 : m_page(get_widget(builder,
"transaction_type_page")),
1764 m_type(get_widget(builder,
"transaction_type_page_combobox")),
1765 m_explanation(get_widget(builder,
"transaction_type_page_explanation"))
1767 g_object_set_data(G_OBJECT(m_type),
"owner",
this);
1773 auto me =
static_cast<PageTransType *
>(g_object_get_data (G_OBJECT (widget),
"owner"));
1774 g_return_if_fail (me);
1775 me->change_txn_type (model);
1785 set_transaction_types(txn_types.value());
1786 change_txn_type (model);
1787 g_signal_connect(m_page,
"focus", G_CALLBACK(assistant_page_set_focus), m_type);
1791 PageTransType::get_transaction_type_index ()
1793 return gtk_combo_box_get_active (GTK_COMBO_BOX (m_type));
1797 PageTransType::set_transaction_types (
const TxnTypeVec& txn_types)
1799 auto combo = GTK_COMBO_BOX_TEXT (m_type);
1800 gtk_combo_box_text_remove_all (combo);
1801 std::for_each (txn_types.begin(), txn_types.end(),
1802 [&combo](
const auto& it)
1803 { gtk_combo_box_text_append_text (combo, _(it.friendly_name)); });
1804 gtk_combo_box_set_active (GTK_COMBO_BOX (combo), 0);
1810 gtk_label_set_text (GTK_LABEL (this->m_explanation), txt);
1816 auto type_idx = get_transaction_type_index();
1829 g_signal_connect(m_type,
"changed",
1830 G_CALLBACK (page_trans_type_changed_cb), model);
1843 GtkWidget *m_description;
1846 time64 get_date_time () {
return m_date.get_date_time(); }
1847 const char* get_description () {
return gtk_entry_get_text (GTK_ENTRY (m_description)); }
1852 PageTransDeets::PageTransDeets (GtkBuilder *builder) :
1853 m_page (get_widget (builder,
"transaction_details_page")),
1855 m_description (get_widget (builder,
"transaction_description_entry"))
1857 m_date.attach(builder,
"transaction_details_table",
"transaction_date_label", 0);
1863 m_date.connect(G_CALLBACK (stock_assistant_model_date_changed_cb),
1864 static_cast<void*>(model));
1865 g_signal_connect(m_description,
"changed",
1866 G_CALLBACK (stock_assistant_model_description_changed_cb),
1867 static_cast<void*>(model));
1875 g_signal_connect(m_page,
"focus", G_CALLBACK(assistant_page_set_focus), m_description);
1891 GtkWidget * m_title;
1892 GtkWidget * m_prev_amount;
1893 GtkWidget * m_next_amount;
1894 GtkWidget * m_next_amount_label;
1896 GtkWidget * m_amount_label;
1900 gnc_numeric get_stock_amount () {
return m_amount.get(); }
1901 void set_stock_amount (std::string new_amount_str);
1905 PageStockAmount::PageStockAmount (GtkBuilder *builder,
Account* account) :
1906 m_page (get_widget (builder,
"stock_amount_page")),
1907 m_title (get_widget (builder,
"stock_amount_title")),
1908 m_prev_amount (get_widget (builder,
"prev_balance_amount")),
1909 m_next_amount (get_widget (builder,
"next_balance_amount")),
1910 m_next_amount_label (get_widget (builder,
"next_balance_label")),
1912 m_amount_label (get_widget (builder,
"stock_amount_label"))
1914 m_amount.attach (builder,
"stock_amount_table",
"stock_amount_label", 1);
1920 gtk_label_set_text_with_mnemonic
1921 (GTK_LABEL (m_amount_label),
1922 entry->input_new_balance() ? _(
"Ne_w Balance") : _(
"_Shares"));
1924 (GTK_LABEL (m_next_amount_label),
1925 entry->input_new_balance() ? _(
"Ratio") : _(
"Next Balance"));
1927 (GTK_LABEL (m_title),
1928 entry->input_new_balance() ?
1929 _(
"Enter the new balance of shares after the stock split.") :
1930 _(
"Enter the number of shares you gained or lost in the transaction."));
1931 gtk_label_set_text (GTK_LABEL (m_prev_amount), entry->
print_amount(entry->get_balance()));
1933 entry->set_amount(get_stock_amount());
1935 g_signal_connect(m_page,
"focus", G_CALLBACK(assistant_page_set_focus), m_amount.widget());
1941 auto me =
static_cast<PageStockAmount*
>(g_object_get_data (G_OBJECT (widget),
"owner"));
1942 entry->set_amount(me->get_stock_amount());
1949 m_amount.connect(G_CALLBACK (page_stock_amount_changed_cb), entry);
1950 m_amount.set_owner(static_cast<gpointer>(
this));
1954 PageStockAmount::set_stock_amount (std::string new_amount_str)
1956 gtk_label_set_text (GTK_LABEL(m_next_amount), new_amount_str.c_str());
1968 GtkWidget * m_price;
1972 const char* get_memo ();
1976 void set_price(
const gchar *val);
1982 auto me =
static_cast<PageStockValue*
>(g_object_get_data (G_OBJECT (widget),
"owner"));
1983 entry->set_value (me->value_edit().get());
1987 PageStockValue::PageStockValue(GtkBuilder *builder,
Account* account)
1988 : m_page(get_widget(builder,
"stock_value_page")),
1990 m_price(get_widget(builder,
"stock_price_amount")),
1991 m_memo(get_widget(builder,
"stock_memo_entry"))
1993 m_value.attach(builder,
"stock_value_table",
"stock_value_label", 0);
1999 m_value.connect(G_CALLBACK (page_stock_value_changed_cb), entry);
2000 m_value.set_owner (static_cast<gpointer>(
this));
2001 g_signal_connect (m_memo,
"changed", G_CALLBACK(text_entry_changed_cb), entry);
2007 entry->set_memo(get_memo());
2009 entry->set_value(m_value.get());
2011 g_signal_connect(m_page,
"focus", G_CALLBACK(assistant_page_set_focus), m_value.widget());
2015 PageStockValue::get_memo()
2017 return gtk_entry_get_text(GTK_ENTRY (m_memo));
2021 PageStockValue::set_price (
const gchar *val)
2023 gtk_label_set_text(GTK_LABEL(this->m_price), val);
2042 const char* get_memo();
2045 PageCash::PageCash(GtkBuilder *builder,
Account* account)
2046 : m_page(get_widget(builder,
"cash_details_page")),
2050 m_memo(get_widget(builder,
"cash_memo_entry")),
2053 m_account.attach (builder,
"cash_table",
"cash_account_label", 0);
2054 m_value.attach (builder,
"cash_table",
"cash_label", 1);
2060 m_account.connect(entry);
2061 g_signal_connect(m_memo,
"changed", G_CALLBACK(text_entry_changed_cb), entry);
2062 m_value.connect(G_CALLBACK(value_changed_cb), entry);
2068 entry->set_memo(get_memo());
2070 entry->set_value(m_value.get());
2071 entry->set_account(m_account.get());
2072 g_signal_connect(m_page,
"focus", G_CALLBACK(assistant_page_set_focus), m_value.widget());
2076 PageCash::get_memo()
2078 return gtk_entry_get_text(GTK_ENTRY (m_memo));
2090 GtkWidget * m_capitalize;
2098 bool get_capitalize_fees ();
2099 const char* get_memo();
2100 void set_capitalize_fees (
bool state);
2101 void set_account (
Account *acct) { m_account.set(acct); }
2102 Account* stock_account() {
return m_stock_account; }
2103 void update_fees_acct_sensitive (
bool sensitive);
2107 PageFees::PageFees(GtkBuilder *builder,
Account* account)
2108 : m_page(get_widget(builder,
"fees_details_page")),
2110 get_widget(builder,
"capitalize_fees_checkbutton")),
2113 m_memo(get_widget(builder,
"fees_memo_entry")),
2115 m_stock_account(account)
2117 m_account.attach (builder,
"fees_table",
"fees_account_label", 1);
2118 m_value.attach(builder,
"fees_table",
"fees_label", 2);
2122 PageFees::get_capitalize_fees()
2124 return gtk_toggle_button_get_active(
2125 GTK_TOGGLE_BUTTON(m_capitalize));
2129 PageFees::get_memo()
2131 return gtk_entry_get_text(GTK_ENTRY (m_memo));
2135 PageFees::set_capitalize_fees(
bool state)
2137 gtk_toggle_button_set_active(
2138 GTK_TOGGLE_BUTTON(m_capitalize), state);
2142 PageFees::update_fees_acct_sensitive(
bool sensitive)
2144 m_account.set_sensitive(sensitive);
2150 g_return_if_fail (entry);
2151 auto me =
static_cast<PageFees *
>(g_object_get_data (G_OBJECT (widget),
"owner"));
2152 g_return_if_fail (me);
2153 bool cap = me->get_capitalize_fees();
2154 entry->set_capitalize(cap);
2156 entry->set_account(me->stock_account());
2157 me->update_fees_acct_sensitive(!cap);
2163 m_account.connect(entry);
2164 g_signal_connect(m_memo,
"changed", G_CALLBACK(text_entry_changed_cb), entry);
2165 m_value.connect(G_CALLBACK(value_changed_cb), entry);
2166 g_object_set_data(G_OBJECT (m_capitalize),
"owner",
this);
2167 g_signal_connect (m_capitalize,
"toggled", G_CALLBACK (capitalize_fees_toggled_cb), entry);
2173 set_capitalize_fees (entry->do_capitalize());
2174 entry->set_memo(get_memo());
2176 entry->set_value (m_value.get());
2177 entry->set_account(m_account.get());
2178 g_signal_connect(m_page,
"focus", G_CALLBACK(assistant_page_set_focus), m_value.widget());
2194 const char* get_memo();
2197 PageDividend::PageDividend(GtkBuilder *builder,
Account* account)
2198 : m_page(get_widget(builder,
"dividend_details_page")),
2201 m_memo(get_widget(builder,
"dividend_memo_entry")),
2204 m_account.attach(builder,
"dividend_table",
"dividend_account_label", 0);
2205 m_value.attach(builder,
"dividend_table",
"dividend_label", 1);
2212 m_account.connect(entry);
2213 g_signal_connect(m_memo,
"changed", G_CALLBACK(text_entry_changed_cb), entry);
2214 m_value.connect(G_CALLBACK(value_changed_cb), entry);
2220 entry->set_memo(get_memo());
2222 entry->set_value(m_value.get());
2223 entry->set_account(m_account.get());
2224 g_signal_connect(m_page,
"focus", G_CALLBACK(assistant_page_set_focus), m_value.widget());
2228 PageDividend::get_memo()
2230 return gtk_entry_get_text(GTK_ENTRY (m_memo));
2244 const char* get_memo();
2247 PageCapGain::PageCapGain (GtkBuilder *builder,
Account* account) :
2248 m_page (get_widget (builder,
"capgains_details_page")),
2251 m_memo (get_widget (builder,
"capgains_memo_entry")),
2254 m_account.attach(builder,
"capgains_table",
"capgains_account_label", 0);
2255 m_value.attach(builder,
"capgains_table",
"capgains_label", 1);
2259 PageCapGain::get_memo()
2261 return gtk_entry_get_text(GTK_ENTRY (m_memo));
2268 m_account.connect(entry);
2269 g_signal_connect(m_memo,
"changed", G_CALLBACK(text_entry_changed_cb), entry);
2270 m_value.connect(G_CALLBACK(value_changed_cb), entry);
2276 entry->set_memo(get_memo());
2278 entry->set_value(m_value.get());
2279 entry->set_account(m_account.get());
2280 g_signal_connect(m_page,
"focus", G_CALLBACK(assistant_page_set_focus), m_value.widget());
2286 SPLIT_COL_ACCOUNT = 0,
2292 SPLIT_COL_UNITS_COLOR,
2299 GtkWidget *m_treeview;
2306 void load(
const EntryVec& list_of_splits);
2309 GncFinishTreeview::GncFinishTreeview (GtkBuilder *builder) :
2310 m_treeview{get_widget (builder,
"transaction_view")}
2312 auto view = GTK_TREE_VIEW (m_treeview);
2313 gtk_tree_view_set_grid_lines (GTK_TREE_VIEW(view), gnc_tree_view_get_grid_lines_pref ());
2315 auto store = gtk_list_store_new (NUM_SPLIT_COLS, G_TYPE_STRING, G_TYPE_STRING,
2316 G_TYPE_STRING, G_TYPE_STRING, G_TYPE_STRING,
2317 G_TYPE_STRING, G_TYPE_STRING);
2318 gtk_tree_view_set_model(view, GTK_TREE_MODEL(store));
2319 gtk_tree_selection_set_mode (gtk_tree_view_get_selection (view),
2320 GTK_SELECTION_NONE);
2321 g_object_unref(store);
2323 auto renderer = gtk_cell_renderer_text_new();
2324 auto column = gtk_tree_view_column_new_with_attributes
2325 (_(
"Account"), renderer,
"text", SPLIT_COL_ACCOUNT,
nullptr);
2326 gtk_tree_view_append_column(view, column);
2328 renderer = gtk_cell_renderer_text_new();
2329 g_object_set (renderer,
"ellipsize", PANGO_ELLIPSIZE_END,
nullptr);
2330 column = gtk_tree_view_column_new_with_attributes
2331 (_(
"Memo"), renderer,
"text", SPLIT_COL_MEMO,
nullptr);
2332 gtk_tree_view_column_set_expand (column,
true);
2333 gtk_tree_view_append_column(view, column);
2335 renderer = gtk_cell_renderer_text_new();
2336 gtk_cell_renderer_set_alignment (renderer, 1.0, 0.5);
2337 gtk_cell_renderer_set_padding (renderer, 5, 0);
2338 column = gtk_tree_view_column_new_with_attributes
2339 (_(
"Debit"), renderer,
"text", SPLIT_COL_DEBIT,
nullptr);
2340 gtk_tree_view_append_column(view, column);
2342 renderer = gtk_cell_renderer_text_new();
2343 gtk_cell_renderer_set_alignment (renderer, 1.0, 0.5);
2344 gtk_cell_renderer_set_padding (renderer, 5, 0);
2345 column = gtk_tree_view_column_new_with_attributes
2346 (_(
"Credit"), renderer,
"text", SPLIT_COL_CREDIT,
nullptr);
2347 gtk_tree_view_append_column(view, column);
2349 renderer = gtk_cell_renderer_text_new();
2350 gtk_cell_renderer_set_alignment (renderer, 1.0, 0.5);
2351 gtk_cell_renderer_set_padding (renderer, 5, 0);
2352 column = gtk_tree_view_column_new_with_attributes
2353 (_(
"Units"), renderer,
2354 "text", SPLIT_COL_UNITS,
2355 "foreground", SPLIT_COL_UNITS_COLOR,
2357 gtk_tree_view_append_column(view, column);
2358 gtk_tree_view_set_tooltip_column(GTK_TREE_VIEW(m_treeview),
2359 SPLIT_COL_TOOLTIP);}
2364 auto gtv = GTK_TREE_VIEW(m_treeview);
2366 GNC_PREF_NEGATIVE_IN_RED);
2367 auto list = GTK_LIST_STORE(gtk_tree_view_get_model(gtv));
2368 gtk_list_store_clear(list);
2369 for (
const auto &entry : list_of_splits) {
2371 auto memo{entry->memo()};
2372 auto tooltip = (memo && *memo ?
2373 g_markup_escape_text(memo, -1) : strdup(
""));
2379 auto char2str{[](
const char* str) -> std::string {
2380 return std::string{ str ? str :
"" }; }};
2382 auto units{char2str(entry->has_amount() ?
2383 entry->
print_amount(entry->debit_side() ? entry->amount() :
2385 auto units_in_red{negative_in_red && !entry->debit_side()};
2386 gtk_list_store_append(list, &iter);
2390 entry->print_account(), SPLIT_COL_MEMO,
2391 entry->memo(), SPLIT_COL_TOOLTIP, tooltip, SPLIT_COL_DEBIT,
2392 entry->debit_side() ? amount.c_str() :
nullptr,
2394 entry->debit_side() ? nullptr : amount.c_str(),
2395 SPLIT_COL_UNITS, units.c_str(),
2396 SPLIT_COL_UNITS_COLOR, units_in_red ?
"red" :
nullptr, -1);
2409 GtkWidget * m_summary;
2415 PageFinish::PageFinish (GtkBuilder *builder) :
2416 m_page (get_widget (builder,
"finish_page")), m_view (builder),
2417 m_summary (get_widget (builder,
"finish_summary")) {}
2424 m_view.
load(list_of_splits);
2425 gtk_label_set_text(GTK_LABEL(m_summary), summary.c_str());
2426 gtk_assistant_set_page_complete(GTK_ASSISTANT(window), m_page, success);
2429 enum assistant_pages
2432 PAGE_TRANSACTION_DETAILS,
2433 PAGE_TRANSACTION_TYPE,
2445 GtkWidget * m_window;
2472 GtkWidget* window() {
return m_window; }
2475 StockAssistantView::StockAssistantView (GtkBuilder *builder,
Account* account, GtkWidget *parent) :
2476 m_window (get_widget (builder,
"stock_transaction_assistant")), m_type_page(builder), m_deets_page(builder),
2477 m_stock_amount_page (builder, account), m_stock_value_page (builder, account), m_cash_page (builder, account),
2478 m_fees_page (builder, account), m_dividend_page (builder, account), m_capgain_page (builder, account),
2479 m_finish_page (builder)
2482 gtk_widget_set_name (GTK_WIDGET(m_window),
"gnc-id-assistant-stock-transaction");
2483 gtk_window_set_transient_for (GTK_WINDOW (m_window), GTK_WINDOW(parent));
2484 gnc_window_adjust_for_screen (GTK_WINDOW(m_window));
2485 gnc_restore_window_size (GNC_PREFS_GROUP, GTK_WINDOW(m_window),
2486 GTK_WINDOW(parent));
2487 gtk_widget_show_all (m_window);
2488 DEBUG (
"StockAssistantView constructor\n");
2491 StockAssistantView::~StockAssistantView()
2493 gnc_save_window_size (GNC_PREFS_GROUP, GTK_WINDOW(m_window));
2494 gtk_widget_destroy (m_window);
2495 DEBUG (
"StockAssistantView destructor\n");
2505 forward_page_func (gint current_page,
void* data)
2510 return current_page;
2512 if (!model->
stock_entry()->has_amount() && current_page == PAGE_STOCK_AMOUNT)
2514 if (!model->
stock_entry()->enabled() && current_page == PAGE_STOCK_VALUE)
2516 if (!model->
cash_entry()->enabled() && current_page == PAGE_CASH)
2518 if (!model->
fees_entry()->enabled() && current_page == PAGE_FEES)
2520 if (!model->
dividend_entry()->enabled() && current_page == PAGE_DIVIDEND)
2522 if (!model->
capgains_entry()->enabled() && current_page == PAGE_CAPGAINS)
2525 return current_page;
2531 m_type_page.connect(model);
2532 m_deets_page.connect(model);
2533 m_stock_amount_page.connect(model->
stock_entry());
2540 gtk_assistant_set_forward_page_func (GTK_ASSISTANT(m_window),
2541 (GtkAssistantPageFunc)forward_page_func,
2548 g_return_if_fail (page < PAGE_STOCK_AMOUNT || model->txn_type_valid());
2551 case PAGE_TRANSACTION_TYPE:
2554 m_type_page.prepare(model);
2556 case PAGE_TRANSACTION_DETAILS:
2557 m_deets_page.prepare(model);
2559 case PAGE_STOCK_AMOUNT:
2561 m_stock_amount_page.prepare(model->
stock_entry());
2564 case PAGE_STOCK_VALUE:
2585 m_finish_page.prepare (m_window, model);
2599 void* handler_data, [[maybe_unused]]
void* event_data);
2603 std::unique_ptr<StockAssistantModel> m_model;
2605 bool m_destroying =
false;
2606 int m_qof_event_handler;
2609 : m_model{std::make_unique<StockAssistantModel>(acct)},
2610 m_view{builder, acct, parent},
2613 connect_signals (builder);
2614 DEBUG (
"StockAssistantController constructor\n");
2617 void connect_signals(GtkBuilder *builder);
2618 void prepare(GtkAssistant* assistant, GtkWidget *page);
2620 bool destroying() {
return m_destroying; }
2621 Account* model_account() {
return m_model->account(); }
2624 static void stock_assistant_window_destroy_cb(GtkWidget *
object, gpointer user_data);
2625 static void close_handler (gpointer user_data);
2627 StockAssistantController::~StockAssistantController()
2629 m_destroying =
true;
2630 gnc_unregister_gui_component_by_data (ASSISTANT_STOCK_TRANSACTION_CM_CLASS,
this);
2635 StockAssistantController::connect_signals (GtkBuilder *builder)
2637 m_view.
connect(m_model.get());
2638 gtk_builder_connect_signals (builder,
this);
2639 g_signal_connect (m_view.window(),
"destroy",
2640 G_CALLBACK (stock_assistant_window_destroy_cb),
this);
2643 auto component_id = gnc_register_gui_component
2644 (ASSISTANT_STOCK_TRANSACTION_CM_CLASS,
nullptr, close_handler,
this);
2645 gnc_gui_component_watch_entity_type (component_id, GNC_ID_ACCOUNT,
2646 QOF_EVENT_MODIFY | QOF_EVENT_DESTROY);
2650 StockAssistantController::prepare(GtkAssistant* assistant, GtkWidget* page)
2652 auto currentpage = gtk_assistant_get_current_page(assistant);
2653 m_view.
prepare(currentpage, m_model.get());
2657 StockAssistantController::finish()
2659 g_return_if_fail (m_model->txn_type_valid());
2661 gnc_suspend_gui_refresh ();
2662 [[maybe_unused]]
auto [success, trans] = m_model->create_transaction();
2663 gnc_resume_gui_refresh ();
2665 gnc_close_gui_component_by_data (ASSISTANT_STOCK_TRANSACTION_CM_CLASS,
this);
2670 void* handler_data, [[maybe_unused]]
void* event_data)
2673 if ((inst && inst != QOF_INSTANCE(controller->model_account())) || (
event & QOF_EVENT_DESTROY) == 0 ||
2674 controller->destroying())
2682 stock_assistant_prepare_cb (GtkAssistant *assistant, GtkWidget *page,
2686 info->prepare(assistant, page);
2691 stock_assistant_window_destroy_cb (GtkWidget *
object, gpointer user_data)
2694 if (controller->destroying())
2697 gnc_close_gui_component_by_data (ASSISTANT_STOCK_TRANSACTION_CM_CLASS, controller);
2702 stock_assistant_finish_cb (GtkAssistant *assistant, gpointer user_data)
2705 controller->finish();
2710 stock_assistant_cancel_cb (GtkAssistant *assistant, gpointer user_data)
2713 if (controller->destroying())
2715 gnc_close_gui_component_by_data (ASSISTANT_STOCK_TRANSACTION_CM_CLASS, controller);
2720 close_handler (gpointer user_data)
2723 if (controller->destroying())
2737 gnc_stock_transaction_assistant (GtkWidget *parent,
Account *account)
2739 auto builder = gtk_builder_new();
2740 gnc_builder_add_from_file(builder,
"assistant-stock-transaction.glade",
2741 "stock_transaction_assistant");
2744 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...
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.
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...
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 a, gnc_numeric b, 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 in)
Check for error signal in value.
gint64 time64
Most systems that are currently maintained, including Microsoft Windows, BSD-derived Unixes and Linux...
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)
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.