From 04f67a786f6da93bdb44fc24e88805cca7f5ff8d Mon Sep 17 00:00:00 2001 From: Miguel Jacq Date: Wed, 31 Dec 2025 16:09:16 +1100 Subject: [PATCH] Add ability to delete an invoice via Manage Invoices dialog --- CHANGELOG.md | 4 +++ bouquin/db.py | 12 ++++++++ bouquin/invoices.py | 66 +++++++++++++++++++++++++++++++++++++++++ bouquin/locales/en.json | 3 +- bouquin/locales/fr.json | 3 +- debian/changelog | 6 ++++ rpm/bouquin.spec | 4 ++- 7 files changed, 95 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 27bd1a3..808fb36 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,7 @@ +# 0.8.2 + + * Add ability to delete an invoice via 'Manage Invoices' dialog + # 0.8.1 * Fix bold/italic/strikethrough styling in certain conditions when toolbar action is used. diff --git a/bouquin/db.py b/bouquin/db.py index d1c6a69..157aae8 100644 --- a/bouquin/db.py +++ b/bouquin/db.py @@ -2392,6 +2392,18 @@ class DBManager: (document_id, invoice_id), ) + def delete_invoice(self, invoice_id: int) -> None: + """Delete an invoice. + + Related invoice line items and invoice ↔ time log links are removed via + ON DELETE CASCADE. + """ + with self.conn: + self.conn.execute( + "DELETE FROM invoices WHERE id = ?", + (invoice_id,), + ) + def time_logs_for_range( self, project_id: int, diff --git a/bouquin/invoices.py b/bouquin/invoices.py index a0b50cb..fde6a92 100644 --- a/bouquin/invoices.py +++ b/bouquin/invoices.py @@ -1065,6 +1065,10 @@ class InvoicesDialog(QDialog): btn_row = QHBoxLayout() btn_row.addStretch(1) + delete_btn = QPushButton(strings._("delete")) + delete_btn.clicked.connect(self._on_delete_clicked) + btn_row.addWidget(delete_btn) + close_btn = QPushButton(strings._("close")) close_btn.clicked.connect(self.accept) btn_row.addWidget(close_btn) @@ -1073,6 +1077,68 @@ class InvoicesDialog(QDialog): self._reload_invoices() + # ----------------------------------------------------------------- deletion + + def _on_delete_clicked(self) -> None: + """Delete the currently selected invoice.""" + row = self.table.currentRow() + if row < 0: + sel = self.table.selectionModel().selectedRows() + if sel: + row = sel[0].row() + if row < 0: + QMessageBox.information( + self, + strings._("delete"), + strings._("invoice_required"), + ) + return + + base_item = self.table.item(row, self.COL_NUMBER) + if base_item is None: + return + + inv_id = base_item.data(Qt.ItemDataRole.UserRole) + if not inv_id: + return + + invoice_number = (base_item.text() or "").strip() or "?" + proj_item = self.table.item(row, self.COL_PROJECT) + project_name = (proj_item.text() if proj_item is not None else "").strip() + + label = strings._("delete") + prompt = ( + f"{label} '{invoice_number}'" + + (f" ({project_name})" if project_name else "") + + "?" + ) + + resp = QMessageBox.question( + self, + label, + prompt, + QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No, + QMessageBox.StandardButton.No, + ) + if resp != QMessageBox.StandardButton.Yes: + return + + # Remove any automatically created due-date reminder. + if self.cfg.reminders: + self._remove_invoice_due_reminder(row, int(inv_id)) + + try: + self._db.delete_invoice(int(inv_id)) + except Exception as e: + QMessageBox.warning( + self, + strings._("error"), + f"Failed to delete invoice: {e}", + ) + return + + self._reload_invoices() + # ------------------------------------------------------------------ helpers def _reload_projects(self) -> None: diff --git a/bouquin/locales/en.json b/bouquin/locales/en.json index 26a4d5c..f1f86dd 100644 --- a/bouquin/locales/en.json +++ b/bouquin/locales/en.json @@ -436,5 +436,6 @@ "invoice_invalid_date_format": "Invalid date format", "invoice_invalid_tax_rate": "The tax rate is invalid", "invoice_no_items": "There are no items in the invoice", - "invoice_number_required": "An invoice number is required" + "invoice_number_required": "An invoice number is required", + "invoice_required": "Please select a specific invoice before trying to delete an invoice." } diff --git a/bouquin/locales/fr.json b/bouquin/locales/fr.json index d82890d..87a6a16 100644 --- a/bouquin/locales/fr.json +++ b/bouquin/locales/fr.json @@ -432,5 +432,6 @@ "invoice_invalid_date_format": "Format de date invalide", "invoice_invalid_tax_rate": "Le taux de TVA est invalide", "invoice_no_items": "La facture ne contient aucun article", - "invoice_number_required": "Un numéro de facture est requis" + "invoice_number_required": "Un numéro de facture est requis", + "invoice_required": "Veuillez sélectionner une facture spécifique avant d'essayer de supprimer la facture." } diff --git a/debian/changelog b/debian/changelog index 036fb4e..26075f4 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +bouquin (0.8.2) unstable; urgency=medium + + * Add ability to delete an invoice via 'Manage Invoices' dialog + + -- Miguel Jacq Wed, 31 Dec 2025 16:00:00 +1100 + bouquin (0.8.1) unstable; urgency=medium * Fix bold/italic/strikethrough styling in certain conditions when toolbar action is used. diff --git a/rpm/bouquin.spec b/rpm/bouquin.spec index f7d3c37..2f8d442 100644 --- a/rpm/bouquin.spec +++ b/rpm/bouquin.spec @@ -4,7 +4,7 @@ # provides the Python distribution/module as "sqlcipher4". To keep Fedora's # auto-generated python3dist() Requires correct, we rewrite the dependency key in # pyproject.toml at build time. -%global upstream_version 0.8.1 +%global upstream_version 0.8.2 Name: bouquin Version: %{upstream_version} @@ -82,6 +82,8 @@ install -Dpm 0644 bouquin/icons/bouquin.svg %{buildroot}%{_datadir}/icons/hicolo %{_datadir}/icons/hicolor/scalable/apps/bouquin.svg %changelog +* Wed Dec 31 2025 Miguel Jacq - %{version}-%{release} +- Add ability to delete an invoice via 'Manage Invoices' dialog * Fri Dec 26 2025 Miguel Jacq - %{version}-%{release} - Fix bold/italic/strikethrough styling in certain conditions when toolbar action is used. - Move a code block or collapsed section (or generally, anything after a checkbox line until the next checkbox line) when moving an unchecked checkbox line to another day.