diff options
Diffstat (limited to 'cashflow.cpp')
| -rw-r--r-- | cashflow.cpp | 206 |
1 files changed, 192 insertions, 14 deletions
diff --git a/cashflow.cpp b/cashflow.cpp index 8eb46a4..f302110 100644 --- a/cashflow.cpp +++ b/cashflow.cpp @@ -71,6 +71,10 @@ void CashFlow::setupConnections() { connect(ui->newBtn, &QPushButton::clicked, this, &CashFlow::onNewTransaction); connect(ui->deleteBtn, &QPushButton::clicked, this, &CashFlow::onDeleteTransaction); + // Transaction entry recurring rule linking + connect(ui->entryRecurringCombo, QOverload<int>::of(&QComboBox::currentIndexChanged), this, &CashFlow::onRecurringRuleChanged); + connect(ui->entryDateEdit, &QDateEdit::dateChanged, this, &CashFlow::onTransactionDateChanged); + // Set up Delete key shortcut for transaction table ui->deleteBtn->setShortcut(Qt::Key_Delete); @@ -131,6 +135,9 @@ void CashFlow::refreshView() { ui->entryCategoryCombo->setCurrentText(currentCategory); ui->entryCategoryCombo->blockSignals(false); + // Populate recurring rules combo + populateRecurringRulesCombo(); + // Populate recurring rule account and category combos ui->recurringAccountCombo->blockSignals(true); ui->recurringAccountCombo->clear(); @@ -200,6 +207,10 @@ void CashFlow::refreshTransactionTable() { // Store ID in first column's data for retrieval later QTableWidgetItem *dateItem = new QTableWidgetItem(t.date.toString("MM/dd/yy")); dateItem->setData(Qt::UserRole, t.id); + // Store full transaction for projected items (id == -1) + if (t.id == -1) { + dateItem->setData(Qt::UserRole + 1, QVariant::fromValue(t)); + } dateItem->setFlags(dateItem->flags() & ~Qt::ItemIsEditable); ui->transactionTable->setItem(row, 0, dateItem); @@ -484,6 +495,15 @@ QList<Transaction> CashFlow::generateProjectedTransactions() { QDate startDate = ui->dateFromEdit->date(); QDate endDate = ui->dateToEdit->date(); + // Load all actual transactions with recurring_id (converted projections) + QList<Transaction> actualTransactions = database->getAllTransactions(); + QSet<QString> existingOccurrences; // recurring_id:occurrence_key pairs + for (const Transaction &t : actualTransactions) { + if (t.recurringId != -1 && !t.occurrenceKey.isEmpty()) { + existingOccurrences.insert(QString("%1:%2").arg(t.recurringId).arg(t.occurrenceKey)); + } + } + for (const RecurringRule &rule : rules) { QDate currentDate = rule.startDate > startDate ? rule.startDate : startDate; @@ -514,17 +534,37 @@ QList<Transaction> CashFlow::generateProjectedTransactions() { break; } - Transaction t; - t.id = -1; // Projected transactions have no ID - t.date = currentDate; - t.amount = rule.amount; - t.account = rule.account; - t.category = rule.category; - t.description = rule.description + " (projected)"; - t.type = TransactionType::Estimated; - t.recurringId = rule.id; + // Generate occurrence key based on frequency + QString occurrenceKey; + if (rule.frequency == RecurrenceFrequency::Daily) { + occurrenceKey = currentDate.toString("yyyy-MM-dd"); + } else if (rule.frequency == RecurrenceFrequency::Weekly || rule.frequency == RecurrenceFrequency::BiWeekly) { + occurrenceKey = QString("%1-W%2").arg(currentDate.year()).arg(currentDate.weekNumber(), 2, 10, QChar('0')); + } else if (rule.frequency == RecurrenceFrequency::Monthly) { + occurrenceKey = currentDate.toString("yyyy-MM"); + } else if (rule.frequency == RecurrenceFrequency::Yearly) { + occurrenceKey = QString::number(currentDate.year()); + } - projections.append(t); + // Skip if actual already exists for this occurrence + QString occurrenceCheck = QString("%1:%2").arg(rule.id).arg(occurrenceKey); + if (existingOccurrences.contains(occurrenceCheck)) { + // Actual exists, skip this projection + } else { + Transaction t; + t.id = -1; // Projected transactions have no ID + t.date = currentDate; + t.amount = rule.amount; + t.account = rule.account; + t.category = rule.category; + t.description = rule.description + " (projected)"; + t.type = TransactionType::Estimated; + t.recurringId = rule.id; + t.occurrenceKey = occurrenceKey; + t.reconciled = false; + + projections.append(t); + } count++; // Calculate next occurrence @@ -574,10 +614,16 @@ void CashFlow::onTransactionSelected() { int row = selected[0]->row(); int id = ui->transactionTable->item(row, 0)->data(Qt::UserRole).toInt(); - // If it's a projected transaction (id = -1), don't load it for editing + // If it's a projected transaction (id = -1), load it for editing if (id == -1) { - ui->entryStatusLabel->setText("(Projected - cannot edit)"); - currentTransactionId = -1; + QVariant projectedData = ui->transactionTable->item(row, 0)->data(Qt::UserRole + 1); + if (projectedData.canConvert<Transaction>()) { + Transaction t = projectedData.value<Transaction>(); + currentTransactionId = -1; // Will create new actual when saved + currentProjectedTransaction = t; // Store for conversion + loadTransactionToEntry(t); + ui->entryStatusLabel->setText("(Converting Projection to Actual)"); + } return; } @@ -619,7 +665,51 @@ void CashFlow::onSaveTransaction() { t.category = ui->entryCategoryCombo->currentText(); t.description = ui->entryDescriptionEdit->text(); t.type = ui->entryTypeCombo->currentText() == "Actual" ? TransactionType::Actual : TransactionType::Estimated; - t.recurringId = -1; // Manual entries don't have recurring ID + + // Check if user manually linked to a recurring rule + int manualRuleId = ui->entryRecurringCombo->currentData().toInt(); + QString manualOccurrenceKey = ui->entryOccurrenceEdit->text().trimmed(); + + // Check if we're converting a projection to actual + if (currentTransactionId == -1 && currentProjectedTransaction.recurringId != -1) { + // Converting projection - keep recurring link and store expected values + t.recurringId = currentProjectedTransaction.recurringId; + t.occurrenceKey = currentProjectedTransaction.occurrenceKey; + t.expectedAmount = currentProjectedTransaction.amount; + t.expectedDate = currentProjectedTransaction.date; + t.reconciled = true; + t.type = TransactionType::Actual; // Force to actual when converting + } else if (manualRuleId != -1 && !manualOccurrenceKey.isEmpty()) { + // User manually linked to recurring rule + t.recurringId = manualRuleId; + t.occurrenceKey = manualOccurrenceKey; + t.reconciled = true; + // Try to get expected amount from the rule + QList<RecurringRule> rules = database->getAllRecurringRules(); + for (const RecurringRule &rule : rules) { + if (rule.id == manualRuleId) { + t.expectedAmount = rule.amount; + break; + } + } + } else if (currentTransactionId != -1) { + // Editing existing transaction - load from database to preserve reconciliation fields + QList<Transaction> allTrans = database->getAllTransactions(); + for (const Transaction &existing : allTrans) { + if (existing.id == currentTransactionId) { + t.recurringId = existing.recurringId; + t.occurrenceKey = existing.occurrenceKey; + t.expectedAmount = existing.expectedAmount; + t.expectedDate = existing.expectedDate; + t.reconciled = existing.reconciled; + break; + } + } + } else { + // New manual transaction with no recurring link + t.recurringId = -1; + t.reconciled = false; + } bool success; if (currentTransactionId == -1) { @@ -647,6 +737,7 @@ void CashFlow::onSaveTransaction() { void CashFlow::onNewTransaction() { clearTransactionEntry(); + currentProjectedTransaction = Transaction(); // Reset projected transaction ui->entryDateEdit->setDate(QDate::currentDate()); ui->entryDateEdit->setFocus(); } @@ -771,6 +862,9 @@ void CashFlow::clearTransactionEntry() { ui->entryCategoryCombo->setCurrentText(""); ui->entryDescriptionEdit->clear(); ui->entryTypeCombo->setCurrentIndex(0); + ui->entryRecurringCombo->setCurrentIndex(0); // (None) + ui->entryOccurrenceEdit->clear(); + ui->entryOccurrenceEdit->setEnabled(false); ui->entryStatusLabel->setText("(New transaction)"); updateAmountColors(); } @@ -783,6 +877,8 @@ void CashFlow::loadTransactionToEntry(const Transaction &t) { ui->entryCategoryCombo->blockSignals(true); ui->entryDescriptionEdit->blockSignals(true); ui->entryTypeCombo->blockSignals(true); + ui->entryRecurringCombo->blockSignals(true); + ui->entryOccurrenceEdit->blockSignals(true); ui->entryDateEdit->setDate(t.date); ui->entryAmountSpin->setValue(t.amount); @@ -791,12 +887,31 @@ void CashFlow::loadTransactionToEntry(const Transaction &t) { ui->entryDescriptionEdit->setText(t.description); ui->entryTypeCombo->setCurrentIndex(t.type == TransactionType::Actual ? 1 : 0); + // Set recurring rule link if present + if (t.recurringId != -1) { + // Find and select the rule in combo + for (int i = 0; i < ui->entryRecurringCombo->count(); i++) { + if (ui->entryRecurringCombo->itemData(i).toInt() == t.recurringId) { + ui->entryRecurringCombo->setCurrentIndex(i); + break; + } + } + ui->entryOccurrenceEdit->setText(t.occurrenceKey); + ui->entryOccurrenceEdit->setEnabled(true); + } else { + ui->entryRecurringCombo->setCurrentIndex(0); // (None) + ui->entryOccurrenceEdit->clear(); + ui->entryOccurrenceEdit->setEnabled(false); + } + ui->entryDateEdit->blockSignals(false); ui->entryAmountSpin->blockSignals(false); ui->entryAccountCombo->blockSignals(false); ui->entryCategoryCombo->blockSignals(false); ui->entryDescriptionEdit->blockSignals(false); ui->entryTypeCombo->blockSignals(false); + ui->entryRecurringCombo->blockSignals(false); + ui->entryOccurrenceEdit->blockSignals(false); updateAmountColors(); } @@ -966,3 +1081,66 @@ void CashFlow::onPreferences() { refreshView(); } } + +void CashFlow::populateRecurringRulesCombo() { + ui->entryRecurringCombo->clear(); + ui->entryRecurringCombo->addItem("(None)", -1); + + QList<RecurringRule> rules = database->getAllRecurringRules(); + for (const RecurringRule &rule : rules) { + QString label = QString("%1 (%2)").arg(rule.name).arg( + rule.frequency == RecurrenceFrequency::Daily ? "Daily" : + rule.frequency == RecurrenceFrequency::Weekly ? "Weekly" : + rule.frequency == RecurrenceFrequency::BiWeekly ? "Bi-weekly" : + rule.frequency == RecurrenceFrequency::Monthly ? "Monthly" : + rule.frequency == RecurrenceFrequency::Yearly ? "Yearly" : "Unknown" + ); + ui->entryRecurringCombo->addItem(label, rule.id); + } +} + +QString CashFlow::generateOccurrenceKey(const QDate &date, RecurrenceFrequency frequency) const { + if (frequency == RecurrenceFrequency::Daily) { + return date.toString("yyyy-MM-dd"); + } else if (frequency == RecurrenceFrequency::Weekly || frequency == RecurrenceFrequency::BiWeekly) { + return QString("%1-W%2").arg(date.year()).arg(date.weekNumber(), 2, 10, QChar('0')); + } else if (frequency == RecurrenceFrequency::Monthly) { + return date.toString("yyyy-MM"); + } else if (frequency == RecurrenceFrequency::Yearly) { + return QString::number(date.year()); + } + return QString(); +} + +void CashFlow::updateOccurrenceKey() { + int ruleId = ui->entryRecurringCombo->currentData().toInt(); + if (ruleId == -1) { + ui->entryOccurrenceEdit->clear(); + ui->entryOccurrenceEdit->setEnabled(false); + return; + } + + ui->entryOccurrenceEdit->setEnabled(true); + + // Find the rule to get its frequency + QList<RecurringRule> rules = database->getAllRecurringRules(); + for (const RecurringRule &rule : rules) { + if (rule.id == ruleId) { + QString occurrenceKey = generateOccurrenceKey(ui->entryDateEdit->date(), rule.frequency); + ui->entryOccurrenceEdit->setText(occurrenceKey); + break; + } + } +} + +void CashFlow::onRecurringRuleChanged() { + updateOccurrenceKey(); +} + +void CashFlow::onTransactionDateChanged() { + // Update occurrence key if a recurring rule is selected + if (ui->entryRecurringCombo->currentData().toInt() != -1) { + updateOccurrenceKey(); + } +} + |
