feat: finish adding DCA Tracker tool

This commit is contained in:
Herculino Trotta
2024-11-12 11:40:50 -03:00
parent 7ebe3b6b5b
commit ba52e8740f
14 changed files with 887 additions and 369 deletions

View File

@@ -1,16 +1,24 @@
from crispy_forms.bootstrap import FormActions
from django import forms from django import forms
from crispy_forms.helper import FormHelper from crispy_forms.helper import FormHelper
from crispy_forms.layout import Layout, Row, Column from crispy_forms.layout import Layout, Row, Column
from django.utils.translation import gettext_lazy as _ from django.utils.translation import gettext_lazy as _
from .models import DCAStrategy, DCAEntry from apps.common.widgets.tom_select import TomSelect
from apps.dca.models import DCAStrategy, DCAEntry
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
from apps.common.widgets.crispy.submit import NoClassSubmit
class DCAStrategyForm(forms.ModelForm): class DCAStrategyForm(forms.ModelForm):
class Meta: class Meta:
model = DCAStrategy model = DCAStrategy
fields = ["name", "target_currency", "payment_currency", "notes"] fields = ["name", "target_currency", "payment_currency", "notes"]
widgets = {
"target_currency": TomSelect(clear_button=False),
"payment_currency": TomSelect(clear_button=False),
"notes": forms.Textarea(attrs={"rows": 3}),
}
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs) super().__init__(*args, **kwargs)
@@ -19,12 +27,29 @@ class DCAStrategyForm(forms.ModelForm):
self.helper.layout = Layout( self.helper.layout = Layout(
"name", "name",
Row( Row(
Column("target_currency", css_class="form-group col-md-6"),
Column("payment_currency", css_class="form-group col-md-6"), Column("payment_currency", css_class="form-group col-md-6"),
Column("target_currency", css_class="form-group col-md-6"),
), ),
"notes", "notes",
) )
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 DCAEntryForm(forms.ModelForm): class DCAEntryForm(forms.ModelForm):
class Meta: class Meta:
@@ -33,13 +58,11 @@ class DCAEntryForm(forms.ModelForm):
"date", "date",
"amount_paid", "amount_paid",
"amount_received", "amount_received",
"expense_transaction",
"income_transaction",
"notes", "notes",
] ]
widgets = { widgets = {
"amount_paid": ArbitraryDecimalDisplayNumberInput(decimal_places=8), "date": forms.DateInput(attrs={"type": "date"}, format="%Y-%m-%d"),
"amount_received": ArbitraryDecimalDisplayNumberInput(decimal_places=8), "notes": forms.Textarea(attrs={"rows": 3}),
} }
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@@ -58,3 +81,28 @@ class DCAEntryForm(forms.ModelForm):
), ),
"notes", "notes",
) )
if self.instance and self.instance.pk:
# decimal_places = self.instance.account.currency.decimal_places
# self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput(
# decimal_places=decimal_places
# )
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Update"), css_class="btn btn-outline-primary w-100"
),
),
)
else:
# self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
self.helper.layout.append(
FormActions(
NoClassSubmit(
"submit", _("Add"), css_class="btn btn-outline-primary w-100"
),
),
)
self.fields["amount_paid"].widget = ArbitraryDecimalDisplayNumberInput()
self.fields["amount_received"].widget = ArbitraryDecimalDisplayNumberInput()

View File

@@ -3,7 +3,34 @@ from . import views
urlpatterns = [ urlpatterns = [
path("dca/", views.strategy_list, name="strategy_list"), path("dca/", views.strategy_index, name="dca_strategy_index"),
path("dca/<int:pk>/", views.strategy_detail, name="strategy_detail"), path("dca/list/", views.strategy_list, name="dca_strategy_list"),
# Add more URLs for CRUD operations path("dca/add/", views.strategy_add, name="dca_strategy_add"),
path("dca/<int:strategy_id>/edit/", views.strategy_edit, name="dca_strategy_edit"),
path(
"dca/<int:strategy_id>/delete/",
views.strategy_delete,
name="dca_strategy_delete",
),
path(
"dca/<int:strategy_id>/",
views.strategy_detail_index,
name="dca_strategy_detail_index",
),
path(
"dca/<int:strategy_id>/details/",
views.strategy_detail,
name="dca_strategy_detail",
),
path("dca/<int:strategy_id>/add/", views.strategy_entry_add, name="dca_entry_add"),
path(
"dca/<int:strategy_id>/<int:entry_id>/edit/",
views.strategy_entry_edit,
name="dca_entry_edit",
),
path(
"dca/<int:strategy_id>/<int:entry_id>/delete/",
views.strategy_entry_delete,
name="dca_entry_delete",
),
] ]

View File

@@ -1,31 +1,119 @@
# apps/dca_tracker/views.py # apps/dca_tracker/views.py
from django.contrib.auth.decorators import login_required
from django.shortcuts import render, get_object_or_404, redirect
from django.contrib import messages from django.contrib import messages
from django.utils.translation import gettext_lazy as _ from django.contrib.auth.decorators import login_required
from django.db.models import Sum, Avg from django.db.models import Sum, Avg
from django.db.models.functions import TruncMonth from django.db.models.functions import TruncMonth
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 .models import DCAStrategy, DCAEntry from apps.common.decorators.htmx import only_htmx
from .forms import DCAStrategyForm, DCAEntryForm from apps.dca.models import DCAStrategy, DCAEntry
from apps.dca.forms import DCAEntryForm, DCAStrategyForm
@login_required
def strategy_index(request):
return render(request, "dca/pages/strategy_index.html")
@only_htmx
@login_required @login_required
def strategy_list(request): def strategy_list(request):
strategies = DCAStrategy.objects.all() strategies = DCAStrategy.objects.all().order_by("created_at")
return render(request, "dca/strategy_list.html", {"strategies": strategies}) return render(
request, "dca/fragments/strategy/list.html", {"strategies": strategies}
)
from django.shortcuts import render, get_object_or_404, redirect @only_htmx
from django.contrib import messages @login_required
from django.utils.translation import gettext_lazy as _ def strategy_add(request):
from django.db.models import Sum, Avg if request.method == "POST":
from django.db.models.functions import TruncMonth form = DCAStrategyForm(request.POST)
if form.is_valid():
form.save()
messages.success(request, _("DCA Strategy added successfully"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas, toasts",
},
)
else:
form = DCAStrategyForm()
return render(
request,
"dca/fragments/strategy/add.html",
{"form": form},
)
@only_htmx
@login_required
def strategy_edit(request, strategy_id):
dca_strategy = get_object_or_404(DCAStrategy, id=strategy_id)
if request.method == "POST":
form = DCAStrategyForm(request.POST, instance=dca_strategy)
if form.is_valid():
form.save()
messages.success(request, _("DCA Strategy updated successfully"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas, toasts",
},
)
else:
form = DCAStrategyForm(instance=dca_strategy)
return render(
request,
"dca/fragments/strategy/edit.html",
{"form": form, "dca_strategy": dca_strategy},
)
@only_htmx
@login_required
@csrf_exempt
@require_http_methods(["DELETE"])
def strategy_delete(request, strategy_id):
dca_strategy = get_object_or_404(DCAStrategy, id=strategy_id)
dca_strategy.delete()
messages.success(request, _("DCA strategy deleted successfully"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas, toasts",
},
)
@login_required @login_required
def strategy_detail(request, pk): def strategy_detail_index(request, strategy_id):
strategy = get_object_or_404(DCAStrategy, id=pk) strategy = get_object_or_404(DCAStrategy, id=strategy_id)
return render(
request,
"dca/pages/strategy_detail_index.html",
context={"strategy": strategy},
)
@only_htmx
@login_required
def strategy_detail(request, strategy_id):
strategy = get_object_or_404(DCAStrategy, id=strategy_id)
entries = strategy.entries.all() entries = strategy.entries.all()
# Calculate monthly aggregates # Calculate monthly aggregates
@@ -50,6 +138,7 @@ def strategy_detail(request, pk):
} }
for entry in entries for entry in entries
] ]
entries_data.reverse()
context = { context = {
"strategy": strategy, "strategy": strategy,
@@ -64,4 +153,78 @@ def strategy_detail(request, pk):
"total_profit_loss": strategy.total_profit_loss(), "total_profit_loss": strategy.total_profit_loss(),
"total_profit_loss_percentage": strategy.total_profit_loss_percentage(), "total_profit_loss_percentage": strategy.total_profit_loss_percentage(),
} }
return render(request, "dca/strategy_detail.html", context) return render(request, "dca/fragments/strategy/details.html", context)
@only_htmx
@login_required
def strategy_entry_add(request, strategy_id):
strategy = get_object_or_404(DCAStrategy, id=strategy_id)
if request.method == "POST":
form = DCAEntryForm(request.POST)
if form.is_valid():
entry = form.save(commit=False)
entry.strategy = strategy
entry.save()
messages.success(request, _("Entry added successfully"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas, toasts",
},
)
else:
form = DCAEntryForm()
return render(
request,
"dca/fragments/entry/add.html",
{"form": form, "strategy": strategy},
)
@only_htmx
@login_required
def strategy_entry_edit(request, strategy_id, entry_id):
dca_entry = get_object_or_404(DCAEntry, id=entry_id, strategy__id=strategy_id)
if request.method == "POST":
form = DCAEntryForm(request.POST, instance=dca_entry)
if form.is_valid():
form.save()
messages.success(request, _("Entry updated successfully"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas, toasts",
},
)
else:
form = DCAEntryForm(instance=dca_entry)
return render(
request,
"dca/fragments/strategy/edit.html",
{"form": form, "dca_entry": dca_entry},
)
@only_htmx
@login_required
@csrf_exempt
@require_http_methods(["DELETE"])
def strategy_entry_delete(request, entry_id, strategy_id):
dca_entry = get_object_or_404(DCAEntry, id=entry_id, strategy__id=strategy_id)
dca_entry.delete()
messages.success(request, _("Entry deleted successfully"))
return HttpResponse(
status=204,
headers={
"HX-Trigger": "updated, hide_offcanvas, toasts",
},
)

View File

@@ -8,8 +8,8 @@ msgid ""
msgstr "" msgstr ""
"Project-Id-Version: \n" "Project-Id-Version: \n"
"Report-Msgid-Bugs-To: \n" "Report-Msgid-Bugs-To: \n"
"POT-Creation-Date: 2024-11-05 03:01+0000\n" "POT-Creation-Date: 2024-11-12 14:32+0000\n"
"PO-Revision-Date: 2024-11-05 00:02-0300\n" "PO-Revision-Date: 2024-11-12 11:39-0300\n"
"Last-Translator: \n" "Last-Translator: \n"
"Language-Team: \n" "Language-Team: \n"
"Language: pt_BR\n" "Language: pt_BR\n"
@@ -24,23 +24,26 @@ msgid "Group name"
msgstr "Nome do grupo" msgstr "Nome do grupo"
#: apps/accounts/forms.py:40 apps/accounts/forms.py:95 #: apps/accounts/forms.py:40 apps/accounts/forms.py:95
#: apps/currencies/forms.py:35 apps/currencies/forms.py:74 #: apps/currencies/forms.py:51 apps/currencies/forms.py:90 apps/dca/forms.py:40
#: apps/rules/forms.py:45 apps/rules/forms.py:87 apps/transactions/forms.py:110 #: apps/dca/forms.py:93 apps/rules/forms.py:45 apps/rules/forms.py:87
#: apps/transactions/forms.py:420 apps/transactions/forms.py:463 #: apps/transactions/forms.py:110 apps/transactions/forms.py:420
#: apps/transactions/forms.py:498 apps/transactions/forms.py:599 #: apps/transactions/forms.py:463 apps/transactions/forms.py:498
#: apps/transactions/forms.py:599
msgid "Update" msgid "Update"
msgstr "Atualizar" msgstr "Atualizar"
#: apps/accounts/forms.py:48 apps/accounts/forms.py:103 #: apps/accounts/forms.py:48 apps/accounts/forms.py:103
#: apps/common/widgets/tom_select.py:12 apps/currencies/forms.py:43 #: apps/common/widgets/tom_select.py:12 apps/currencies/forms.py:59
#: apps/currencies/forms.py:82 apps/rules/forms.py:53 apps/rules/forms.py:95 #: apps/currencies/forms.py:98 apps/dca/forms.py:48 apps/dca/forms.py:102
#: apps/transactions/forms.py:119 apps/transactions/forms.py:428 #: apps/rules/forms.py:53 apps/rules/forms.py:95 apps/transactions/forms.py:119
#: apps/transactions/forms.py:471 apps/transactions/forms.py:506 #: apps/transactions/forms.py:428 apps/transactions/forms.py:471
#: apps/transactions/forms.py:607 #: apps/transactions/forms.py:506 apps/transactions/forms.py:607
#: templates/account_groups/fragments/list.html:9 #: templates/account_groups/fragments/list.html:9
#: templates/accounts/fragments/list.html:9 #: templates/accounts/fragments/list.html:9
#: templates/categories/fragments/list.html:9 #: templates/categories/fragments/list.html:9
#: templates/currencies/fragments/list.html:9 #: templates/currencies/fragments/list.html:9
#: templates/dca/fragments/strategy/details.html:101
#: templates/dca/fragments/strategy/list.html:9
#: templates/exchange_rates/fragments/list.html:10 #: templates/exchange_rates/fragments/list.html:10
#: templates/installment_plans/fragments/list.html:9 #: templates/installment_plans/fragments/list.html:9
#: templates/recurring_transactions/fragments/list.html:9 #: templates/recurring_transactions/fragments/list.html:9
@@ -69,13 +72,14 @@ msgstr "Categoria"
#: apps/transactions/forms.py:186 apps/transactions/forms.py:193 #: apps/transactions/forms.py:186 apps/transactions/forms.py:193
#: apps/transactions/forms.py:347 apps/transactions/forms.py:523 #: apps/transactions/forms.py:347 apps/transactions/forms.py:523
#: apps/transactions/models.py:80 apps/transactions/models.py:187 #: apps/transactions/models.py:80 apps/transactions/models.py:187
#: apps/transactions/models.py:355 templates/includes/navbar.html:71 #: apps/transactions/models.py:355 templates/includes/navbar.html:84
#: templates/tags/fragments/list.html:5 templates/tags/pages/index.html:4 #: templates/tags/fragments/list.html:5 templates/tags/pages/index.html:4
msgid "Tags" msgid "Tags"
msgstr "Tags" msgstr "Tags"
#: apps/accounts/models.py:8 apps/accounts/models.py:20 apps/rules/models.py:9 #: apps/accounts/models.py:8 apps/accounts/models.py:20 apps/dca/models.py:11
#: apps/transactions/models.py:19 apps/transactions/models.py:32 #: apps/rules/models.py:9 apps/transactions/models.py:19
#: apps/transactions/models.py:32
#: templates/account_groups/fragments/list.html:23 #: templates/account_groups/fragments/list.html:23
#: templates/accounts/fragments/list.html:23 #: templates/accounts/fragments/list.html:23
#: templates/categories/fragments/list.html:23 #: templates/categories/fragments/list.html:23
@@ -92,20 +96,21 @@ msgstr "Grupo da Conta"
#: apps/accounts/models.py:12 templates/account_groups/fragments/list.html:5 #: apps/accounts/models.py:12 templates/account_groups/fragments/list.html:5
#: templates/account_groups/pages/index.html:4 #: templates/account_groups/pages/index.html:4
#: templates/includes/navbar.html:79 #: templates/includes/navbar.html:92
msgid "Account Groups" msgid "Account Groups"
msgstr "Grupos da Conta" msgstr "Grupos da Conta"
#: apps/accounts/models.py:30 apps/currencies/models.py:21 #: apps/accounts/models.py:30 apps/currencies/models.py:31
#: templates/accounts/fragments/list.html:24 #: templates/accounts/fragments/list.html:24
msgid "Currency" msgid "Currency"
msgstr "Moeda" msgstr "Moeda"
#: apps/accounts/models.py:36 templates/accounts/fragments/list.html:25 #: apps/accounts/models.py:36 apps/currencies/models.py:19
#: templates/accounts/fragments/list.html:25
msgid "Exchange Currency" msgid "Exchange Currency"
msgstr "Moeda de Câmbio" msgstr "Moeda de Câmbio"
#: apps/accounts/models.py:41 #: apps/accounts/models.py:41 apps/currencies/models.py:24
msgid "Default currency for exchange calculations" msgid "Default currency for exchange calculations"
msgstr "Moeda padrão para os cálculos de câmbio" msgstr "Moeda padrão para os cálculos de câmbio"
@@ -138,8 +143,8 @@ msgstr "Conta"
#: apps/accounts/models.py:59 apps/transactions/filters.py:48 #: apps/accounts/models.py:59 apps/transactions/filters.py:48
#: templates/accounts/fragments/list.html:5 #: templates/accounts/fragments/list.html:5
#: templates/accounts/pages/index.html:4 templates/includes/navbar.html:75 #: templates/accounts/pages/index.html:4 templates/includes/navbar.html:88
#: templates/includes/navbar.html:77 #: templates/includes/navbar.html:90
msgid "Accounts" msgid "Accounts"
msgstr "Contas" msgstr "Contas"
@@ -282,11 +287,11 @@ msgstr "Limpar"
msgid "No results..." msgid "No results..."
msgstr "Sem resultados..." msgstr "Sem resultados..."
#: apps/currencies/forms.py:14 apps/currencies/models.py:14 #: apps/currencies/forms.py:15 apps/currencies/models.py:14
msgid "Prefix" msgid "Prefix"
msgstr "Prefixo" msgstr "Prefixo"
#: apps/currencies/forms.py:15 apps/currencies/models.py:15 #: apps/currencies/forms.py:16 apps/currencies/models.py:15
msgid "Suffix" msgid "Suffix"
msgstr "Sufixo" msgstr "Sufixo"
@@ -302,31 +307,31 @@ msgstr "Nome da Moeda"
msgid "Decimal Places" msgid "Decimal Places"
msgstr "Casas Decimais" msgstr "Casas Decimais"
#: apps/currencies/models.py:22 templates/currencies/fragments/list.html:5 #: apps/currencies/models.py:32 templates/currencies/fragments/list.html:5
#: templates/currencies/pages/index.html:4 templates/includes/navbar.html:83 #: templates/currencies/pages/index.html:4 templates/includes/navbar.html:96
#: templates/includes/navbar.html:85 #: templates/includes/navbar.html:98
msgid "Currencies" msgid "Currencies"
msgstr "Moedas" msgstr "Moedas"
#: apps/currencies/models.py:30 #: apps/currencies/models.py:40
msgid "From Currency" msgid "From Currency"
msgstr "Moeda de origem" msgstr "Moeda de origem"
#: apps/currencies/models.py:36 #: apps/currencies/models.py:46
msgid "To Currency" msgid "To Currency"
msgstr "Moeda de destino" msgstr "Moeda de destino"
#: apps/currencies/models.py:39 apps/currencies/models.py:44 #: apps/currencies/models.py:49 apps/currencies/models.py:54
msgid "Exchange Rate" msgid "Exchange Rate"
msgstr "Taxa de Câmbio" msgstr "Taxa de Câmbio"
#: apps/currencies/models.py:41 #: apps/currencies/models.py:51
msgid "Date and Time" msgid "Date and Time"
msgstr "Data e Tempo" msgstr "Data e Tempo"
#: apps/currencies/models.py:45 templates/exchange_rates/fragments/list.html:6 #: apps/currencies/models.py:55 templates/exchange_rates/fragments/list.html:6
#: templates/exchange_rates/pages/index.html:4 #: templates/exchange_rates/pages/index.html:4
#: templates/includes/navbar.html:87 #: templates/includes/navbar.html:100
msgid "Exchange Rates" msgid "Exchange Rates"
msgstr "Taxas de Câmbio" msgstr "Taxas de Câmbio"
@@ -354,6 +359,87 @@ msgstr "Taxa de câmbio atualizada com sucesso"
msgid "Exchange rate deleted successfully" msgid "Exchange rate deleted successfully"
msgstr "Taxa de câmbio apagada com sucesso" msgstr "Taxa de câmbio apagada com sucesso"
#: apps/dca/models.py:14
msgid "Target Currency"
msgstr "Moeda de destino"
#: apps/dca/models.py:20
msgid "Payment Currency"
msgstr "Moeda de pagamento"
#: apps/dca/models.py:24 apps/dca/models.py:98 apps/rules/models.py:26
#: apps/transactions/forms.py:209 apps/transactions/models.py:72
#: apps/transactions/models.py:188 apps/transactions/models.py:356
msgid "Notes"
msgstr "Notas"
#: apps/dca/models.py:29
msgid "DCA Strategy"
msgstr "Estratégia CMP"
#: apps/dca/models.py:30
msgid "DCA Strategies"
msgstr "Estratégias CMP"
#: apps/dca/models.py:73
msgid "Strategy"
msgstr "Estratégia"
#: apps/dca/models.py:75 apps/rules/models.py:22 apps/transactions/forms.py:197
#: apps/transactions/models.py:61
#: templates/dca/fragments/strategy/details.html:116
#: templates/exchange_rates/fragments/table.html:14
msgid "Date"
msgstr "Data"
#: apps/dca/models.py:77 templates/dca/fragments/strategy/details.html:117
msgid "Amount Paid"
msgstr "Quantia paga"
#: apps/dca/models.py:80 templates/dca/fragments/strategy/details.html:118
msgid "Amount Received"
msgstr "Quantia recebida"
#: apps/dca/models.py:88
msgid "Expense Transaction"
msgstr "Transação de saída"
#: apps/dca/models.py:96
msgid "Income Transaction"
msgstr "Transação de entrada"
#: apps/dca/models.py:103
msgid "DCA Entry"
msgstr "Entrada CMP"
#: apps/dca/models.py:104
msgid "DCA Entries"
msgstr "Entradas CMP"
#: apps/dca/views.py:38
msgid "DCA Strategy added successfully"
msgstr "Estratégia CMP adicionada com sucesso"
#: apps/dca/views.py:65
msgid "DCA Strategy updated successfully"
msgstr "Estratégia CMP atualizada com sucesso"
#: apps/dca/views.py:92
msgid "DCA strategy deleted successfully"
msgstr "Estratégia CMP apagada com sucesso"
#: apps/dca/views.py:169
msgid "Entry added successfully"
msgstr "Entrada adicionada com sucesso"
#: apps/dca/views.py:196
msgid "Entry updated successfully"
msgstr "Entrada atualizada com sucesso"
#: apps/dca/views.py:223
msgid "Entry deleted successfully"
msgstr "Entrada apagada com sucesso"
#: apps/rules/forms.py:20 #: apps/rules/forms.py:20
msgid "Run on creation" msgid "Run on creation"
msgstr "Rodar na criação" msgstr "Rodar na criação"
@@ -398,12 +484,6 @@ msgstr "Tipo"
msgid "Paid" msgid "Paid"
msgstr "Pago" msgstr "Pago"
#: apps/rules/models.py:22 apps/transactions/forms.py:197
#: apps/transactions/models.py:61
#: templates/exchange_rates/fragments/table.html:14
msgid "Date"
msgstr "Data"
#: apps/rules/models.py:23 apps/transactions/forms.py:50 #: apps/rules/models.py:23 apps/transactions/forms.py:50
#: apps/transactions/forms.py:200 apps/transactions/forms.py:355 #: apps/transactions/forms.py:200 apps/transactions/forms.py:355
#: apps/transactions/forms.py:531 apps/transactions/models.py:62 #: apps/transactions/forms.py:531 apps/transactions/models.py:62
@@ -416,12 +496,6 @@ msgstr "Data de Referência"
msgid "Amount" msgid "Amount"
msgstr "Quantia" msgstr "Quantia"
#: apps/rules/models.py:26 apps/transactions/forms.py:209
#: apps/transactions/models.py:72 apps/transactions/models.py:188
#: apps/transactions/models.py:356
msgid "Notes"
msgstr "Notas"
#: apps/rules/models.py:34 #: apps/rules/models.py:34
msgid "Rule" msgid "Rule"
msgstr "Regra" msgstr "Regra"
@@ -475,7 +549,7 @@ msgid "Transaction Type"
msgstr "Tipo de Transação" msgstr "Tipo de Transação"
#: apps/transactions/filters.py:55 templates/categories/fragments/list.html:5 #: apps/transactions/filters.py:55 templates/categories/fragments/list.html:5
#: templates/categories/pages/index.html:4 templates/includes/navbar.html:69 #: templates/categories/pages/index.html:4 templates/includes/navbar.html:82
msgid "Categories" msgid "Categories"
msgstr "Categorias" msgstr "Categorias"
@@ -557,7 +631,7 @@ msgstr "Tags da Transação"
#: apps/transactions/models.py:45 #: apps/transactions/models.py:45
#: templates/calendar_view/pages/calendar.html:54 #: templates/calendar_view/pages/calendar.html:54
#: templates/monthly_overview/fragments/monthly_summary.html:40 #: templates/monthly_overview/fragments/monthly_summary.html:41
#: templates/monthly_overview/pages/overview.html:54 #: templates/monthly_overview/pages/overview.html:54
#: templates/yearly_overview/pages/overview_by_account.html:49 #: templates/yearly_overview/pages/overview_by_account.html:49
#: templates/yearly_overview/pages/overview_by_currency.html:51 #: templates/yearly_overview/pages/overview_by_currency.html:51
@@ -585,7 +659,7 @@ msgid "Transaction"
msgstr "Transação" msgstr "Transação"
#: apps/transactions/models.py:102 templates/includes/navbar.html:45 #: apps/transactions/models.py:102 templates/includes/navbar.html:45
#: templates/includes/navbar.html:67 #: templates/includes/navbar.html:80
#: templates/recurring_transactions/fragments/list_transactions.html:5 #: templates/recurring_transactions/fragments/list_transactions.html:5
#: templates/recurring_transactions/fragments/table.html:35 #: templates/recurring_transactions/fragments/table.html:35
#: templates/transactions/pages/transactions.html:5 #: templates/transactions/pages/transactions.html:5
@@ -885,6 +959,7 @@ msgstr "Editar grupo de conta"
#: templates/accounts/fragments/list.html:34 #: templates/accounts/fragments/list.html:34
#: templates/categories/fragments/list.html:31 #: templates/categories/fragments/list.html:31
#: templates/currencies/fragments/list.html:31 #: templates/currencies/fragments/list.html:31
#: templates/dca/fragments/strategy/details.html:127
#: templates/exchange_rates/fragments/table.html:23 #: templates/exchange_rates/fragments/table.html:23
#: templates/installment_plans/fragments/table.html:21 #: templates/installment_plans/fragments/table.html:21
#: templates/recurring_transactions/fragments/table.html:23 #: templates/recurring_transactions/fragments/table.html:23
@@ -895,8 +970,10 @@ msgstr "Ações"
#: templates/account_groups/fragments/list.html:34 #: templates/account_groups/fragments/list.html:34
#: templates/accounts/fragments/list.html:38 #: templates/accounts/fragments/list.html:38
#: templates/categories/fragments/list.html:35 #: templates/categories/fragments/list.html:35
#: templates/cotton/transaction/item.html:95 #: templates/cotton/transaction/item.html:99
#: templates/currencies/fragments/list.html:35 #: templates/currencies/fragments/list.html:35
#: templates/dca/fragments/strategy/details.html:131
#: templates/dca/fragments/strategy/list.html:31
#: templates/exchange_rates/fragments/table.html:27 #: templates/exchange_rates/fragments/table.html:27
#: templates/installment_plans/fragments/table.html:25 #: templates/installment_plans/fragments/table.html:25
#: templates/recurring_transactions/fragments/table.html:27 #: templates/recurring_transactions/fragments/table.html:27
@@ -909,28 +986,31 @@ msgstr "Editar"
#: templates/account_groups/fragments/list.html:41 #: templates/account_groups/fragments/list.html:41
#: templates/accounts/fragments/list.html:45 #: templates/accounts/fragments/list.html:45
#: templates/categories/fragments/list.html:42 #: templates/categories/fragments/list.html:42
#: templates/cotton/transaction/item.html:102 #: templates/cotton/transaction/item.html:106
#: templates/cotton/ui/transactions_action_bar.html:50
#: templates/currencies/fragments/list.html:42 #: templates/currencies/fragments/list.html:42
#: templates/dca/fragments/strategy/details.html:139
#: templates/dca/fragments/strategy/list.html:39
#: templates/exchange_rates/fragments/table.html:35 #: templates/exchange_rates/fragments/table.html:35
#: templates/installment_plans/fragments/table.html:54 #: templates/installment_plans/fragments/table.html:54
#: templates/monthly_overview/fragments/list.html:91
#: templates/recurring_transactions/fragments/table.html:89 #: templates/recurring_transactions/fragments/table.html:89
#: templates/rules/fragments/list.html:42 #: templates/rules/fragments/list.html:42
#: templates/rules/fragments/transaction_rule/view.html:56 #: templates/rules/fragments/transaction_rule/view.html:56
#: templates/tags/fragments/list.html:41 #: templates/tags/fragments/list.html:41
#: templates/transactions/fragments/list_all.html:171
msgid "Delete" msgid "Delete"
msgstr "Apagar" msgstr "Apagar"
#: templates/account_groups/fragments/list.html:45 #: templates/account_groups/fragments/list.html:45
#: templates/accounts/fragments/list.html:49 #: templates/accounts/fragments/list.html:49
#: templates/categories/fragments/list.html:46 #: templates/categories/fragments/list.html:46
#: templates/cotton/transaction/item.html:106 #: templates/cotton/transaction/item.html:110
#: templates/cotton/ui/transactions_action_bar.html:52
#: templates/currencies/fragments/list.html:46 #: templates/currencies/fragments/list.html:46
#: templates/dca/fragments/strategy/details.html:144
#: templates/dca/fragments/strategy/list.html:43
#: templates/exchange_rates/fragments/table.html:40 #: templates/exchange_rates/fragments/table.html:40
#: templates/installment_plans/fragments/table.html:46 #: templates/installment_plans/fragments/table.html:46
#: templates/installment_plans/fragments/table.html:58 #: templates/installment_plans/fragments/table.html:58
#: templates/monthly_overview/fragments/list.html:93
#: templates/recurring_transactions/fragments/table.html:51 #: templates/recurring_transactions/fragments/table.html:51
#: templates/recurring_transactions/fragments/table.html:65 #: templates/recurring_transactions/fragments/table.html:65
#: templates/recurring_transactions/fragments/table.html:80 #: templates/recurring_transactions/fragments/table.html:80
@@ -938,29 +1018,31 @@ msgstr "Apagar"
#: templates/rules/fragments/list.html:46 #: templates/rules/fragments/list.html:46
#: templates/rules/fragments/transaction_rule/view.html:60 #: templates/rules/fragments/transaction_rule/view.html:60
#: templates/tags/fragments/list.html:45 #: templates/tags/fragments/list.html:45
#: templates/transactions/fragments/list_all.html:173
msgid "Are you sure?" msgid "Are you sure?"
msgstr "Tem certeza?" msgstr "Tem certeza?"
#: templates/account_groups/fragments/list.html:46 #: templates/account_groups/fragments/list.html:46
#: templates/accounts/fragments/list.html:50 #: templates/accounts/fragments/list.html:50
#: templates/categories/fragments/list.html:47 #: templates/categories/fragments/list.html:47
#: templates/cotton/transaction/item.html:107 #: templates/cotton/transaction/item.html:111
#: templates/cotton/ui/transactions_action_bar.html:53
#: templates/currencies/fragments/list.html:47 #: templates/currencies/fragments/list.html:47
#: templates/dca/fragments/strategy/details.html:145
#: templates/dca/fragments/strategy/list.html:44
#: templates/exchange_rates/fragments/table.html:41 #: templates/exchange_rates/fragments/table.html:41
#: templates/monthly_overview/fragments/list.html:94
#: templates/rules/fragments/list.html:47 #: templates/rules/fragments/list.html:47
#: templates/rules/fragments/transaction_rule/view.html:61 #: templates/rules/fragments/transaction_rule/view.html:61
#: templates/tags/fragments/list.html:46 #: templates/tags/fragments/list.html:46
#: templates/transactions/fragments/list_all.html:174
msgid "You won't be able to revert this!" msgid "You won't be able to revert this!"
msgstr "Você não será capaz de reverter isso!" msgstr "Você não será capaz de reverter isso!"
#: templates/account_groups/fragments/list.html:47 #: templates/account_groups/fragments/list.html:47
#: templates/accounts/fragments/list.html:51 #: templates/accounts/fragments/list.html:51
#: templates/categories/fragments/list.html:48 #: templates/categories/fragments/list.html:48
#: templates/cotton/transaction/item.html:108 #: templates/cotton/transaction/item.html:112
#: templates/currencies/fragments/list.html:48 #: templates/currencies/fragments/list.html:48
#: templates/dca/fragments/strategy/details.html:146
#: templates/dca/fragments/strategy/list.html:45
#: templates/exchange_rates/fragments/table.html:42 #: templates/exchange_rates/fragments/table.html:42
#: templates/installment_plans/fragments/table.html:60 #: templates/installment_plans/fragments/table.html:60
#: templates/recurring_transactions/fragments/table.html:96 #: templates/recurring_transactions/fragments/table.html:96
@@ -1096,6 +1178,30 @@ msgstr "Fechar"
msgid "Select" msgid "Select"
msgstr "Selecionar" msgstr "Selecionar"
#: templates/cotton/ui/transactions_action_bar.html:17
msgid "Select All"
msgstr "Selecionar todos"
#: templates/cotton/ui/transactions_action_bar.html:23
msgid "Unselect All"
msgstr "Desmarcar todos"
#: templates/cotton/ui/transactions_action_bar.html:34
msgid "Mark as paid"
msgstr "Marcar como pago"
#: templates/cotton/ui/transactions_action_bar.html:41
msgid "Mark as unpaid"
msgstr "Marcar como não pago"
#: templates/cotton/ui/transactions_action_bar.html:54
msgid "Yes, delete them!"
msgstr "Sim, apague!"
#: templates/cotton/ui/transactions_action_bar.html:72
msgid "copied!"
msgstr "copiado!"
#: templates/currencies/fragments/add.html:5 #: templates/currencies/fragments/add.html:5
msgid "Add currency" msgid "Add currency"
msgstr "Adicionar moeda" msgstr "Adicionar moeda"
@@ -1112,6 +1218,83 @@ msgstr "Código"
msgid "No currencies" msgid "No currencies"
msgstr "Nenhuma moeda" msgstr "Nenhuma moeda"
#: templates/dca/fragments/entry/add.html:5
msgid "Add DCA entry"
msgstr "Adicionar entrada CMP"
#: templates/dca/fragments/entry/edit.html:5
msgid "Edit DCA entry"
msgstr "Editar entrada CMP"
#: templates/dca/fragments/strategy/add.html:5
msgid "Add DCA strategy"
msgstr "Adicionar estratégia CMP"
#: templates/dca/fragments/strategy/details.html:11
msgid "Total Invested"
msgstr "Total investido"
#: templates/dca/fragments/strategy/details.html:25
msgid "Total Received"
msgstr "Total recebido"
#: templates/dca/fragments/strategy/details.html:39
msgid "Average Entry Price"
msgstr "Preço médio de entrada"
#: templates/dca/fragments/strategy/details.html:53
msgid "Current Total Value"
msgstr "Valor total atual"
#: templates/dca/fragments/strategy/details.html:67
msgid "Total P/L"
msgstr "P/L total"
#: templates/dca/fragments/strategy/details.html:82
#, python-format
msgid "Total %% P/L"
msgstr "P/L%% Total"
#: templates/dca/fragments/strategy/details.html:97
msgid "Entries"
msgstr "Entradas"
#: templates/dca/fragments/strategy/details.html:119
msgid "Current Value"
msgstr "Valor atual"
#: templates/dca/fragments/strategy/details.html:120
msgid "P/L"
msgstr "P/L"
#: templates/dca/fragments/strategy/details.html:180
msgid "No entries for this DCA"
msgstr "Nenhuma entrada neste CMP"
#: templates/dca/fragments/strategy/details.html:181
#: templates/monthly_overview/fragments/list.html:41
#: templates/transactions/fragments/list_all.html:40
msgid "Try adding one"
msgstr "Tente adicionar uma"
#: templates/dca/fragments/strategy/details.html:192
msgid "Performance Over Time"
msgstr "Desempenho ao longo do tempo"
#: templates/dca/fragments/strategy/details.html:208
#, python-format
msgid "P/L %%"
msgstr "P/L %%"
#: templates/dca/fragments/strategy/edit.html:5
msgid "Edit DCA strategy"
msgstr "Editar estratégia CMP"
#: templates/dca/fragments/strategy/list.html:5
#: templates/dca/pages/strategy_index.html:4
msgid "Dollar Cost Average Strategies"
msgstr "Estratégias de Custo Médio Ponderado"
#: templates/exchange_rates/fragments/add.html:5 #: templates/exchange_rates/fragments/add.html:5
msgid "Add exchange rate" msgid "Add exchange rate"
msgstr "Adicionar taxa de câmbio" msgstr "Adicionar taxa de câmbio"
@@ -1120,7 +1303,7 @@ msgstr "Adicionar taxa de câmbio"
msgid "Edit exchange rate" msgid "Edit exchange rate"
msgstr "Editar taxa de câmbio" msgstr "Editar taxa de câmbio"
#: templates/exchange_rates/fragments/list.html:22 #: templates/exchange_rates/fragments/list.html:25
#: templates/includes/navbar.html:49 #: templates/includes/navbar.html:49
#: templates/installment_plans/fragments/list.html:21 #: templates/installment_plans/fragments/list.html:21
#: templates/yearly_overview/pages/overview_by_account.html:135 #: templates/yearly_overview/pages/overview_by_account.html:135
@@ -1149,23 +1332,31 @@ msgid "Overview"
msgstr "Visão Geral" msgstr "Visão Geral"
#: templates/includes/navbar.html:64 #: templates/includes/navbar.html:64
msgid "Tools"
msgstr "Ferramentas"
#: templates/includes/navbar.html:68
msgid "Dollar Cost Average Tracker"
msgstr "Rastreador de Custo Médio Ponderado"
#: templates/includes/navbar.html:77
msgid "Management" msgid "Management"
msgstr "Gerenciar" msgstr "Gerenciar"
#: templates/includes/navbar.html:91 #: templates/includes/navbar.html:104
msgid "Automation" msgid "Automation"
msgstr "Automação" msgstr "Automação"
#: templates/includes/navbar.html:93 templates/rules/fragments/list.html:5 #: templates/includes/navbar.html:106 templates/rules/fragments/list.html:5
#: templates/rules/pages/index.html:4 #: templates/rules/pages/index.html:4
msgid "Rules" msgid "Rules"
msgstr "Regras" msgstr "Regras"
#: templates/includes/navbar.html:103 #: templates/includes/navbar.html:116
msgid "Only use this if you know what you're doing" msgid "Only use this if you know what you're doing"
msgstr "Só use isso se você souber o que está fazendo" msgstr "Só use isso se você souber o que está fazendo"
#: templates/includes/navbar.html:104 #: templates/includes/navbar.html:117
msgid "Django Admin" msgid "Django Admin"
msgstr "Django Admin" msgstr "Django Admin"
@@ -1235,40 +1426,10 @@ msgstr ""
"Algo deu errado ao carregar seus dados. Tente recarregar a página ou " "Algo deu errado ao carregar seus dados. Tente recarregar a página ou "
"verifique o console para obter mais informações." "verifique o console para obter mais informações."
#: templates/monthly_overview/fragments/list.html:47 #: templates/monthly_overview/fragments/list.html:40
msgid "No transactions this month" msgid "No transactions this month"
msgstr "Nenhuma transação neste mês" msgstr "Nenhuma transação neste mês"
#: templates/monthly_overview/fragments/list.html:48
#: templates/transactions/fragments/list_all.html:47
msgid "Try adding one"
msgstr "Tente adicionar uma"
#: templates/monthly_overview/fragments/list.html:58
#: templates/transactions/fragments/list_all.html:138
msgid "Select All"
msgstr "Selecionar todos"
#: templates/monthly_overview/fragments/list.html:64
#: templates/transactions/fragments/list_all.html:144
msgid "Unselect All"
msgstr "Desmarcar todos"
#: templates/monthly_overview/fragments/list.html:75
#: templates/transactions/fragments/list_all.html:155
msgid "Mark as paid"
msgstr "Marcar como pago"
#: templates/monthly_overview/fragments/list.html:82
#: templates/transactions/fragments/list_all.html:162
msgid "Mark as unpaid"
msgstr "Marcar como não pago"
#: templates/monthly_overview/fragments/list.html:95
#: templates/transactions/fragments/list_all.html:175
msgid "Yes, delete them!"
msgstr "Sim, apague!"
#: templates/monthly_overview/fragments/monthly_summary.html:12 #: templates/monthly_overview/fragments/monthly_summary.html:12
msgid "Daily Spending Allowance" msgid "Daily Spending Allowance"
msgstr "Gasto Diário" msgstr "Gasto Diário"
@@ -1277,23 +1438,23 @@ msgstr "Gasto Diário"
msgid "This is the final total divided by the remaining days in the month" msgid "This is the final total divided by the remaining days in the month"
msgstr "Esse é o total final dividido pelos dias restantes do mês" msgstr "Esse é o total final dividido pelos dias restantes do mês"
#: templates/monthly_overview/fragments/monthly_summary.html:43 #: templates/monthly_overview/fragments/monthly_summary.html:44
#: templates/monthly_overview/fragments/monthly_summary.html:88 #: templates/monthly_overview/fragments/monthly_summary.html:91
#: templates/monthly_overview/fragments/monthly_summary.html:133 #: templates/monthly_overview/fragments/monthly_summary.html:138
msgid "current" msgid "current"
msgstr "atual" msgstr "atual"
#: templates/monthly_overview/fragments/monthly_summary.html:60 #: templates/monthly_overview/fragments/monthly_summary.html:62
#: templates/monthly_overview/fragments/monthly_summary.html:105 #: templates/monthly_overview/fragments/monthly_summary.html:109
#: templates/monthly_overview/fragments/monthly_summary.html:149 #: templates/monthly_overview/fragments/monthly_summary.html:155
msgid "projected" msgid "projected"
msgstr "previsto" msgstr "previsto"
#: templates/monthly_overview/fragments/monthly_summary.html:85 #: templates/monthly_overview/fragments/monthly_summary.html:88
msgid "Expenses" msgid "Expenses"
msgstr "Despesas" msgstr "Despesas"
#: templates/monthly_overview/fragments/monthly_summary.html:130 #: templates/monthly_overview/fragments/monthly_summary.html:135
msgid "Total" msgid "Total"
msgstr "Total" msgstr "Total"
@@ -1326,16 +1487,16 @@ msgstr "Mais novas primeiro"
msgid "By currency" msgid "By currency"
msgstr "Por moeda" msgstr "Por moeda"
#: templates/net_worth/net_worth.html:50 #: templates/net_worth/net_worth.html:62
#: templates/yearly_overview/pages/overview_by_account.html:7 #: templates/yearly_overview/pages/overview_by_account.html:7
msgid "By account" msgid "By account"
msgstr "Por conta" msgstr "Por conta"
#: templates/net_worth/net_worth.html:140 #: templates/net_worth/net_worth.html:154
msgid "Evolution by currency" msgid "Evolution by currency"
msgstr "Evolução por moeda" msgstr "Evolução por moeda"
#: templates/net_worth/net_worth.html:193 #: templates/net_worth/net_worth.html:207
msgid "Evolution by account" msgid "Evolution by account"
msgstr "Evolução por conta" msgstr "Evolução por conta"
@@ -1481,11 +1642,11 @@ msgstr "Adicionar parcelamento"
msgid "Edit transaction" msgid "Edit transaction"
msgstr "Editar transação" msgstr "Editar transação"
#: templates/transactions/fragments/list_all.html:46 #: templates/transactions/fragments/list_all.html:39
msgid "No transactions found" msgid "No transactions found"
msgstr "Nenhuma transação encontrada" msgstr "Nenhuma transação encontrada"
#: templates/transactions/fragments/list_all.html:54 #: templates/transactions/fragments/list_all.html:47
msgid "Page navigation" msgid "Page navigation"
msgstr "Navegação por página" msgstr "Navegação por página"
@@ -1519,36 +1680,36 @@ msgid "projected income"
msgstr "renda prevista" msgstr "renda prevista"
#: templates/yearly_overview/fragments/account_data.html:36 #: templates/yearly_overview/fragments/account_data.html:36
#: templates/yearly_overview/fragments/currency_data.html:23 #: templates/yearly_overview/fragments/currency_data.html:35
msgid "projected expenses" msgid "projected expenses"
msgstr "despesas previstas" msgstr "despesas previstas"
#: templates/yearly_overview/fragments/account_data.html:60 #: templates/yearly_overview/fragments/account_data.html:60
#: templates/yearly_overview/fragments/currency_data.html:40 #: templates/yearly_overview/fragments/currency_data.html:64
msgid "projected total" msgid "projected total"
msgstr "total previsto" msgstr "total previsto"
#: templates/yearly_overview/fragments/account_data.html:84 #: templates/yearly_overview/fragments/account_data.html:85
#: templates/yearly_overview/fragments/currency_data.html:60 #: templates/yearly_overview/fragments/currency_data.html:95
msgid "current income" msgid "current income"
msgstr "renda atual" msgstr "renda atual"
#: templates/yearly_overview/fragments/account_data.html:106 #: templates/yearly_overview/fragments/account_data.html:107
#: templates/yearly_overview/fragments/currency_data.html:77 #: templates/yearly_overview/fragments/currency_data.html:124
msgid "current expenses" msgid "current expenses"
msgstr "despesas atuais" msgstr "despesas atuais"
#: templates/yearly_overview/fragments/account_data.html:128 #: templates/yearly_overview/fragments/account_data.html:129
#: templates/yearly_overview/fragments/currency_data.html:94 #: templates/yearly_overview/fragments/currency_data.html:153
msgid "current total" msgid "current total"
msgstr "total atual" msgstr "total atual"
#: templates/yearly_overview/fragments/account_data.html:153 #: templates/yearly_overview/fragments/account_data.html:155
#: templates/yearly_overview/fragments/currency_data.html:114 #: templates/yearly_overview/fragments/currency_data.html:184
msgid "final total" msgid "final total"
msgstr "total final" msgstr "total final"
#: templates/yearly_overview/fragments/account_data.html:177 #: templates/yearly_overview/fragments/account_data.html:179
msgid "No information to display" msgid "No information to display"
msgstr "Não há informação para mostrar" msgstr "Não há informação para mostrar"

View File

@@ -0,0 +1,11 @@
{% extends 'extends/offcanvas.html' %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title %}{% translate 'Add DCA entry' %}{% endblock %}
{% block body %}
<form hx-post="{% url 'dca_entry_add' strategy_id=strategy.id %}" hx-target="#generic-offcanvas" novalidate>
{% crispy form %}
</form>
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends 'extends/offcanvas.html' %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title %}{% translate 'Edit DCA entry' %}{% endblock %}
{% block body %}
<form hx-post="{% url 'exchange_rate_edit' pk=exchange_rate.id %}" hx-target="#generic-offcanvas" novalidate>
{% crispy form %}
</form>
{% endblock %}

View File

@@ -0,0 +1,11 @@
{% extends 'extends/offcanvas.html' %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title %}{% translate 'Add DCA strategy' %}{% endblock %}
{% block body %}
<form hx-post="{% url 'dca_strategy_add' %}" hx-target="#generic-offcanvas" novalidate>
{% crispy form %}
</form>
{% endblock %}

View File

@@ -0,0 +1,222 @@
{% load i18n %}
<div class="container px-md-3 py-3 column-gap-5">
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
<div>{{ strategy.name }}</div>
</div>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 gy-3 gx-3">
<div class="col">
<div class="card">
<div class="card-body">
<h5 class="card-title">{% trans "Total Invested" %}</h5>
<div class="card-text">
<c-amount.display
:amount="strategy.total_invested"
:prefix="strategy.payment_currency.prefix"
:suffix="strategy.payment_currency.suffix"
:decimal_places="strategy.payment_currency.decimal_places"></c-amount.display>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="card-body">
<h5 class="card-title">{% trans "Total Received" %}</h5>
<div class="card-text">
<c-amount.display
:amount="strategy.total_received"
:prefix="strategy.target_currency.prefix"
:suffix="strategy.target_currency.suffix"
:decimal_places="strategy.target_currency.decimal_places"></c-amount.display>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="card-body">
<h5 class="card-title">{% trans "Average Entry Price" %}</h5>
<div class="card-text">
<c-amount.display
:amount="strategy.average_entry_price"
:prefix="strategy.payment_currency.prefix"
:suffix="strategy.payment_currency.suffix"
:decimal_places="strategy.payment_currency.decimal_places"></c-amount.display>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="card-body">
<h5 class="card-title">{% trans "Current Total Value" %}</h5>
<div class="card-text">
<c-amount.display
:amount="strategy.current_total_value"
:prefix="strategy.payment_currency.prefix"
:suffix="strategy.payment_currency.suffix"
:decimal_places="strategy.payment_currency.decimal_places"></c-amount.display>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="card-body">
<h5 class="card-title">{% trans "Total P/L" %}</h5>
<div class="card-text {% if strategy.total_profit_loss >= 0 %}text-success{% else %}text-danger{% endif %}">
<c-amount.display
:amount="strategy.total_profit_loss"
:prefix="strategy.payment_currency.prefix"
:suffix="strategy.payment_currency.suffix"
:decimal_places="strategy.payment_currency.decimal_places">
</c-amount.display>
</div>
</div>
</div>
</div>
<div class="col">
<div class="card">
<div class="card-body">
<h5 class="card-title">{% trans "Total % P/L" %}</h5>
<div class="card-text {% if strategy.total_profit_loss >= 0 %}text-success{% else %}text-danger{% endif %}">
{{ strategy.total_profit_loss_percentage|floatformat:2 }}%
</div>
</div>
</div>
</div>
</div>
<!-- Entries Table -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-body">
{% spaceless %}
<div class="card-title tw-text-xl">{% trans "Entries" %}<span>
<a class="text-decoration-none p-1 category-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Add" %}"
hx-get="{% url 'dca_entry_add' strategy_id=strategy.id%}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-circle-plus fa-fw"></i>
</a>
</span>
</div>
{% endspaceless %}
{% if entries %}
<div class="table-responsive">
<table class="table table-hover text-nowrap">
<thead>
<tr>
<th></th>
<th>{% trans "Date" %}</th>
<th>{% trans "Amount Paid" %}</th>
<th>{% trans "Amount Received" %}</th>
<th>{% trans "Current Value" %}</th>
<th>{% trans "P/L" %}</th>
</tr>
</thead>
<tbody>
{% for entry in entries %}
<tr>
<td class="col-auto">
<div class="btn-group" role="group" aria-label="{% translate 'Actions' %}">
<a class="btn btn-secondary btn-sm"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Edit" %}"
hx-get="{% url 'dca_entry_edit' entry_id=entry.id strategy_id=entry.strategy.id %}"
hx-target="#generic-offcanvas"
hx-swap="innerHTML">
<i class="fa-solid fa-pencil fa-fw"></i></a>
<a class="btn btn-secondary btn-sm text-danger"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Delete" %}"
hx-delete="{% url 'dca_entry_delete' entry_id=entry.id strategy_id=entry.strategy.id %}"
hx-trigger='confirmed'
hx-swap="innerHTML"
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal"><i class="fa-solid fa-trash fa-fw"></i></a>
</div>
</td>
<td>{{ entry.date|date:"SHORT_DATE_FORMAT" }}</td>
<td><c-amount.display
:amount="entry.amount_paid"
:prefix="entry.strategy.payment_currency.prefix"
:suffix="entry.strategy.payment_currency.suffix"
:decimal_places="entry.strategy.payment_currency.decimal_places"></c-amount.display></td>
<td><c-amount.display
:amount="entry.amount_received"
:prefix="entry.strategy.target_currency.prefix"
:suffix="entry.strategy.target_currency.suffix"
:decimal_places="entry.strategy.target_currency.decimal_places"></c-amount.display></td>
<td><c-amount.display
:amount="entry.current_value"
:prefix="entry.strategy.payment_currency.prefix"
:suffix="entry.strategy.payment_currency.suffix"
:decimal_places="entry.strategy.payment_currency.decimal_places"></c-amount.display></td>
<td>
{% if entry.profit_loss_percentage > 0 %}
<span class="badge text-bg-success"><i class="fa-solid fa-up-long me-2"></i>{{ entry.profit_loss_percentage|floatformat:"2g" }}%</span>
{% elif entry.profit_loss_percentage < 0 %}
<span class="badge text-bg-danger"><i class="fa-solid fa-down-long me-2"></i>{{ entry.profit_loss_percentage|floatformat:"2g" }}%</span>
{% endif %}
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% else %}
<c-msg.empty
title="{% translate 'No entries for this DCA' %}"
subtitle="{% translate "Try adding one" %}" :remove-padding="True"></c-msg.empty>
{% endif %}
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<h5 class="card-title">{% trans "Performance Over Time" %}</h5>
<canvas id="performanceChart"></canvas>
</div>
</div>
</div>
</div>
</div>
<script defer>
// Add Chart.js initialization for performance visualization
var perfomancectx = document.getElementById('performanceChart').getContext('2d');
new Chart(perfomancectx, {
type: 'line',
data: {
labels: [{% for entry in entries_data %}'{{ entry.entry.date|date:"Y-m-d" }}'{% if not forloop.last %}, {% endif %}{% endfor %}],
datasets: [{
label: '{% trans "P/L %" %}',
data: [{% for entry in entries_data %}{{ entry.profit_loss_percentage|floatformat:"-40u" }}{% if not forloop.last %}, {% endif %}{% endfor %}],
tension: 0.1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: false
}
}
}
});
</script>

View File

@@ -0,0 +1,11 @@
{% extends 'extends/offcanvas.html' %}
{% load i18n %}
{% load crispy_forms_tags %}
{% block title %}{% translate 'Edit DCA strategy' %}{% endblock %}
{% block body %}
<form hx-post="{% url 'dca_entry_edit' entry_id=dca_entry.id strategy_id=dca_entry.strategy.id %}" hx-target="#generic-offcanvas" novalidate>
{% crispy form %}
</form>
{% endblock %}

View File

@@ -0,0 +1,54 @@
{% load i18n %}
<div class="container px-md-3 py-3 column-gap-5">
<div class="tw-text-3xl fw-bold font-monospace tw-w-full mb-3">
{% spaceless %}
<div>{% translate 'Dollar Cost Average Strategies' %}<span>
<a class="text-decoration-none tw-text-2xl p-1 category-action"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Add" %}"
hx-get="{% url 'dca_strategy_add' %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-circle-plus fa-fw"></i></a>
</span></div>
{% endspaceless %}
</div>
<div class="row row-cols-1 row-cols-md-2 row-cols-lg-3 row-cols-xl-4 gy-3 gx-3">
{% for strategy in strategies %}
<div class="col">
<div class="card h-100">
<a href="{% url 'dca_strategy_detail_index' strategy_id=strategy.id %}" hx-boost="true" class="text-decoration-none card-body">
<div class="">
<div class="card-title tw-text-xl">{{ strategy.name }}</div>
<div class="card-text tw-text-gray-400">{{ strategy.notes }}</div>
</div>
</a>
<div class="card-footer text-end">
<a class="text-decoration-none tw-text-gray-400 p-1"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Edit" %}"
hx-get="{% url 'dca_strategy_edit' strategy_id=strategy.id %}"
hx-target="#generic-offcanvas">
<i class="fa-solid fa-pencil fa-fw"></i>
</a>
<a class="text-danger text-decoration-none p-1"
role="button"
data-bs-toggle="tooltip"
data-bs-title="{% translate "Delete" %}"
hx-delete="{% url 'dca_strategy_delete' strategy_id=strategy.id %}"
hx-trigger='confirmed'
data-bypass-on-ctrl="true"
data-title="{% translate "Are you sure?" %}"
data-text="{% translate "You won't be able to revert this!" %}"
data-confirm-text="{% translate "Yes, delete it!" %}"
_="install prompt_swal">
<i class="fa-solid fa-trash fa-fw"></i>
</a>
</div>
</div>
</div>
{% endfor %}
</div>
</div>

View File

@@ -0,0 +1,6 @@
{% extends "layouts/base.html" %}
{% load i18n %}
{% block content %}
<div hx-get="{% url 'dca_strategy_detail' strategy_id=strategy.id %}" hx-trigger="load, updated from:window" class="show-loading"></div>
{% endblock %}

View File

@@ -0,0 +1,8 @@
{% extends "layouts/base.html" %}
{% load i18n %}
{% block title %}{% translate 'Dollar Cost Average Strategies' %}{% endblock %}
{% block content %}
<div hx-get="{% url 'dca_strategy_list' %}" hx-trigger="load, updated from:window" class="show-loading"></div>
{% endblock %}

View File

@@ -1,228 +0,0 @@
{% extends "layouts/base.html" %}
{% load i18n %}
{% block content %}
<div class="container">
<h1>{{ strategy.name }}</h1>
<div class="row">
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">{% trans "Total Invested" %}</h5>
{# <p class="card-text">{{ strategy.total_invested }} {{ strategy.payment_currency }}</p>#}
<div class="card-text">
<c-amount.display
:amount="strategy.total_invested"
:prefix="strategy.payment_currency.prefix"
:suffix="strategy.payment_currency.suffix"
:decimal_places="strategy.payment_currency.decimal_places"></c-amount.display>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">{% trans "Total Received" %}</h5>
<div class="card-text">
<c-amount.display
:amount="strategy.total_received"
:prefix="strategy.target_currency.prefix"
:suffix="strategy.target_currency.suffix"
:decimal_places="strategy.target_currency.decimal_places"></c-amount.display>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">{% trans "Average Entry Price" %}</h5>
<div class="card-text">
<c-amount.display
:amount="strategy.average_entry_price"
:prefix="strategy.payment_currency.prefix"
:suffix="strategy.payment_currency.suffix"
:decimal_places="strategy.payment_currency.decimal_places"></c-amount.display>
</div>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">{% trans "Current Total Value" %}</h5>
<div class="card-text">
<c-amount.display
:amount="strategy.current_total_value"
:prefix="strategy.payment_currency.prefix"
:suffix="strategy.payment_currency.suffix"
:decimal_places="strategy.payment_currency.decimal_places"></c-amount.display>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">{% trans "Total P/L" %}</h5>
<div class="card-text {% if strategy.total_profit_loss >= 0 %}text-success{% else %}text-danger{% endif %}">
<c-amount.display
:amount="strategy.total_profit_loss"
:prefix="strategy.payment_currency.prefix"
:suffix="strategy.payment_currency.suffix"
:decimal_places="strategy.payment_currency.decimal_places">
</c-amount.display>
</div>
</div>
</div>
</div>
<div class="col-md-4">
<div class="card">
<div class="card-body">
<h5 class="card-title">{% trans "Total % P/L" %}</h5>
<div class="card-text {% if strategy.total_profit_loss >= 0 %}text-success{% else %}text-danger{% endif %}">
{{ strategy.total_profit_loss_percentage|floatformat:2 }}%
</div>
</div>
</div>
</div>
</div>
<!-- Monthly Chart -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<canvas id="monthlyChart"></canvas>
</div>
</div>
</div>
</div>
<!-- Entries Table -->
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<h5 class="card-title">{% trans "Entries" %}</h5>
<div class="table-responsive">
<table class="table table-hover text-nowrap">
<thead>
<tr>
<th>{% trans "Date" %}</th>
<th>{% trans "Amount Paid" %}</th>
<th>{% trans "Amount Received" %}</th>
<th>{% trans "Current Value" %}</th>
<th>{% trans "P/L" %}</th>
<th>{% trans "Actions" %}</th>
</tr>
</thead>
<tbody>
{% for entry in entries %}
<tr>
<td>{{ entry.date|date:"SHORT_DATE_FORMAT" }}</td>
<td><c-amount.display
:amount="entry.amount_paid"
:prefix="entry.strategy.payment_currency.prefix"
:suffix="entry.strategy.payment_currency.suffix"
:decimal_places="entry.strategy.payment_currency.decimal_places"></c-amount.display></td>
<td><c-amount.display
:amount="entry.amount_received"
:prefix="entry.strategy.target_currency.prefix"
:suffix="entry.strategy.target_currency.suffix"
:decimal_places="entry.strategy.target_currency.decimal_places"></c-amount.display></td>
<td><c-amount.display
:amount="entry.current_value"
:prefix="entry.strategy.payment_currency.prefix"
:suffix="entry.strategy.payment_currency.suffix"
:decimal_places="entry.strategy.payment_currency.decimal_places"></c-amount.display></td>
<td>
{% if entry.profit_loss_percentage > 0 %}
<span class="badge text-bg-success"><i class="fa-solid fa-up-long me-2"></i>{{ entry.profit_loss_percentage|floatformat:"2g" }}%</span>
{% elif entry.profit_loss_percentage < 0 %}
<span class="badge text-bg-danger"><i class="fa-solid fa-down-long me-2"></i>{{ entry.profit_loss_percentage|floatformat:"2g" }}%</span>
{% endif %}
</td>
<td>
<!-- Add action buttons -->
</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
</div>
<div class="row mt-4">
<div class="col-12">
<div class="card">
<div class="card-body">
<h5 class="card-title">{% trans "Performance Over Time" %}</h5>
<canvas id="performanceChart"></canvas>
</div>
</div>
</div>
</div>
</div>
{% endblock %}
{% block extra_js_body %}
<script>
// Add Chart.js initialization for performance visualization
const perfomancectx = document.getElementById('performanceChart').getContext('2d');
const performanceChart = new Chart(perfomancectx, {
type: 'line',
data: {
labels: [{% for entry in entries_data %}'{{ entry.entry.date|date:"Y-m-d" }}'{% if not forloop.last %}, {% endif %}{% endfor %}],
datasets: [{
label: '{% trans "P/L %" %}',
data: [{% for entry in entries_data %}{{ entry.profit_loss_percentage|floatformat:2 }}{% if not forloop.last %}, {% endif %}{% endfor %}],
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true
}
}
}
});
</script>
<script>
// Add Chart.js initialization for performance visualization
const ctx = document.getElementById('monthlyChart').getContext('2d');
const chart = new Chart(ctx, {
type: 'line',
data: {
labels: [{% for entry in monthly_data %}'{{ entry.date|date:"Y-m-d" }}'{% if not forloop.last %}, {% endif %}{% endfor %}],
datasets: [{
label: '{% trans "P/L %" %}',
data: [{% for entry in monthly_data %}{{ entry.total_paid|floatformat:2 }}{% if not forloop.last %}, {% endif %}{% endfor %}],
borderColor: 'rgb(75, 192, 192)',
tension: 0.1
}]
},
options: {
responsive: true,
scales: {
y: {
beginAtZero: true
}
}
}
});
</script>
{% endblock %}

View File

@@ -56,6 +56,19 @@
href="{% url 'recurring_trasanctions_index' %}">{% translate 'Recurring Transactions' %}</a></li> href="{% url 'recurring_trasanctions_index' %}">{% translate 'Recurring Transactions' %}</a></li>
</ul> </ul>
</li> </li>
<li class="nav-item dropdown">
<a class="nav-link dropdown-toggle {% active_link views='dca_strategy_index||dca_strategy_detail_index' %}"
href="#" role="button"
data-bs-toggle="dropdown"
aria-expanded="false">
{% translate 'Tools' %}
</a>
<ul class="dropdown-menu">
<li><a class="dropdown-item {% active_link views='dca_strategy_index||dca_strategy_detail_index' %}"
href="{% url 'dca_strategy_index' %}">{% translate 'Dollar Cost Average Tracker' %}</a></li>
<li>
</ul>
</li>
<li class="nav-item dropdown"> <li class="nav-item dropdown">
<a class="nav-link dropdown-toggle {% active_link views='tags_index||categories_index||accounts_index||account_groups_index||currencies_index||exchange_rates_index||rules_index' %}" <a class="nav-link dropdown-toggle {% active_link views='tags_index||categories_index||accounts_index||account_groups_index||currencies_index||exchange_rates_index||rules_index' %}"
href="#" role="button" href="#" role="button"