diff --git a/app/apps/insights/utils/transactions.py b/app/apps/insights/utils/transactions.py index 5c3b3a1..5b919d9 100644 --- a/app/apps/insights/utils/transactions.py +++ b/app/apps/insights/utils/transactions.py @@ -91,6 +91,8 @@ def get_transactions(request, include_unpaid=True, include_silent=False): transactions = transactions.filter(is_paid=True) if not include_silent: - transactions = transactions.exclude(Q(category__mute=True) & ~Q(category=None)) + transactions = transactions.exclude( + Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True) + ) return transactions diff --git a/app/apps/insights/views.py b/app/apps/insights/views.py index a5698a5..5ebbcfe 100644 --- a/app/apps/insights/views.py +++ b/app/apps/insights/views.py @@ -260,6 +260,7 @@ def emergency_fund(request): reference_date__gte=start_date, reference_date__lte=end_date, category__mute=False, + mute=False, ) .values("reference_date", "account__currency") .annotate(monthly_total=Sum("amount")) diff --git a/app/apps/monthly_overview/views.py b/app/apps/monthly_overview/views.py index d363067..8624201 100644 --- a/app/apps/monthly_overview/views.py +++ b/app/apps/monthly_overview/views.py @@ -109,7 +109,7 @@ def monthly_summary(request, month: int, year: int): # Base queryset with all required filters base_queryset = Transaction.objects.filter( reference_date__year=year, reference_date__month=month, account__is_asset=False - ).exclude(Q(category__mute=True) & ~Q(category=None)) + ).exclude(Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True)) data = calculate_currency_totals(base_queryset, ignore_empty=True) percentages = calculate_percentage_distribution(data) @@ -143,7 +143,7 @@ def monthly_account_summary(request, month: int, year: int): base_queryset = Transaction.objects.filter( reference_date__year=year, reference_date__month=month, - ).exclude(Q(category__mute=True) & ~Q(category=None)) + ).exclude(Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True)) account_data = calculate_account_totals(transactions_queryset=base_queryset.all()) account_percentages = calculate_percentage_distribution(account_data) @@ -168,7 +168,7 @@ def monthly_currency_summary(request, month: int, year: int): base_queryset = Transaction.objects.filter( reference_date__year=year, reference_date__month=month, - ).exclude(Q(category__mute=True) & ~Q(category=None)) + ).exclude(Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True)) currency_data = calculate_currency_totals(base_queryset.all(), ignore_empty=True) currency_percentages = calculate_percentage_distribution(currency_data) diff --git a/app/apps/transactions/forms.py b/app/apps/transactions/forms.py index 5299901..215832c 100644 --- a/app/apps/transactions/forms.py +++ b/app/apps/transactions/forms.py @@ -290,11 +290,15 @@ class QuickTransactionForm(forms.ModelForm): "category", "tags", "entities", + "mute", ] widgets = { "notes": forms.Textarea(attrs={"rows": 3}), "account": TomSelect(clear_button=False, group_by="group"), } + help_texts = { + "mute": _("Muted transactions won't be displayed on monthly summaries") + } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) @@ -356,6 +360,7 @@ class QuickTransactionForm(forms.ModelForm): css_class="form-row", ), "notes", + Switch("mute"), ) if self.instance and self.instance.pk: @@ -616,6 +621,7 @@ class TransferForm(forms.Form): description=description, category=from_category, notes=notes, + mute=True, ) from_transaction.tags.set(self.cleaned_data.get("from_tags", [])) @@ -630,6 +636,7 @@ class TransferForm(forms.Form): description=description, category=to_category, notes=notes, + mute=True, ) to_transaction.tags.set(self.cleaned_data.get("to_tags", [])) @@ -868,7 +875,7 @@ class TransactionCategoryForm(forms.ModelForm): fields = ["name", "mute", "active"] labels = {"name": _("Category name")} help_texts = { - "mute": _("Muted categories won't count towards your monthly total") + "mute": _("Muted categories won't be displayed on monthly summaries") } def __init__(self, *args, **kwargs): diff --git a/app/apps/transactions/migrations/0045_transaction_mute.py b/app/apps/transactions/migrations/0045_transaction_mute.py new file mode 100644 index 0000000..8e3ab61 --- /dev/null +++ b/app/apps/transactions/migrations/0045_transaction_mute.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.11 on 2025-07-19 18:12 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('transactions', '0044_alter_quicktransaction_unique_together'), + ] + + operations = [ + migrations.AddField( + model_name='transaction', + name='mute', + field=models.BooleanField(default=False, verbose_name='Mute'), + ), + ] diff --git a/app/apps/transactions/migrations/0046_quicktransaction_mute.py b/app/apps/transactions/migrations/0046_quicktransaction_mute.py new file mode 100644 index 0000000..e1a0eac --- /dev/null +++ b/app/apps/transactions/migrations/0046_quicktransaction_mute.py @@ -0,0 +1,18 @@ +# Generated by Django 5.1.11 on 2025-07-19 18:59 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('transactions', '0045_transaction_mute'), + ] + + operations = [ + migrations.AddField( + model_name='quicktransaction', + name='mute', + field=models.BooleanField(default=False, verbose_name='Mute'), + ), + ] diff --git a/app/apps/transactions/models.py b/app/apps/transactions/models.py index 82cfe90..7b363c4 100644 --- a/app/apps/transactions/models.py +++ b/app/apps/transactions/models.py @@ -299,6 +299,7 @@ class Transaction(OwnedObject): is_paid = models.BooleanField(default=True, verbose_name=_("Paid")) date = models.DateField(verbose_name=_("Date")) reference_date = MonthYearModelField(verbose_name=_("Reference Date")) + mute = models.BooleanField(default=False, verbose_name=_("Mute")) amount = models.DecimalField( max_digits=42, @@ -918,6 +919,7 @@ class QuickTransaction(OwnedObject): verbose_name=_("Type"), ) is_paid = models.BooleanField(default=True, verbose_name=_("Paid")) + mute = models.BooleanField(default=False, verbose_name=_("Mute")) amount = models.DecimalField( max_digits=42, diff --git a/app/apps/transactions/urls.py b/app/apps/transactions/urls.py index b808872..df36286 100644 --- a/app/apps/transactions/urls.py +++ b/app/apps/transactions/urls.py @@ -66,6 +66,11 @@ urlpatterns = [ views.transaction_pay, name="transaction_pay", ), + path( + "transaction//mute/", + views.transaction_mute, + name="transaction_mute", + ), path( "transaction//delete/", views.transaction_delete, diff --git a/app/apps/transactions/views/transactions.py b/app/apps/transactions/views/transactions.py index 9a4466a..dbf1967 100644 --- a/app/apps/transactions/views/transactions.py +++ b/app/apps/transactions/views/transactions.py @@ -388,6 +388,26 @@ def transaction_pay(request, transaction_id): return response +@only_htmx +@login_required +@require_http_methods(["GET"]) +def transaction_mute(request, transaction_id): + transaction = get_object_or_404(Transaction, pk=transaction_id) + + new_mute = False if transaction.mute else True + transaction.mute = new_mute + transaction.save() + transaction_updated.send(sender=transaction) + + response = render( + request, + "transactions/fragments/item.html", + context={"transaction": transaction, **request.GET}, + ) + response.headers["HX-Trigger"] = "selective_update" + return response + + @login_required @require_http_methods(["GET"]) def transaction_all_index(request): diff --git a/app/apps/yearly_overview/views.py b/app/apps/yearly_overview/views.py index e8a97f0..eee1d0a 100644 --- a/app/apps/yearly_overview/views.py +++ b/app/apps/yearly_overview/views.py @@ -75,7 +75,7 @@ def yearly_overview_by_currency(request, year: int): transactions = ( Transaction.objects.filter(**filter_params) - .exclude(Q(category__mute=True) & ~Q(category=None)) + .exclude(Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True)) .order_by("account__currency__name") ) @@ -141,7 +141,7 @@ def yearly_overview_by_account(request, year: int): transactions = ( Transaction.objects.filter(**filter_params) - .exclude(Q(category__mute=True) & ~Q(category=None)) + .exclude(Q(Q(category__mute=True) & ~Q(category=None)) | Q(mute=True)) .order_by( "account__group__name", "account__name", diff --git a/app/templates/cotton/transaction/item.html b/app/templates/cotton/transaction/item.html index 5098d06..288b49f 100644 --- a/app/templates/cotton/transaction/item.html +++ b/app/templates/cotton/transaction/item.html @@ -146,6 +146,21 @@ {% else %}