From 68f9df29a803174ca0f33722966a0eb04aa1e7a6 Mon Sep 17 00:00:00 2001 From: julien gourmet Date: Sat, 6 Jun 2026 18:30:56 +0200 Subject: [PATCH 1/4] feat(pockets): remove balance display from pocket view --- app/views/pockets/_pocket.html.erb | 3 --- 1 file changed, 3 deletions(-) diff --git a/app/views/pockets/_pocket.html.erb b/app/views/pockets/_pocket.html.erb index d73b54a8f..447a5ae71 100644 --- a/app/views/pockets/_pocket.html.erb +++ b/app/views/pockets/_pocket.html.erb @@ -62,9 +62,6 @@

<%= format_money pocket.allocated_amount_money %>

-

- <%= t("pockets.pocket.of_balance", balance: format_money(account.balance_money)) %> -

<%# ── Footer ── %> From 686e48b1c9d5492e2a436561da96db2f36e5dcaa Mon Sep 17 00:00:00 2001 From: julien gourmet Date: Sat, 6 Jun 2026 18:34:39 +0200 Subject: [PATCH 2/4] fix(entries): add after_destroy callback to recompute pockets for transactions --- app/models/entry.rb | 14 ++++++++++++++ app/models/tagging.rb | 9 ++++----- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/app/models/entry.rb b/app/models/entry.rb index 1599f4ad4..212d99b23 100644 --- a/app/models/entry.rb +++ b/app/models/entry.rb @@ -15,6 +15,11 @@ class Entry < ApplicationRecord has_many :child_entries, class_name: "Entry", foreign_key: :parent_entry_id, dependent: :destroy + # Must be registered before delegated_type so it fires before Transaction is destroyed. + # In Rails 7.2, belongs_to dependent: :destroy runs as after_destroy (entry row already gone). + # Pockets are recomputed while entry is deleted but taggings still exist in DB. + after_destroy :recompute_pockets_for_transaction + delegated_type :entryable, types: Entryable::TYPES, dependent: :destroy accepts_nested_attributes_for :entryable @@ -529,4 +534,13 @@ def prevent_individual_child_deletion throw :abort end + + def recompute_pockets_for_transaction + return unless transaction? + + entryable.taggings.each do |tagging| + pocket = account.pockets.find_by(tag_id: tagging.tag_id) + pocket&.recompute_from_tag! + end + end end diff --git a/app/models/tagging.rb b/app/models/tagging.rb index 4c32ba137..ae0d827b2 100644 --- a/app/models/tagging.rb +++ b/app/models/tagging.rb @@ -39,11 +39,10 @@ def sibling_tagging_exists? def linked_pocket return unless taggable_type == "Transaction" - # taggable.entry traverses the has_one :entry on Transaction. - # For AR-mediated destroys this is always populated (belongs_to dependent: :destroy - # fires as before_destroy, so the Entry row is still present when this runs). - # For raw SQL deletes (delete_all) the entry may be gone; the nil guard below - # ensures we fail silently rather than raising. + # taggable.entry may be nil if the Entry row was already deleted + # (e.g. during Entry#destroy — delegated_type dependent: :destroy fires as after_destroy + # in Rails 7.2, so Entry is gone before Transaction/Taggings cascade). + # In that case Entry#recompute_pockets_for_transaction handles pocket updates. account = taggable.entry&.account return unless account From 3c5876a239da5acaa9ebfee5a11a721c96758234 Mon Sep 17 00:00:00 2001 From: julien gourmet Date: Sat, 6 Jun 2026 18:37:57 +0200 Subject: [PATCH 3/4] feat(i18n): add French translations for pockets and update English labels --- config/locales/models/pocket/fr.yml | 19 +++++++++++ config/locales/views/pockets/en.yml | 1 - config/locales/views/pockets/fr.yml | 49 +++++++++++++++++++++++++++++ 3 files changed, 68 insertions(+), 1 deletion(-) create mode 100644 config/locales/models/pocket/fr.yml create mode 100644 config/locales/views/pockets/fr.yml diff --git a/config/locales/models/pocket/fr.yml b/config/locales/models/pocket/fr.yml new file mode 100644 index 000000000..e3f4e727b --- /dev/null +++ b/config/locales/models/pocket/fr.yml @@ -0,0 +1,19 @@ +--- +fr: + activerecord: + models: + pocket: Enveloppe + attributes: + pocket: + name: Nom + allocated_amount: Montant alloué + currency: Devise + tag: Étiquette de remplissage auto + errors: + models: + pocket: + attributes: + allocated_amount: + exceeds_account_balance: "dépasse le solde disponible (max : %{available} %{currency})" + tag: + wrong_family: "n'appartient pas à cette famille" diff --git a/config/locales/views/pockets/en.yml b/config/locales/views/pockets/en.yml index 18b0d05b4..ec26035c1 100644 --- a/config/locales/views/pockets/en.yml +++ b/config/locales/views/pockets/en.yml @@ -30,7 +30,6 @@ en: manual: "Manual" auto_fills_from: "Auto-fills from \"%{tag}\"" no_auto_fill: "No auto-fill linked" - of_balance: "of %{balance}" view_transactions: "View tagged transactions" fill_direction: inflows: "Deposits only" diff --git a/config/locales/views/pockets/fr.yml b/config/locales/views/pockets/fr.yml new file mode 100644 index 000000000..688f0b341 --- /dev/null +++ b/config/locales/views/pockets/fr.yml @@ -0,0 +1,49 @@ +--- +fr: + pockets: + index: + new: "Nouvelle enveloppe" + total_allocated: "Alloué" + free_balance: "Solde libre" + empty: "Aucune enveloppe. Créez-en une pour commencer à allouer des fonds." + overflow_warning: "Les enveloppes allouées dépassent le solde actuel du compte." + new: + title: "Nouvelle enveloppe" + edit: + title: "Modifier l'enveloppe" + form: + name: "Nom" + name_placeholder: "ex. Épargne de précaution" + allocated_amount: "Montant alloué" + auto_fill_tag: "Remplissage automatique par étiquette" + no_tag: "Aucune (manuel uniquement)" + fill_direction_label: "Compter les transactions" + fill_direction: + inflows: "Dépôts uniquement (argent entrant)" + outflows: "Dépenses uniquement (argent sortant)" + both: "Toutes les transactions étiquetées" + auto_fill_hint: "Les transactions avec cette étiquette mettront automatiquement à jour cette enveloppe." + free_balance_hint: "Disponible à allouer : %{amount}" + save: "Enregistrer" + pocket: + overflow_badge: "DÉPASSEMENT" + manual: "Manuel" + auto_fills_from: "Remplissage auto depuis \"%{tag}\"" + no_auto_fill: "Aucun remplissage automatique" + of_balance: "sur %{balance}" + view_transactions: "Voir les transactions étiquetées" + fill_direction: + inflows: "Dépôts uniquement" + outflows: "Dépenses uniquement" + both: "Toutes les transactions" + create: + success: "Enveloppe créé." + update: + success: "Enveloppe mis à jour." + destroy: + confirm: "Supprimer l'enveloppe \"%{name}\" ?" + success: "Enveloppe supprimé." + accounts: + show: + tabs: + pockets: "Enveloppes" From c783b2b6c6e1e9d1ca68a9701fafc1a91957c435 Mon Sep 17 00:00:00 2001 From: julien gourmet Date: Sat, 6 Jun 2026 19:14:59 +0200 Subject: [PATCH 4/4] fix(entries): add nil check for entryable in recompute_pockets_for_transaction method --- app/models/entry.rb | 4 ++++ test/models/sure_import_test.rb | 3 +++ 2 files changed, 7 insertions(+) diff --git a/app/models/entry.rb b/app/models/entry.rb index 212d99b23..1b58aed97 100644 --- a/app/models/entry.rb +++ b/app/models/entry.rb @@ -538,6 +538,10 @@ def prevent_individual_child_deletion def recompute_pockets_for_transaction return unless transaction? + if entryable.nil? + return + end + entryable.taggings.each do |tagging| pocket = account.pockets.find_by(tag_id: tagging.tag_id) pocket&.recompute_from_tag! diff --git a/test/models/sure_import_test.rb b/test/models/sure_import_test.rb index d0a076bf8..380fedad0 100644 --- a/test/models/sure_import_test.rb +++ b/test/models/sure_import_test.rb @@ -460,6 +460,9 @@ class SureImportTest < ActiveSupport::TestCase assert_difference -> { Entry.where(id: split_entry_ids).count }, -3 do @import.revert + puts "DEBUG status=#{@import.reload.status} error=#{@import.reload.respond_to?(:error) ? @import.reload.error : 'n/a'}" + puts "DEBUG entries remaining=#{Entry.where(id: split_entry_ids).count}" + puts "DEBUG import entries count=#{@import.entries.where(parent_entry_id: nil).count}" end assert_equal "pending", @import.reload.status end