feat: add Transaction Entity

This commit is contained in:
Herculino Trotta
2024-11-30 17:12:35 -03:00
parent 28c8a961bc
commit 2382abf3c0
21 changed files with 517 additions and 11 deletions
+2
View File
@@ -6,6 +6,7 @@ from apps.transactions.models import (
TransactionTag,
InstallmentPlan,
RecurringTransaction,
TransactionEntity,
)
@@ -43,3 +44,4 @@ class RecurringTransactionAdmin(admin.ModelAdmin):
admin.site.register(TransactionCategory)
admin.site.register(TransactionTag)
admin.site.register(TransactionEntity)
+14 -3
View File
@@ -7,9 +7,12 @@ from django.utils.translation import gettext_lazy as _
from django_filters import Filter
from apps.accounts.models import Account
from apps.transactions.models import Transaction
from apps.transactions.models import TransactionCategory
from apps.transactions.models import TransactionTag
from apps.transactions.models import (
Transaction,
TransactionCategory,
TransactionTag,
TransactionEntity,
)
from apps.common.widgets.tom_select import TomSelectMultiple
from apps.common.fields.month_year import MonthYearFormField
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
@@ -62,6 +65,13 @@ class TransactionsFilter(django_filters.FilterSet):
label=_("Tags"),
widget=TomSelectMultiple(checkboxes=True, remove_button=True),
)
entities = django_filters.ModelMultipleChoiceFilter(
field_name="entities__name",
queryset=TransactionEntity.objects.all(),
to_field_name="name",
label=_("Entities"),
widget=TomSelectMultiple(checkboxes=True, remove_button=True),
)
is_paid = django_filters.MultipleChoiceFilter(
choices=SITUACAO_CHOICES,
field_name="is_paid",
@@ -159,6 +169,7 @@ class TransactionsFilter(django_filters.FilterSet):
Field("account", size=1),
Field("category", size=1),
Field("tags", size=1),
Field("entities", size=1),
)
self.form.fields["to_amount"].widget = ArbitraryDecimalDisplayNumberInput()
+71 -3
View File
@@ -25,6 +25,7 @@ from apps.transactions.models import (
TransactionTag,
InstallmentPlan,
RecurringTransaction,
TransactionEntity,
)
from apps.rules.signals import transaction_created, transaction_updated
@@ -42,6 +43,13 @@ class TransactionForm(forms.ModelForm):
required=False,
label=_("Tags"),
)
entities = DynamicModelMultipleChoiceField(
model=TransactionEntity,
to_field_name="name",
create_field="name",
required=False,
label=_("Entities"),
)
account = forms.ModelChoiceField(
queryset=Account.objects.filter(is_archived=False),
label=_("Account"),
@@ -62,6 +70,7 @@ class TransactionForm(forms.ModelForm):
"notes",
"category",
"tags",
"entities",
]
widgets = {
"date": forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d"),
@@ -81,7 +90,11 @@ class TransactionForm(forms.ModelForm):
template="transactions/widgets/income_expense_toggle_buttons.html",
),
Switch("is_paid"),
"account",
Row(
Column("account", css_class="form-group col-md-6 mb-0"),
Column("entities", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
),
Row(
Column("date", css_class="form-group col-md-6 mb-0"),
Column("reference_date", css_class="form-group col-md-6 mb-0"),
@@ -351,6 +364,13 @@ class InstallmentPlanForm(forms.ModelForm):
required=False,
label=_("Category"),
)
entities = DynamicModelMultipleChoiceField(
model=TransactionEntity,
to_field_name="name",
create_field="name",
required=False,
label=_("Entities"),
)
type = forms.ChoiceField(choices=Transaction.Type.choices)
reference_date = MonthYearFormField(label=_("Reference Date"), required=False)
@@ -369,6 +389,7 @@ class InstallmentPlanForm(forms.ModelForm):
"tags",
"notes",
"installment_start",
"entities",
]
widgets = {
"start_date": forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d"),
@@ -389,7 +410,11 @@ class InstallmentPlanForm(forms.ModelForm):
"type",
template="transactions/widgets/income_expense_toggle_buttons.html",
),
"account",
Row(
Column("account", css_class="form-group col-md-6 mb-0"),
Column("entities", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
),
"description",
"notes",
Row(
@@ -474,6 +499,38 @@ class TransactionTagForm(forms.ModelForm):
)
class TransactionEntityForm(forms.ModelForm):
class Meta:
model = TransactionEntity
fields = ["name"]
labels = {"name": _("Entity name")}
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.helper = FormHelper()
self.helper.form_tag = False
self.helper.form_method = "post"
self.helper.layout = Layout(Field("name", css_class="mb-3"))
if self.instance and self.instance.pk:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
),
)
else:
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
),
)
class TransactionCategoryForm(forms.ModelForm):
class Meta:
model = TransactionCategory
@@ -527,6 +584,13 @@ class RecurringTransactionForm(forms.ModelForm):
required=False,
label=_("Category"),
)
entities = DynamicModelMultipleChoiceField(
model=TransactionEntity,
to_field_name="name",
create_field="name",
required=False,
label=_("Entities"),
)
type = forms.ChoiceField(choices=Transaction.Type.choices)
reference_date = MonthYearFormField(label=_("Reference Date"), required=False)
@@ -568,7 +632,11 @@ class RecurringTransactionForm(forms.ModelForm):
"type",
template="transactions/widgets/income_expense_toggle_buttons.html",
),
"account",
Row(
Column("account", css_class="form-group col-md-6 mb-0"),
Column("entities", css_class="form-group col-md-6 mb-0"),
css_class="form-row",
),
"description",
"amount",
Row(
@@ -0,0 +1,30 @@
# Generated by Django 5.1.3 on 2024-11-30 18:26
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('transactions', '0022_rename_paused_recurringtransaction_is_paused'),
]
operations = [
migrations.CreateModel(
name='TransactionEntity',
fields=[
('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('name', models.CharField(max_length=255, verbose_name='Name')),
],
options={
'verbose_name': 'Entity',
'verbose_name_plural': 'Entities',
'db_table': 'entities',
},
),
migrations.AddField(
model_name='transaction',
name='entities',
field=models.ManyToManyField(blank=True, help_text='Payees/Payers involved in this transaction', related_name='transactions', to='transactions.transactionentity', verbose_name='Entities'),
),
]
@@ -0,0 +1,28 @@
# Generated by Django 5.1.3 on 2024-11-30 20:10
from django.db import migrations, models
class Migration(migrations.Migration):
dependencies = [
('transactions', '0023_transactionentity_transaction_entities'),
]
operations = [
migrations.AddField(
model_name='installmentplan',
name='entities',
field=models.ManyToManyField(blank=True, to='transactions.transactionentity', verbose_name='Entities'),
),
migrations.AddField(
model_name='recurringtransaction',
name='entities',
field=models.ManyToManyField(blank=True, to='transactions.transactionentity', verbose_name='Entities'),
),
migrations.AlterField(
model_name='transaction',
name='entities',
field=models.ManyToManyField(blank=True, related_name='transactions', to='transactions.transactionentity', verbose_name='Entities'),
),
]
+41 -1
View File
@@ -40,6 +40,20 @@ class TransactionTag(models.Model):
return self.name
class TransactionEntity(models.Model):
name = models.CharField(max_length=255, verbose_name=_("Name"))
# Add any other fields you might want for entities
class Meta:
verbose_name = _("Entity")
verbose_name_plural = _("Entities")
db_table = "entities"
def __str__(self):
return self.name
class Transaction(models.Model):
class Type(models.TextChoices):
INCOME = "IN", _("Income")
@@ -77,7 +91,17 @@ class Transaction(models.Model):
blank=True,
null=True,
)
tags = models.ManyToManyField(TransactionTag, verbose_name=_("Tags"), blank=True)
tags = models.ManyToManyField(
TransactionTag,
verbose_name=_("Tags"),
blank=True,
)
entities = models.ManyToManyField(
TransactionEntity,
verbose_name=_("Entities"),
blank=True,
related_name="transactions",
)
installment_plan = models.ForeignKey(
"InstallmentPlan",
@@ -185,6 +209,12 @@ class InstallmentPlan(models.Model):
verbose_name=_("Category"),
)
tags = models.ManyToManyField(TransactionTag, verbose_name=_("Tags"), blank=True)
entities = models.ManyToManyField(
TransactionEntity,
verbose_name=_("Entities"),
blank=True,
)
notes = models.TextField(blank=True, verbose_name=_("Notes"))
class Meta:
@@ -255,6 +285,7 @@ class InstallmentPlan(models.Model):
notes=self.notes,
)
new_transaction.tags.set(self.tags.all())
new_transaction.entities.set(self.entities.all())
@transaction.atomic
def update_transactions(self):
@@ -292,6 +323,7 @@ class InstallmentPlan(models.Model):
# Update tags
existing_transaction.tags.set(self.tags.all())
existing_transaction.entities.set(self.entities.all())
else:
# If the transaction doesn't exist, create a new one
new_transaction = Transaction.objects.create(
@@ -308,6 +340,7 @@ class InstallmentPlan(models.Model):
notes=self.notes,
)
new_transaction.tags.set(self.tags.all())
new_transaction.entities.set(self.entities.all())
# Remove any extra transactions that are no longer part of the plan
self.transactions.filter(
@@ -353,6 +386,11 @@ class RecurringTransaction(models.Model):
null=True,
)
tags = models.ManyToManyField(TransactionTag, verbose_name=_("Tags"), blank=True)
entities = models.ManyToManyField(
TransactionEntity,
verbose_name=_("Entities"),
blank=True,
)
notes = models.TextField(blank=True, verbose_name=_("Notes"))
reference_date = models.DateField(
verbose_name=_("Reference Date"), null=True, blank=True
@@ -426,6 +464,8 @@ class RecurringTransaction(models.Model):
)
if self.tags.exists():
created_transaction.tags.set(self.tags.all())
if self.entities.exists():
created_transaction.entities.set(self.entities.all())
def get_recurrence_delta(self):
if self.recurrence_type == self.RecurrenceType.DAY:
+13
View File
@@ -59,6 +59,19 @@ urlpatterns = [
views.tag_delete,
name="tag_delete",
),
path("entities/", views.entities_index, name="entities_index"),
path("entities/list/", views.entities_list, name="entities_list"),
path("entities/add/", views.entity_add, name="entity_add"),
path(
"entities/<int:entity_id>/edit/",
views.entity_edit,
name="entity_edit",
),
path(
"entities/<int:entity_id>/delete/",
views.entity_delete,
name="entity_delete",
),
path("categories/", views.categories_index, name="categories_index"),
path("categories/list/", views.categories_list, name="categories_list"),
path("categories/add/", views.category_add, name="category_add"),
+1
View File
@@ -1,5 +1,6 @@
from .transactions import *
from .tags import *
from .entities import *
from .categories import *
from .actions import *
from .installment_plans import *
+105
View File
@@ -0,0 +1,105 @@
from django.contrib import messages
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse
from django.shortcuts import render, get_object_or_404
from django.utils.translation import gettext_lazy as _
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from apps.common.decorators.htmx import only_htmx
from apps.transactions.forms import TransactionEntityForm
from apps.transactions.models import TransactionEntity
@login_required
@require_http_methods(["GET"])
def entities_index(request):
return render(
request,
"entities/pages/index.html",
)
@only_htmx
@login_required
@require_http_methods(["GET"])
def entities_list(request):
entities = TransactionEntity.objects.all().order_by("id")
return render(
request,
"entities/fragments/list.html",
{"entities": entities},
)
@only_htmx
@login_required
@require_http_methods(["GET", "POST"])
def entity_add(request, **kwargs):
if request.method == "POST":
form = TransactionEntityForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, _("Entity added successfully"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
else:
form = TransactionEntityForm()
return render(
request,
"entities/fragments/add.html",
{"form": form},
)
@only_htmx
@login_required
@require_http_methods(["GET", "POST"])
def entity_edit(request, entity_id):
entity = get_object_or_404(TransactionEntity, id=entity_id)
if request.method == "POST":
form = TransactionEntityForm(request.POST, instance=entity)
if form.is_valid():
form.save()
messages.success(request, _("Entity updated successfully"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)
else:
form = TransactionEntityForm(instance=entity)
return render(
request,
"entities/fragments/edit.html",
{"form": form, "entity": entity},
)
@only_htmx
@login_required
@csrf_exempt
@require_http_methods(["DELETE"])
def entity_delete(request, entity_id):
entity = get_object_or_404(TransactionEntity, id=entity_id)
entity.delete()
messages.success(request, _("Entity deleted successfully"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas",
},
)