mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-02-25 00:44:52 +01:00
Compare commits
33 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
627b5d250b | ||
|
|
195a8a68d6 | ||
|
|
daf1f68b82 | ||
|
|
dd24fd56d3 | ||
|
|
7a2acb6497 | ||
|
|
9c339faa72 | ||
|
|
02376ad02b | ||
|
|
b53a4a0286 | ||
|
|
a1f618434b | ||
|
|
7b5be29f0d | ||
|
|
56a73b181a | ||
|
|
865618e054 | ||
|
|
9e912b2736 | ||
|
|
da7680e70f | ||
|
|
ab594eb511 | ||
|
|
cffaaa369a | ||
|
|
5f414e82ee | ||
|
|
f3bcef534e | ||
|
|
d140ff5b70 | ||
|
|
7eceacfe68 | ||
|
|
038438fba7 | ||
|
|
ee98a5ef12 | ||
|
|
28b12faaf0 | ||
|
|
d0f2742637 | ||
|
|
9c55dac866 | ||
|
|
e6d8b548b7 | ||
|
|
4f8c2215c1 | ||
|
|
851b34f07a | ||
|
|
546ed5c6af | ||
|
|
04ae7337f5 | ||
|
|
a3a8791e96 | ||
|
|
63069f0ec9 | ||
|
|
32b522dad2 |
@@ -49,4 +49,5 @@ urlpatterns = [
|
||||
path("", include("apps.dca.urls")),
|
||||
path("", include("apps.mini_tools.urls")),
|
||||
path("", include("apps.import_app.urls")),
|
||||
path("", include("apps.insights.urls")),
|
||||
]
|
||||
|
||||
@@ -12,15 +12,14 @@ class DynamicModelChoiceField(forms.ModelChoiceField):
|
||||
self.to_field_name = kwargs.pop("to_field_name", "pk")
|
||||
|
||||
self.create_field = kwargs.pop("create_field", None)
|
||||
if not self.create_field:
|
||||
raise ValueError("The 'create_field' parameter is required.")
|
||||
|
||||
self.queryset = kwargs.pop("queryset", model.objects.all())
|
||||
super().__init__(queryset=self.queryset, *args, **kwargs)
|
||||
self._created_instance = None
|
||||
|
||||
self.widget = TomSelect(clear_button=True, create=True)
|
||||
|
||||
super().__init__(queryset=self.queryset, *args, **kwargs)
|
||||
self._created_instance = None
|
||||
|
||||
def to_python(self, value):
|
||||
if value in self.empty_values:
|
||||
return None
|
||||
@@ -53,14 +52,19 @@ class DynamicModelChoiceField(forms.ModelChoiceField):
|
||||
else:
|
||||
raise self.model.DoesNotExist
|
||||
except self.model.DoesNotExist:
|
||||
try:
|
||||
with transaction.atomic():
|
||||
instance, _ = self.model.objects.update_or_create(
|
||||
**{self.create_field: value}
|
||||
if self.create_field:
|
||||
try:
|
||||
with transaction.atomic():
|
||||
instance, _ = self.model.objects.update_or_create(
|
||||
**{self.create_field: value}
|
||||
)
|
||||
self._created_instance = instance
|
||||
return instance
|
||||
except Exception as e:
|
||||
raise ValidationError(
|
||||
self.error_messages["invalid_choice"], code="invalid_choice"
|
||||
)
|
||||
self._created_instance = instance
|
||||
return instance
|
||||
except Exception as e:
|
||||
else:
|
||||
raise ValidationError(
|
||||
self.error_messages["invalid_choice"], code="invalid_choice"
|
||||
)
|
||||
|
||||
@@ -227,3 +227,56 @@ class AirMonthYearPickerInput(AirDatePickerInput):
|
||||
except (ValueError, KeyError):
|
||||
return None
|
||||
return None
|
||||
|
||||
|
||||
class AirYearPickerInput(AirDatePickerInput):
|
||||
def __init__(self, attrs=None, format=None, *args, **kwargs):
|
||||
super().__init__(attrs=attrs, format=format, *args, **kwargs)
|
||||
# Store the display format for AirDatepicker
|
||||
self.display_format = "yyyy"
|
||||
# Store the Python format for internal use
|
||||
self.python_format = "%Y"
|
||||
|
||||
def build_attrs(self, base_attrs, extra_attrs=None):
|
||||
attrs = super().build_attrs(base_attrs, extra_attrs)
|
||||
|
||||
# Add data attributes for AirDatepicker configuration
|
||||
attrs["data-now-button-txt"] = _("Today")
|
||||
attrs["data-date-format"] = "yyyy"
|
||||
|
||||
return attrs
|
||||
|
||||
def format_value(self, value):
|
||||
"""Format the value for display in the widget."""
|
||||
if value:
|
||||
self.attrs["data-value"] = (
|
||||
value # We use this to dynamically select the initial date on AirDatePicker
|
||||
)
|
||||
|
||||
if value is None:
|
||||
return ""
|
||||
if isinstance(value, str):
|
||||
try:
|
||||
value = datetime.datetime.strptime(value, "%Y-%m-%d").date()
|
||||
except ValueError:
|
||||
return value
|
||||
if isinstance(value, (datetime.datetime, datetime.date)):
|
||||
# Use Django's date translation
|
||||
return f"{value.year}"
|
||||
return value
|
||||
|
||||
def value_from_datadict(self, data, files, name):
|
||||
"""Convert the value from the widget format back to a format Django can handle."""
|
||||
value = super().value_from_datadict(data, files, name)
|
||||
if value:
|
||||
try:
|
||||
# Split the value into month name and year
|
||||
year_str = value
|
||||
year = int(year_str)
|
||||
|
||||
if year:
|
||||
# Return the first day of the month in Django's expected format
|
||||
return datetime.date(year, 1, 1).strftime("%Y-%m-%d")
|
||||
except (ValueError, KeyError):
|
||||
return None
|
||||
return None
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
from django.forms import widgets, SelectMultiple
|
||||
from django.urls import reverse
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
@@ -129,3 +130,28 @@ class TomSelect(widgets.Select):
|
||||
|
||||
class TomSelectMultiple(SelectMultiple, TomSelect):
|
||||
pass
|
||||
|
||||
|
||||
class TransactionSelect(TomSelect):
|
||||
def __init__(self, income: bool = True, expense: bool = True, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.load_income = income
|
||||
self.load_expense = expense
|
||||
self.create = False
|
||||
|
||||
def build_attrs(self, base_attrs, extra_attrs=None):
|
||||
attrs = super().build_attrs(base_attrs, extra_attrs)
|
||||
|
||||
if self.load_income and self.load_expense:
|
||||
attrs["data-load"] = reverse("transactions_search")
|
||||
elif self.load_income and not self.load_expense:
|
||||
attrs["data-load"] = reverse(
|
||||
"transactions_search", kwargs={"filter_type": "income"}
|
||||
)
|
||||
elif self.load_expense and not self.load_income:
|
||||
attrs["data-load"] = reverse(
|
||||
"transactions_search", kwargs={"filter_type": "expenses"}
|
||||
)
|
||||
|
||||
return attrs
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
from crispy_forms.bootstrap import FormActions
|
||||
from crispy_bootstrap5.bootstrap5 import Switch, BS5Accordion
|
||||
from crispy_forms.bootstrap import FormActions, AccordionGroup
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Layout, Row, Column
|
||||
from crispy_forms.layout import Layout, Row, Column, HTML
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from apps.accounts.models import Account
|
||||
from apps.common.widgets.crispy.submit import NoClassSubmit
|
||||
from apps.common.widgets.datepicker import AirDatePickerInput
|
||||
from apps.common.widgets.decimal import ArbitraryDecimalDisplayNumberInput
|
||||
from apps.common.widgets.tom_select import TomSelect
|
||||
from apps.dca.models import DCAStrategy, DCAEntry
|
||||
from apps.common.widgets.tom_select import TransactionSelect
|
||||
from apps.transactions.models import Transaction, TransactionTag, TransactionCategory
|
||||
from apps.common.fields.forms.dynamic_select import (
|
||||
DynamicModelChoiceField,
|
||||
DynamicModelMultipleChoiceField,
|
||||
)
|
||||
|
||||
|
||||
class DCAStrategyForm(forms.ModelForm):
|
||||
@@ -53,6 +61,75 @@ class DCAStrategyForm(forms.ModelForm):
|
||||
|
||||
|
||||
class DCAEntryForm(forms.ModelForm):
|
||||
create_transaction = forms.BooleanField(
|
||||
label=_("Create transaction"), initial=False, required=False
|
||||
)
|
||||
|
||||
from_account = forms.ModelChoiceField(
|
||||
queryset=Account.objects.filter(is_archived=False),
|
||||
label=_("From Account"),
|
||||
widget=TomSelect(clear_button=False, group_by="group"),
|
||||
required=False,
|
||||
)
|
||||
to_account = forms.ModelChoiceField(
|
||||
queryset=Account.objects.filter(is_archived=False),
|
||||
label=_("To Account"),
|
||||
widget=TomSelect(clear_button=False, group_by="group"),
|
||||
required=False,
|
||||
)
|
||||
|
||||
from_category = DynamicModelChoiceField(
|
||||
create_field="name",
|
||||
model=TransactionCategory,
|
||||
required=False,
|
||||
label=_("Category"),
|
||||
queryset=TransactionCategory.objects.filter(active=True),
|
||||
)
|
||||
to_category = DynamicModelChoiceField(
|
||||
create_field="name",
|
||||
model=TransactionCategory,
|
||||
required=False,
|
||||
label=_("Category"),
|
||||
queryset=TransactionCategory.objects.filter(active=True),
|
||||
)
|
||||
|
||||
from_tags = DynamicModelMultipleChoiceField(
|
||||
model=TransactionTag,
|
||||
to_field_name="name",
|
||||
create_field="name",
|
||||
required=False,
|
||||
label=_("Tags"),
|
||||
queryset=TransactionTag.objects.filter(active=True),
|
||||
)
|
||||
to_tags = DynamicModelMultipleChoiceField(
|
||||
model=TransactionTag,
|
||||
to_field_name="name",
|
||||
create_field="name",
|
||||
required=False,
|
||||
label=_("Tags"),
|
||||
queryset=TransactionTag.objects.filter(active=True),
|
||||
)
|
||||
|
||||
expense_transaction = DynamicModelChoiceField(
|
||||
model=Transaction,
|
||||
to_field_name="id",
|
||||
label=_("Expense Transaction"),
|
||||
required=False,
|
||||
queryset=Transaction.objects.none(),
|
||||
widget=TransactionSelect(clear_button=True, income=False, expense=True),
|
||||
help_text=_("Type to search for a transaction to link to this entry"),
|
||||
)
|
||||
|
||||
income_transaction = DynamicModelChoiceField(
|
||||
model=Transaction,
|
||||
to_field_name="id",
|
||||
label=_("Income Transaction"),
|
||||
required=False,
|
||||
queryset=Transaction.objects.none(),
|
||||
widget=TransactionSelect(clear_button=True, income=True, expense=False),
|
||||
help_text=_("Type to search for a transaction to link to this entry"),
|
||||
)
|
||||
|
||||
class Meta:
|
||||
model = DCAEntry
|
||||
fields = [
|
||||
@@ -60,13 +137,19 @@ class DCAEntryForm(forms.ModelForm):
|
||||
"amount_paid",
|
||||
"amount_received",
|
||||
"notes",
|
||||
"expense_transaction",
|
||||
"income_transaction",
|
||||
]
|
||||
widgets = {
|
||||
"notes": forms.Textarea(attrs={"rows": 3}),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
strategy = kwargs.pop("strategy", None)
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.strategy = strategy if strategy else self.instance.strategy
|
||||
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_tag = False
|
||||
self.helper.layout = Layout(
|
||||
@@ -75,18 +158,66 @@ class DCAEntryForm(forms.ModelForm):
|
||||
Column("amount_paid", css_class="form-group col-md-6"),
|
||||
Column("amount_received", css_class="form-group col-md-6"),
|
||||
),
|
||||
Row(
|
||||
Column("expense_transaction", css_class="form-group col-md-6"),
|
||||
Column("income_transaction", css_class="form-group col-md-6"),
|
||||
),
|
||||
"notes",
|
||||
BS5Accordion(
|
||||
AccordionGroup(
|
||||
_("Create transaction"),
|
||||
Switch("create_transaction"),
|
||||
Row(
|
||||
Column(
|
||||
Row(
|
||||
Column(
|
||||
"from_account",
|
||||
css_class="form-group col-md-6 mb-0",
|
||||
),
|
||||
css_class="form-row",
|
||||
),
|
||||
Row(
|
||||
Column(
|
||||
"from_category",
|
||||
css_class="form-group col-md-6 mb-0",
|
||||
),
|
||||
Column(
|
||||
"from_tags", css_class="form-group col-md-6 mb-0"
|
||||
),
|
||||
css_class="form-row",
|
||||
),
|
||||
),
|
||||
css_class="p-1 mx-1 my-3 border rounded-3",
|
||||
),
|
||||
Row(
|
||||
Column(
|
||||
Row(
|
||||
Column(
|
||||
"to_account",
|
||||
css_class="form-group col-md-6 mb-0",
|
||||
),
|
||||
css_class="form-row",
|
||||
),
|
||||
Row(
|
||||
Column(
|
||||
"to_category", css_class="form-group col-md-6 mb-0"
|
||||
),
|
||||
Column("to_tags", css_class="form-group col-md-6 mb-0"),
|
||||
css_class="form-row",
|
||||
),
|
||||
),
|
||||
css_class="p-1 mx-1 my-3 border rounded-3",
|
||||
),
|
||||
active=False,
|
||||
),
|
||||
AccordionGroup(
|
||||
_("Link transaction"),
|
||||
"income_transaction",
|
||||
"expense_transaction",
|
||||
),
|
||||
flush=False,
|
||||
always_open=False,
|
||||
css_class="mb-3",
|
||||
),
|
||||
)
|
||||
|
||||
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(
|
||||
@@ -95,7 +226,6 @@ class DCAEntryForm(forms.ModelForm):
|
||||
),
|
||||
)
|
||||
else:
|
||||
# self.fields["amount"].widget = ArbitraryDecimalDisplayNumberInput()
|
||||
self.helper.layout.append(
|
||||
FormActions(
|
||||
NoClassSubmit(
|
||||
@@ -107,3 +237,118 @@ class DCAEntryForm(forms.ModelForm):
|
||||
self.fields["amount_paid"].widget = ArbitraryDecimalDisplayNumberInput()
|
||||
self.fields["amount_received"].widget = ArbitraryDecimalDisplayNumberInput()
|
||||
self.fields["date"].widget = AirDatePickerInput(clear_button=False)
|
||||
|
||||
expense_transaction = None
|
||||
income_transaction = None
|
||||
if self.instance and self.instance.pk:
|
||||
# Edit mode - get from instance
|
||||
expense_transaction = self.instance.expense_transaction
|
||||
income_transaction = self.instance.income_transaction
|
||||
elif self.data.get("expense_transaction"):
|
||||
# Form validation - get from submitted data
|
||||
try:
|
||||
expense_transaction = Transaction.objects.get(
|
||||
id=self.data["expense_transaction"]
|
||||
)
|
||||
income_transaction = Transaction.objects.get(
|
||||
id=self.data["income_transaction"]
|
||||
)
|
||||
except Transaction.DoesNotExist:
|
||||
pass
|
||||
|
||||
# If we have a current transaction, ensure it's in the queryset
|
||||
if income_transaction:
|
||||
self.fields["income_transaction"].queryset = Transaction.objects.filter(
|
||||
id=income_transaction.id
|
||||
)
|
||||
if expense_transaction:
|
||||
self.fields["expense_transaction"].queryset = Transaction.objects.filter(
|
||||
id=expense_transaction.id
|
||||
)
|
||||
|
||||
def clean(self):
|
||||
cleaned_data = super().clean()
|
||||
|
||||
if cleaned_data.get("create_transaction"):
|
||||
from_account = cleaned_data.get("from_account")
|
||||
to_account = cleaned_data.get("to_account")
|
||||
|
||||
if not from_account and not to_account:
|
||||
raise forms.ValidationError(
|
||||
{
|
||||
"from_account": _("You must provide an account."),
|
||||
"to_account": _("You must provide an account."),
|
||||
}
|
||||
)
|
||||
elif not from_account and to_account:
|
||||
raise forms.ValidationError(
|
||||
{"from_account": _("You must provide an account.")}
|
||||
)
|
||||
elif not to_account and from_account:
|
||||
raise forms.ValidationError(
|
||||
{"to_account": _("You must provide an account.")}
|
||||
)
|
||||
|
||||
if from_account == to_account:
|
||||
raise forms.ValidationError(
|
||||
_("From and To accounts must be different.")
|
||||
)
|
||||
|
||||
return cleaned_data
|
||||
|
||||
def save(self, **kwargs):
|
||||
instance = super().save(commit=False)
|
||||
|
||||
if self.cleaned_data.get("create_transaction"):
|
||||
from_account = self.cleaned_data["from_account"]
|
||||
to_account = self.cleaned_data["to_account"]
|
||||
from_amount = instance.amount_paid
|
||||
to_amount = instance.amount_received
|
||||
date = instance.date
|
||||
description = _("DCA for %(strategy_name)s") % {
|
||||
"strategy_name": self.strategy.name
|
||||
}
|
||||
from_category = self.cleaned_data.get("from_category")
|
||||
to_category = self.cleaned_data.get("to_category")
|
||||
notes = self.cleaned_data.get("notes")
|
||||
|
||||
# Create "From" transaction
|
||||
from_transaction = Transaction.objects.create(
|
||||
account=from_account,
|
||||
type=Transaction.Type.EXPENSE,
|
||||
is_paid=True,
|
||||
date=date,
|
||||
amount=from_amount,
|
||||
description=description,
|
||||
category=from_category,
|
||||
notes=notes,
|
||||
)
|
||||
from_transaction.tags.set(self.cleaned_data.get("from_tags", []))
|
||||
|
||||
# Create "To" transaction
|
||||
to_transaction = Transaction.objects.create(
|
||||
account=to_account,
|
||||
type=Transaction.Type.INCOME,
|
||||
is_paid=True,
|
||||
date=date,
|
||||
amount=to_amount,
|
||||
description=description,
|
||||
category=to_category,
|
||||
notes=notes,
|
||||
)
|
||||
to_transaction.tags.set(self.cleaned_data.get("to_tags", []))
|
||||
|
||||
instance.expense_transaction = from_transaction
|
||||
instance.income_transaction = to_transaction
|
||||
else:
|
||||
if instance.expense_transaction:
|
||||
instance.expense_transaction.amount = instance.amount_paid
|
||||
instance.expense_transaction.save()
|
||||
if instance.income_transaction:
|
||||
instance.income_transaction.amount = instance.amount_received
|
||||
instance.income_transaction.save()
|
||||
|
||||
instance.strategy = self.strategy
|
||||
instance.save()
|
||||
|
||||
return instance
|
||||
|
||||
@@ -155,11 +155,9 @@ def strategy_detail(request, strategy_id):
|
||||
def strategy_entry_add(request, strategy_id):
|
||||
strategy = get_object_or_404(DCAStrategy, id=strategy_id)
|
||||
if request.method == "POST":
|
||||
form = DCAEntryForm(request.POST)
|
||||
form = DCAEntryForm(request.POST, strategy=strategy)
|
||||
if form.is_valid():
|
||||
entry = form.save(commit=False)
|
||||
entry.strategy = strategy
|
||||
entry.save()
|
||||
entry = form.save()
|
||||
messages.success(request, _("Entry added successfully"))
|
||||
|
||||
return HttpResponse(
|
||||
@@ -169,7 +167,7 @@ def strategy_entry_add(request, strategy_id):
|
||||
},
|
||||
)
|
||||
else:
|
||||
form = DCAEntryForm()
|
||||
form = DCAEntryForm(strategy=strategy)
|
||||
|
||||
return render(
|
||||
request,
|
||||
|
||||
0
app/apps/insights/__init__.py
Normal file
0
app/apps/insights/__init__.py
Normal file
3
app/apps/insights/admin.py
Normal file
3
app/apps/insights/admin.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.contrib import admin
|
||||
|
||||
# Register your models here.
|
||||
6
app/apps/insights/apps.py
Normal file
6
app/apps/insights/apps.py
Normal file
@@ -0,0 +1,6 @@
|
||||
from django.apps import AppConfig
|
||||
|
||||
|
||||
class InsightsConfig(AppConfig):
|
||||
default_auto_field = "django.db.models.BigAutoField"
|
||||
name = "apps.insights"
|
||||
110
app/apps/insights/forms.py
Normal file
110
app/apps/insights/forms.py
Normal file
@@ -0,0 +1,110 @@
|
||||
from crispy_forms.helper import FormHelper
|
||||
from crispy_forms.layout import Layout, Field, Row, Column
|
||||
from django import forms
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
from apps.common.widgets.datepicker import (
|
||||
AirMonthYearPickerInput,
|
||||
AirYearPickerInput,
|
||||
AirDatePickerInput,
|
||||
)
|
||||
|
||||
|
||||
class SingleMonthForm(forms.Form):
|
||||
month = forms.DateField(
|
||||
widget=AirMonthYearPickerInput(clear_button=False), label="", required=False
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_tag = False
|
||||
self.helper.disable_csrf = True
|
||||
|
||||
self.helper.layout = Layout(Field("month"))
|
||||
|
||||
|
||||
class SingleYearForm(forms.Form):
|
||||
year = forms.DateField(
|
||||
widget=AirYearPickerInput(clear_button=False), label="", required=False
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_tag = False
|
||||
self.helper.disable_csrf = True
|
||||
|
||||
self.helper.layout = Layout(Field("year"))
|
||||
|
||||
|
||||
class MonthRangeForm(forms.Form):
|
||||
month_from = forms.DateField(
|
||||
widget=AirMonthYearPickerInput(clear_button=False), label="", required=False
|
||||
)
|
||||
month_to = forms.DateField(
|
||||
widget=AirMonthYearPickerInput(clear_button=False), label="", required=False
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_tag = False
|
||||
self.helper.disable_csrf = True
|
||||
|
||||
self.helper.layout = Layout(
|
||||
Row(
|
||||
Column("month_from", css_class="form-group col-md-6"),
|
||||
Column("month_to", css_class="form-group col-md-6"),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class YearRangeForm(forms.Form):
|
||||
year_from = forms.DateField(
|
||||
widget=AirYearPickerInput(clear_button=False), label="", required=False
|
||||
)
|
||||
year_to = forms.DateField(
|
||||
widget=AirYearPickerInput(clear_button=False), label="", required=False
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_tag = False
|
||||
self.helper.disable_csrf = True
|
||||
|
||||
self.helper.layout = Layout(
|
||||
Row(
|
||||
Column("year_from", css_class="form-group col-md-6"),
|
||||
Column("year_to", css_class="form-group col-md-6"),
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class DateRangeForm(forms.Form):
|
||||
date_from = forms.DateField(
|
||||
widget=AirDatePickerInput(clear_button=False), label="", required=False
|
||||
)
|
||||
date_to = forms.DateField(
|
||||
widget=AirDatePickerInput(clear_button=False), label="", required=False
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
|
||||
self.helper = FormHelper()
|
||||
self.helper.form_tag = False
|
||||
self.helper.disable_csrf = True
|
||||
|
||||
self.helper.layout = Layout(
|
||||
Row(
|
||||
Column("date_from", css_class="form-group col-md-6"),
|
||||
Column("date_to", css_class="form-group col-md-6"),
|
||||
css_class="mb-0",
|
||||
),
|
||||
)
|
||||
0
app/apps/insights/migrations/__init__.py
Normal file
0
app/apps/insights/migrations/__init__.py
Normal file
3
app/apps/insights/models.py
Normal file
3
app/apps/insights/models.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
3
app/apps/insights/tests.py
Normal file
3
app/apps/insights/tests.py
Normal file
@@ -0,0 +1,3 @@
|
||||
from django.test import TestCase
|
||||
|
||||
# Create your tests here.
|
||||
17
app/apps/insights/urls.py
Normal file
17
app/apps/insights/urls.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from django.urls import path
|
||||
|
||||
from . import views
|
||||
|
||||
urlpatterns = [
|
||||
path("insights/", views.index, name="insights_index"),
|
||||
path(
|
||||
"insights/sankey/account/",
|
||||
views.sankey_by_account,
|
||||
name="insights_sankey_by_account",
|
||||
),
|
||||
path(
|
||||
"insights/sankey/currency/",
|
||||
views.sankey_by_currency,
|
||||
name="insights_sankey_by_currency",
|
||||
),
|
||||
]
|
||||
0
app/apps/insights/utils/__init__.py
Normal file
0
app/apps/insights/utils/__init__.py
Normal file
248
app/apps/insights/utils/sankey.py
Normal file
248
app/apps/insights/utils/sankey.py
Normal file
@@ -0,0 +1,248 @@
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from decimal import Decimal
|
||||
from typing import Dict, List, TypedDict
|
||||
|
||||
|
||||
class SankeyNode(TypedDict):
|
||||
name: str
|
||||
|
||||
|
||||
class SankeyFlow(TypedDict):
|
||||
from_node: str
|
||||
to_node: str
|
||||
flow: float
|
||||
currency: Dict
|
||||
original_amount: float
|
||||
percentage: float
|
||||
|
||||
|
||||
def generate_sankey_data_by_account(transactions_queryset):
|
||||
"""
|
||||
Generates Sankey diagram data from transaction queryset using account as intermediary.
|
||||
"""
|
||||
nodes: Dict[str, Dict] = {}
|
||||
flows: List[SankeyFlow] = []
|
||||
|
||||
# Aggregate transactions
|
||||
income_data = {} # {(category, currency, account) -> amount}
|
||||
expense_data = {} # {(category, currency, account) -> amount}
|
||||
total_income_by_currency = {} # {currency -> amount}
|
||||
total_expense_by_currency = {} # {currency -> amount}
|
||||
total_volume_by_currency = {} # {currency -> amount}
|
||||
|
||||
for transaction in transactions_queryset:
|
||||
currency = transaction.account.currency
|
||||
account = transaction.account
|
||||
category = transaction.category or _("Uncategorized")
|
||||
key = (category, currency, account)
|
||||
amount = transaction.amount
|
||||
|
||||
if transaction.type == "IN":
|
||||
income_data[key] = income_data.get(key, Decimal("0")) + amount
|
||||
total_income_by_currency[currency] = (
|
||||
total_income_by_currency.get(currency, Decimal("0")) + amount
|
||||
)
|
||||
else:
|
||||
expense_data[key] = expense_data.get(key, Decimal("0")) + amount
|
||||
total_expense_by_currency[currency] = (
|
||||
total_expense_by_currency.get(currency, Decimal("0")) + amount
|
||||
)
|
||||
|
||||
total_volume_by_currency[currency] = (
|
||||
total_volume_by_currency.get(currency, Decimal("0")) + amount
|
||||
)
|
||||
|
||||
def get_node_id(node_type: str, name: str, account_id: int) -> str:
|
||||
"""Generate unique node ID."""
|
||||
return f"{node_type}_{name}_{account_id}".lower().replace(" ", "_")
|
||||
|
||||
def add_node(node_id: str, display_name: str) -> None:
|
||||
"""Add node with both ID and display name."""
|
||||
nodes[node_id] = {"id": node_id, "name": display_name}
|
||||
|
||||
def add_flow(
|
||||
from_node_id: str, to_node_id: str, amount: Decimal, currency, is_income: bool
|
||||
) -> None:
|
||||
"""
|
||||
Add flow with percentage based on total transaction volume for the specific currency.
|
||||
"""
|
||||
total_volume = total_volume_by_currency.get(currency, Decimal("0"))
|
||||
percentage = (amount / total_volume) * 100 if total_volume else 0
|
||||
scaled_flow = percentage / 100
|
||||
|
||||
flows.append(
|
||||
{
|
||||
"from_node": from_node_id,
|
||||
"to_node": to_node_id,
|
||||
"flow": float(scaled_flow),
|
||||
"currency": {
|
||||
"code": currency.code,
|
||||
"prefix": currency.prefix,
|
||||
"suffix": currency.suffix,
|
||||
"decimal_places": currency.decimal_places,
|
||||
},
|
||||
"original_amount": float(amount),
|
||||
"percentage": float(percentage),
|
||||
}
|
||||
)
|
||||
|
||||
# Process income
|
||||
for (category, currency, account), amount in income_data.items():
|
||||
category_node_id = get_node_id("income", category, account.id)
|
||||
account_node_id = get_node_id("account", account.name, account.id)
|
||||
add_node(category_node_id, str(category))
|
||||
add_node(account_node_id, account.name)
|
||||
add_flow(category_node_id, account_node_id, amount, currency, is_income=True)
|
||||
|
||||
# Process expenses
|
||||
for (category, currency, account), amount in expense_data.items():
|
||||
category_node_id = get_node_id("expense", category, account.id)
|
||||
account_node_id = get_node_id("account", account.name, account.id)
|
||||
add_node(category_node_id, str(category))
|
||||
add_node(account_node_id, account.name)
|
||||
add_flow(account_node_id, category_node_id, amount, currency, is_income=False)
|
||||
|
||||
# Calculate and add savings flows
|
||||
savings_data = {} # {(account, currency) -> amount}
|
||||
for (category, currency, account), amount in income_data.items():
|
||||
key = (account, currency)
|
||||
savings_data[key] = savings_data.get(key, Decimal("0")) + amount
|
||||
for (category, currency, account), amount in expense_data.items():
|
||||
key = (account, currency)
|
||||
savings_data[key] = savings_data.get(key, Decimal("0")) - amount
|
||||
|
||||
for (account, currency), amount in savings_data.items():
|
||||
if amount > 0:
|
||||
account_node_id = get_node_id("account", account.name, account.id)
|
||||
savings_node_id = get_node_id("savings", _("Saved"), account.id)
|
||||
add_node(savings_node_id, str(_("Saved")))
|
||||
add_flow(account_node_id, savings_node_id, amount, currency, is_income=True)
|
||||
|
||||
# Calculate total across all currencies (for reference only)
|
||||
total_amount = sum(float(amount) for amount in total_income_by_currency.values())
|
||||
|
||||
return {
|
||||
"nodes": list(nodes.values()),
|
||||
"flows": flows,
|
||||
"total_amount": total_amount,
|
||||
"total_by_currency": {
|
||||
curr.code: float(amount)
|
||||
for curr, amount in total_income_by_currency.items()
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def generate_sankey_data_by_currency(transactions_queryset):
|
||||
"""
|
||||
Generates Sankey diagram data from transaction queryset, using currency as intermediary.
|
||||
"""
|
||||
nodes: Dict[str, Dict] = {}
|
||||
flows: List[SankeyFlow] = []
|
||||
|
||||
# Aggregate transactions
|
||||
income_data = {} # {(category, currency) -> amount}
|
||||
expense_data = {} # {(category, currency) -> amount}
|
||||
total_income_by_currency = {} # {currency -> amount}
|
||||
total_expense_by_currency = {} # {currency -> amount}
|
||||
total_volume_by_currency = {} # {currency -> amount}
|
||||
|
||||
for transaction in transactions_queryset:
|
||||
currency = transaction.account.currency
|
||||
category = transaction.category or _("Uncategorized")
|
||||
key = (category, currency)
|
||||
amount = transaction.amount
|
||||
|
||||
if transaction.type == "IN":
|
||||
income_data[key] = income_data.get(key, Decimal("0")) + amount
|
||||
total_income_by_currency[currency] = (
|
||||
total_income_by_currency.get(currency, Decimal("0")) + amount
|
||||
)
|
||||
else:
|
||||
expense_data[key] = expense_data.get(key, Decimal("0")) + amount
|
||||
total_expense_by_currency[currency] = (
|
||||
total_expense_by_currency.get(currency, Decimal("0")) + amount
|
||||
)
|
||||
|
||||
total_volume_by_currency[currency] = (
|
||||
total_volume_by_currency.get(currency, Decimal("0")) + amount
|
||||
)
|
||||
|
||||
def get_node_id(node_type: str, name: str, currency_id: int) -> str:
|
||||
"""Generate unique node ID including currency information."""
|
||||
return f"{node_type}_{name}_{currency_id}".lower().replace(" ", "_")
|
||||
|
||||
def add_node(node_id: str, display_name: str) -> None:
|
||||
"""Add node with both ID and display name."""
|
||||
nodes[node_id] = {"id": node_id, "name": display_name}
|
||||
|
||||
def add_flow(
|
||||
from_node_id: str, to_node_id: str, amount: Decimal, currency, is_income: bool
|
||||
) -> None:
|
||||
"""
|
||||
Add flow with percentage based on total transaction volume for the specific currency.
|
||||
"""
|
||||
total_volume = total_volume_by_currency.get(currency, Decimal("0"))
|
||||
percentage = (amount / total_volume) * 100 if total_volume else 0
|
||||
scaled_flow = percentage / 100
|
||||
|
||||
flows.append(
|
||||
{
|
||||
"from_node": from_node_id,
|
||||
"to_node": to_node_id,
|
||||
"flow": float(scaled_flow),
|
||||
"currency": {
|
||||
"code": currency.code,
|
||||
"name": currency.name,
|
||||
"prefix": currency.prefix,
|
||||
"suffix": currency.suffix,
|
||||
"decimal_places": currency.decimal_places,
|
||||
},
|
||||
"original_amount": float(amount),
|
||||
"percentage": float(percentage),
|
||||
}
|
||||
)
|
||||
|
||||
# Process income
|
||||
for (category, currency), amount in income_data.items():
|
||||
category_node_id = get_node_id("income", category, currency.id)
|
||||
currency_node_id = get_node_id("currency", currency.name, currency.id)
|
||||
add_node(category_node_id, str(category))
|
||||
add_node(currency_node_id, currency.name)
|
||||
add_flow(category_node_id, currency_node_id, amount, currency, is_income=True)
|
||||
|
||||
# Process expenses
|
||||
for (category, currency), amount in expense_data.items():
|
||||
category_node_id = get_node_id("expense", category, currency.id)
|
||||
currency_node_id = get_node_id("currency", currency.name, currency.id)
|
||||
add_node(category_node_id, str(category))
|
||||
add_node(currency_node_id, currency.name)
|
||||
add_flow(currency_node_id, category_node_id, amount, currency, is_income=False)
|
||||
|
||||
# Calculate and add savings flows
|
||||
savings_data = {} # {currency -> amount}
|
||||
for (category, currency), amount in income_data.items():
|
||||
savings_data[currency] = savings_data.get(currency, Decimal("0")) + amount
|
||||
for (category, currency), amount in expense_data.items():
|
||||
savings_data[currency] = savings_data.get(currency, Decimal("0")) - amount
|
||||
|
||||
for currency, amount in savings_data.items():
|
||||
if amount > 0:
|
||||
currency_node_id = get_node_id("currency", currency.name, currency.id)
|
||||
savings_node_id = get_node_id("savings", _("Saved"), currency.id)
|
||||
add_node(savings_node_id, str(_("Saved")))
|
||||
add_flow(
|
||||
currency_node_id, savings_node_id, amount, currency, is_income=True
|
||||
)
|
||||
|
||||
# Calculate total across all currencies (for reference only)
|
||||
total_amount = sum(float(amount) for amount in total_income_by_currency.values())
|
||||
|
||||
return {
|
||||
"nodes": list(nodes.values()),
|
||||
"flows": flows,
|
||||
"total_amount": total_amount,
|
||||
"total_by_currency": {
|
||||
curr.name: float(amount)
|
||||
for curr, amount in total_income_by_currency.items()
|
||||
},
|
||||
}
|
||||
96
app/apps/insights/utils/transactions.py
Normal file
96
app/apps/insights/utils/transactions.py
Normal file
@@ -0,0 +1,96 @@
|
||||
from django.db.models import Q
|
||||
from django.utils import timezone
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from apps.transactions.models import Transaction
|
||||
from apps.insights.forms import (
|
||||
SingleMonthForm,
|
||||
SingleYearForm,
|
||||
MonthRangeForm,
|
||||
YearRangeForm,
|
||||
DateRangeForm,
|
||||
)
|
||||
|
||||
|
||||
def get_transactions(request, include_unpaid=True, include_silent=False):
|
||||
transactions = Transaction.objects.all()
|
||||
|
||||
filter_type = request.GET.get("type", None)
|
||||
|
||||
if filter_type is not None:
|
||||
if filter_type == "month":
|
||||
form = SingleMonthForm(request.GET)
|
||||
|
||||
if form.is_valid():
|
||||
month = form.cleaned_data["month"].replace(day=1)
|
||||
else:
|
||||
month = timezone.localdate(timezone.now()).replace(day=1)
|
||||
|
||||
transactions = transactions.filter(
|
||||
reference_date__month=month.month, reference_date__year=month.year
|
||||
)
|
||||
elif filter_type == "year":
|
||||
form = SingleYearForm(request.GET)
|
||||
if form.is_valid():
|
||||
year = form.cleaned_data["year"].replace(day=1, month=1)
|
||||
else:
|
||||
year = timezone.localdate(timezone.now()).replace(day=1, month=1)
|
||||
|
||||
transactions = transactions.filter(reference_date__year=year.year)
|
||||
elif filter_type == "month-range":
|
||||
form = MonthRangeForm(request.GET)
|
||||
if form.is_valid():
|
||||
month_from = form.cleaned_data["month_from"].replace(day=1)
|
||||
month_to = form.cleaned_data["month_to"].replace(day=1)
|
||||
else:
|
||||
month_from = timezone.localdate(timezone.now()).replace(day=1)
|
||||
month_to = (
|
||||
timezone.localdate(timezone.now()) + relativedelta(months=1)
|
||||
).replace(day=1)
|
||||
|
||||
transactions = transactions.filter(
|
||||
reference_date__gte=month_from,
|
||||
reference_date__lte=month_to,
|
||||
)
|
||||
elif filter_type == "year-range":
|
||||
form = YearRangeForm(request.GET)
|
||||
if form.is_valid():
|
||||
year_from = form.cleaned_data["year_from"].replace(day=1, month=1)
|
||||
year_to = form.cleaned_data["year_to"].replace(day=31, month=12)
|
||||
else:
|
||||
year_from = timezone.localdate(timezone.now()).replace(day=1, month=1)
|
||||
year_to = (
|
||||
timezone.localdate(timezone.now()) + relativedelta(years=1)
|
||||
).replace(day=31, month=12)
|
||||
|
||||
transactions = transactions.filter(
|
||||
reference_date__gte=year_from,
|
||||
reference_date__lte=year_to,
|
||||
)
|
||||
elif filter_type == "date-range":
|
||||
form = DateRangeForm(request.GET)
|
||||
if form.is_valid():
|
||||
date_from = form.cleaned_data["date_from"]
|
||||
date_to = form.cleaned_data["date_to"]
|
||||
else:
|
||||
date_from = timezone.localdate(timezone.now())
|
||||
date_to = timezone.localdate(timezone.now()) + relativedelta(months=1)
|
||||
|
||||
transactions = transactions.filter(
|
||||
date__gte=date_from,
|
||||
date__lte=date_to,
|
||||
)
|
||||
else: # Default to current month
|
||||
month = timezone.localdate(timezone.now())
|
||||
transactions = transactions.filter(
|
||||
reference_date__month=month.month, reference_date__year=month.year
|
||||
)
|
||||
|
||||
if not include_unpaid:
|
||||
transactions = transactions.filter(is_paid=True)
|
||||
|
||||
if not include_silent:
|
||||
transactions = transactions.exclude(Q(category__mute=True) & ~Q(category=None))
|
||||
|
||||
return transactions
|
||||
94
app/apps/insights/views.py
Normal file
94
app/apps/insights/views.py
Normal file
@@ -0,0 +1,94 @@
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.shortcuts import render
|
||||
from django.utils import timezone
|
||||
from django.views.decorators.http import require_http_methods
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
|
||||
from apps.transactions.models import Transaction
|
||||
from apps.insights.utils.sankey import (
|
||||
generate_sankey_data_by_account,
|
||||
generate_sankey_data_by_currency,
|
||||
)
|
||||
from apps.insights.forms import (
|
||||
SingleMonthForm,
|
||||
SingleYearForm,
|
||||
MonthRangeForm,
|
||||
YearRangeForm,
|
||||
DateRangeForm,
|
||||
)
|
||||
from apps.common.decorators.htmx import only_htmx
|
||||
from apps.insights.utils.transactions import get_transactions
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def index(request):
|
||||
date = timezone.localdate(timezone.now())
|
||||
month_form = SingleMonthForm(initial={"month": date.replace(day=1)})
|
||||
year_form = SingleYearForm(initial={"year": date.replace(day=1)})
|
||||
month_range_form = MonthRangeForm(
|
||||
initial={
|
||||
"month_from": date.replace(day=1),
|
||||
"month_to": date.replace(day=1) + relativedelta(months=1),
|
||||
}
|
||||
)
|
||||
year_range_form = YearRangeForm(
|
||||
initial={
|
||||
"year_from": date.replace(day=1, month=1),
|
||||
"year_to": date.replace(day=1, month=1) + relativedelta(years=1),
|
||||
}
|
||||
)
|
||||
date_range_form = DateRangeForm(
|
||||
initial={
|
||||
"date_from": date,
|
||||
"date_to": date + relativedelta(months=1),
|
||||
}
|
||||
)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"insights/pages/index.html",
|
||||
context={
|
||||
"month_form": month_form,
|
||||
"year_form": year_form,
|
||||
"month_range_form": month_range_form,
|
||||
"year_range_form": year_range_form,
|
||||
"date_range_form": date_range_form,
|
||||
},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def sankey_by_account(request):
|
||||
# Get filtered transactions
|
||||
|
||||
transactions = get_transactions(request)
|
||||
|
||||
# Generate Sankey data
|
||||
sankey_data = generate_sankey_data_by_account(transactions)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"insights/fragments/sankey.html",
|
||||
{"sankey_data": sankey_data, "type": "account"},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET", "POST"])
|
||||
def sankey_by_currency(request):
|
||||
# Get filtered transactions
|
||||
transactions = get_transactions(request)
|
||||
|
||||
# Generate Sankey data
|
||||
sankey_data = generate_sankey_data_by_currency(transactions)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"insights/fragments/sankey.html",
|
||||
{"sankey_data": sankey_data, "type": "currency"},
|
||||
)
|
||||
@@ -92,6 +92,8 @@ def transactions_list(request, month: int, year: int):
|
||||
"account__currency",
|
||||
"installment_plan",
|
||||
"entities",
|
||||
"dca_expense_entries",
|
||||
"dca_income_entries",
|
||||
)
|
||||
)
|
||||
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
from django.dispatch import Signal, receiver
|
||||
from django.dispatch import receiver
|
||||
|
||||
from apps.transactions.models import Transaction
|
||||
from apps.transactions.models import (
|
||||
Transaction,
|
||||
transaction_created,
|
||||
transaction_updated,
|
||||
)
|
||||
from apps.rules.tasks import check_for_transaction_rules
|
||||
|
||||
transaction_created = Signal()
|
||||
transaction_updated = Signal()
|
||||
|
||||
|
||||
@receiver(transaction_created)
|
||||
@receiver(transaction_updated)
|
||||
def transaction_changed_receiver(sender: Transaction, signal, **kwargs):
|
||||
for dca_entry in sender.dca_expense_entries.all():
|
||||
dca_entry.amount_paid = sender.amount
|
||||
dca_entry.save()
|
||||
for dca_entry in sender.dca_income_entries.all():
|
||||
dca_entry.amount_received = sender.amount
|
||||
dca_entry.save()
|
||||
|
||||
check_for_transaction_rules.defer(
|
||||
instance_id=sender.id,
|
||||
signal=(
|
||||
|
||||
@@ -1,22 +1,66 @@
|
||||
import logging
|
||||
|
||||
from dateutil.relativedelta import relativedelta
|
||||
from django.conf import settings
|
||||
from django.core.validators import MinValueValidator
|
||||
from django.db import models, transaction
|
||||
from django.db.models import Q
|
||||
from django.dispatch import Signal
|
||||
from django.template.defaultfilters import date
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
from django.conf import settings
|
||||
|
||||
from apps.common.fields.month_year import MonthYearModelField
|
||||
from apps.common.functions.decimals import truncate_decimal
|
||||
from apps.common.templatetags.decimal import localize_number, drop_trailing_zeros
|
||||
from apps.currencies.utils.convert import convert
|
||||
from apps.transactions.validators import validate_decimal_places, validate_non_negative
|
||||
|
||||
logger = logging.getLogger()
|
||||
|
||||
|
||||
transaction_created = Signal()
|
||||
transaction_updated = Signal()
|
||||
|
||||
|
||||
class SoftDeleteQuerySet(models.QuerySet):
|
||||
@staticmethod
|
||||
def _emit_signals(instances, created=False):
|
||||
"""Helper to emit signals for multiple instances"""
|
||||
for instance in instances:
|
||||
if created:
|
||||
transaction_created.send(sender=instance)
|
||||
else:
|
||||
transaction_updated.send(sender=instance)
|
||||
|
||||
def bulk_create(self, objs, emit_signal=True, **kwargs):
|
||||
instances = super().bulk_create(objs, **kwargs)
|
||||
|
||||
if emit_signal:
|
||||
self._emit_signals(instances, created=True)
|
||||
|
||||
return instances
|
||||
|
||||
def bulk_update(self, objs, fields, emit_signal=True, **kwargs):
|
||||
result = super().bulk_update(objs, fields, **kwargs)
|
||||
|
||||
if emit_signal:
|
||||
self._emit_signals(objs, created=False)
|
||||
|
||||
return result
|
||||
|
||||
def update(self, emit_signal=True, **kwargs):
|
||||
# Get instances before update
|
||||
instances = list(self)
|
||||
result = super().update(**kwargs)
|
||||
|
||||
if emit_signal:
|
||||
# Refresh instances to get new values
|
||||
refreshed = self.model.objects.filter(pk__in=[obj.pk for obj in instances])
|
||||
self._emit_signals(refreshed, created=False)
|
||||
|
||||
return result
|
||||
|
||||
def delete(self):
|
||||
if not settings.ENABLE_SOFT_DELETE:
|
||||
# If soft deletion is disabled, perform a normal delete
|
||||
@@ -274,7 +318,13 @@ class Transaction(models.Model):
|
||||
|
||||
def __str__(self):
|
||||
type_display = self.get_type_display()
|
||||
return f"{self.description} - {type_display} - {self.account} - {self.date}"
|
||||
frmt_date = date(self.date, "SHORT_DATE_FORMAT")
|
||||
account = self.account
|
||||
tags = ", ".join([x.name for x in self.tags.all()]) or _("No tags")
|
||||
category = self.category or _("No category")
|
||||
amount = localize_number(drop_trailing_zeros(self.amount))
|
||||
description = self.description or _("No description")
|
||||
return f"[{frmt_date}][{type_display}][{account}] {description} • {category} • {tags} • {amount}"
|
||||
|
||||
|
||||
class InstallmentPlan(models.Model):
|
||||
|
||||
@@ -86,6 +86,16 @@ urlpatterns = [
|
||||
views.transactions_bulk_edit,
|
||||
name="transactions_bulk_edit",
|
||||
),
|
||||
path(
|
||||
"transactions/json/search/",
|
||||
views.get_recent_transactions,
|
||||
name="transactions_search",
|
||||
),
|
||||
path(
|
||||
"transactions/json/search/<str:filter_type>/",
|
||||
views.get_recent_transactions,
|
||||
name="transactions_search",
|
||||
),
|
||||
path(
|
||||
"transaction/<int:transaction_id>/clone/",
|
||||
views.transaction_clone,
|
||||
|
||||
@@ -7,6 +7,7 @@ from django.utils.translation import gettext_lazy as _, ngettext_lazy
|
||||
|
||||
from apps.common.decorators.htmx import only_htmx
|
||||
from apps.transactions.models import Transaction
|
||||
from apps.rules.signals import transaction_updated
|
||||
|
||||
|
||||
@only_htmx
|
||||
@@ -87,7 +88,7 @@ def bulk_undelete_transactions(request):
|
||||
selected_transactions = request.GET.getlist("transactions", [])
|
||||
transactions = Transaction.deleted_objects.filter(id__in=selected_transactions)
|
||||
count = transactions.count()
|
||||
transactions.update(deleted=False, deleted_at=None)
|
||||
transactions.update(deleted=False, deleted_at=None, emit_signal=False)
|
||||
|
||||
messages.success(
|
||||
request,
|
||||
|
||||
@@ -4,14 +4,14 @@ from copy import deepcopy
|
||||
from django.contrib import messages
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.core.paginator import Paginator
|
||||
from django.http import HttpResponse
|
||||
from django.db.models import Q
|
||||
from django.http import HttpResponse, JsonResponse
|
||||
from django.shortcuts import render, get_object_or_404
|
||||
from django.utils import timezone
|
||||
from django.utils.translation import gettext_lazy as _, ngettext_lazy
|
||||
from django.views.decorators.http import require_http_methods
|
||||
|
||||
from apps.common.decorators.htmx import only_htmx
|
||||
from apps.common.utils.dicts import remove_falsey_entries
|
||||
from apps.rules.signals import transaction_created, transaction_updated
|
||||
from apps.transactions.filters import TransactionsFilter
|
||||
from apps.transactions.forms import (
|
||||
@@ -316,6 +316,7 @@ def transaction_pay(request, transaction_id):
|
||||
new_is_paid = False if transaction.is_paid else True
|
||||
transaction.is_paid = new_is_paid
|
||||
transaction.save()
|
||||
transaction_updated.send(sender=transaction)
|
||||
|
||||
response = render(
|
||||
request,
|
||||
@@ -362,6 +363,8 @@ def transaction_all_list(request):
|
||||
"account__currency",
|
||||
"installment_plan",
|
||||
"entities",
|
||||
"dca_expense_entries",
|
||||
"dca_income_entries",
|
||||
).all()
|
||||
|
||||
transactions = default_order(transactions, order=order)
|
||||
@@ -394,6 +397,9 @@ def transaction_all_summary(request):
|
||||
"account__exchange_currency",
|
||||
"account__currency",
|
||||
"installment_plan",
|
||||
"entities",
|
||||
"dca_expense_entries",
|
||||
"dca_income_entries",
|
||||
).all()
|
||||
|
||||
f = TransactionsFilter(request.GET, queryset=transactions)
|
||||
@@ -425,6 +431,9 @@ def transaction_all_account_summary(request):
|
||||
"account__exchange_currency",
|
||||
"account__currency",
|
||||
"installment_plan",
|
||||
"entities",
|
||||
"dca_expense_entries",
|
||||
"dca_income_entries",
|
||||
).all()
|
||||
|
||||
f = TransactionsFilter(request.GET, queryset=transactions)
|
||||
@@ -452,6 +461,9 @@ def transaction_all_currency_summary(request):
|
||||
"account__exchange_currency",
|
||||
"account__currency",
|
||||
"installment_plan",
|
||||
"entities",
|
||||
"dca_expense_entries",
|
||||
"dca_income_entries",
|
||||
).all()
|
||||
|
||||
f = TransactionsFilter(request.GET, queryset=transactions)
|
||||
@@ -483,6 +495,9 @@ def transactions_trash_can_index(request):
|
||||
return render(request, "transactions/pages/trash.html")
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def transactions_trash_can_list(request):
|
||||
transactions = Transaction.deleted_objects.prefetch_related(
|
||||
"account",
|
||||
@@ -492,6 +507,10 @@ def transactions_trash_can_list(request):
|
||||
"account__exchange_currency",
|
||||
"account__currency",
|
||||
"installment_plan",
|
||||
"entities",
|
||||
"entities",
|
||||
"dca_expense_entries",
|
||||
"dca_income_entries",
|
||||
).all()
|
||||
|
||||
return render(
|
||||
@@ -499,3 +518,41 @@ def transactions_trash_can_list(request):
|
||||
"transactions/fragments/trash_list.html",
|
||||
{"transactions": transactions},
|
||||
)
|
||||
|
||||
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def get_recent_transactions(request, filter_type=None):
|
||||
"""Return the 100 most recent non-deleted transactions with optional search."""
|
||||
# Get search term from query params
|
||||
search_term = request.GET.get("q", "").strip()
|
||||
|
||||
# Base queryset with selected fields
|
||||
queryset = (
|
||||
Transaction.objects.filter(deleted=False)
|
||||
.select_related("account", "category")
|
||||
.order_by("-created_at")
|
||||
)
|
||||
|
||||
if filter_type:
|
||||
if filter_type == "expenses":
|
||||
queryset = queryset.filter(type=Transaction.Type.EXPENSE)
|
||||
elif filter_type == "income":
|
||||
queryset = queryset.filter(type=Transaction.Type.INCOME)
|
||||
|
||||
# Apply search if provided
|
||||
if search_term:
|
||||
queryset = queryset.filter(
|
||||
Q(description__icontains=search_term)
|
||||
| Q(notes__icontains=search_term)
|
||||
| Q(internal_note__icontains=search_term)
|
||||
| Q(tags__name__icontains=search_term)
|
||||
| Q(category__name__icontains=search_term)
|
||||
)
|
||||
|
||||
# Prepare data for JSON response
|
||||
data = []
|
||||
for t in queryset:
|
||||
data.append({"text": str(t), "value": str(t.id)})
|
||||
|
||||
return JsonResponse(data, safe=False)
|
||||
|
||||
@@ -2,13 +2,13 @@
|
||||
# Copyright (C) YEAR THE PACKAGE'S COPYRIGHT HOLDER
|
||||
# This file is distributed under the same license as the PACKAGE package.
|
||||
# FIRST AUTHOR <EMAIL@ADDRESS>, YEAR.
|
||||
#
|
||||
#
|
||||
#, fuzzy
|
||||
msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: PACKAGE VERSION\n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-02-09 17:27-0300\n"
|
||||
"POT-Creation-Date: 2025-02-16 00:03-0300\n"
|
||||
"PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
|
||||
"Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
|
||||
"Language-Team: LANGUAGE <LL@li.org>\n"
|
||||
@@ -24,7 +24,7 @@ msgstr ""
|
||||
|
||||
#: apps/accounts/forms.py:40 apps/accounts/forms.py:96
|
||||
#: apps/currencies/forms.py:53 apps/currencies/forms.py:91
|
||||
#: apps/currencies/forms.py:142 apps/dca/forms.py:41 apps/dca/forms.py:93
|
||||
#: apps/currencies/forms.py:142 apps/dca/forms.py:49 apps/dca/forms.py:224
|
||||
#: apps/import_app/forms.py:34 apps/rules/forms.py:45 apps/rules/forms.py:87
|
||||
#: apps/rules/forms.py:359 apps/transactions/forms.py:190
|
||||
#: apps/transactions/forms.py:257 apps/transactions/forms.py:581
|
||||
@@ -34,9 +34,9 @@ msgid "Update"
|
||||
msgstr ""
|
||||
|
||||
#: apps/accounts/forms.py:48 apps/accounts/forms.py:104
|
||||
#: apps/common/widgets/tom_select.py:12 apps/currencies/forms.py:61
|
||||
#: apps/common/widgets/tom_select.py:13 apps/currencies/forms.py:61
|
||||
#: apps/currencies/forms.py:99 apps/currencies/forms.py:150
|
||||
#: apps/dca/forms.py:49 apps/dca/forms.py:102 apps/import_app/forms.py:42
|
||||
#: apps/dca/forms.py:57 apps/dca/forms.py:232 apps/import_app/forms.py:42
|
||||
#: apps/rules/forms.py:53 apps/rules/forms.py:95 apps/rules/forms.py:367
|
||||
#: apps/transactions/forms.py:174 apps/transactions/forms.py:199
|
||||
#: apps/transactions/forms.py:589 apps/transactions/forms.py:632
|
||||
@@ -68,30 +68,32 @@ msgstr ""
|
||||
msgid "New balance"
|
||||
msgstr ""
|
||||
|
||||
#: apps/accounts/forms.py:119 apps/rules/forms.py:168 apps/rules/forms.py:183
|
||||
#: apps/rules/models.py:32 apps/rules/models.py:280
|
||||
#: apps/transactions/forms.py:39 apps/transactions/forms.py:291
|
||||
#: apps/transactions/forms.py:298 apps/transactions/forms.py:478
|
||||
#: apps/transactions/forms.py:723 apps/transactions/models.py:159
|
||||
#: apps/transactions/models.py:328 apps/transactions/models.py:508
|
||||
#: apps/accounts/forms.py:119 apps/dca/forms.py:85 apps/dca/forms.py:92
|
||||
#: apps/rules/forms.py:168 apps/rules/forms.py:183 apps/rules/models.py:32
|
||||
#: apps/rules/models.py:280 apps/transactions/forms.py:39
|
||||
#: apps/transactions/forms.py:291 apps/transactions/forms.py:298
|
||||
#: apps/transactions/forms.py:478 apps/transactions/forms.py:723
|
||||
#: apps/transactions/models.py:203 apps/transactions/models.py:378
|
||||
#: apps/transactions/models.py:558
|
||||
msgid "Category"
|
||||
msgstr ""
|
||||
|
||||
#: apps/accounts/forms.py:126 apps/rules/forms.py:171 apps/rules/forms.py:180
|
||||
#: apps/rules/models.py:33 apps/rules/models.py:284
|
||||
#: apps/transactions/filters.py:74 apps/transactions/forms.py:47
|
||||
#: apps/transactions/forms.py:307 apps/transactions/forms.py:315
|
||||
#: apps/transactions/forms.py:471 apps/transactions/forms.py:716
|
||||
#: apps/transactions/models.py:165 apps/transactions/models.py:330
|
||||
#: apps/transactions/models.py:512 templates/includes/navbar.html:105
|
||||
#: templates/tags/fragments/list.html:5 templates/tags/pages/index.html:4
|
||||
#: apps/accounts/forms.py:126 apps/dca/forms.py:101 apps/dca/forms.py:109
|
||||
#: apps/rules/forms.py:171 apps/rules/forms.py:180 apps/rules/models.py:33
|
||||
#: apps/rules/models.py:284 apps/transactions/filters.py:74
|
||||
#: apps/transactions/forms.py:47 apps/transactions/forms.py:307
|
||||
#: apps/transactions/forms.py:315 apps/transactions/forms.py:471
|
||||
#: apps/transactions/forms.py:716 apps/transactions/models.py:209
|
||||
#: apps/transactions/models.py:380 apps/transactions/models.py:562
|
||||
#: templates/includes/navbar.html:108 templates/tags/fragments/list.html:5
|
||||
#: templates/tags/pages/index.html:4
|
||||
msgid "Tags"
|
||||
msgstr ""
|
||||
|
||||
#: apps/accounts/models.py:9 apps/accounts/models.py:21 apps/dca/models.py:14
|
||||
#: apps/import_app/models.py:14 apps/rules/models.py:10
|
||||
#: apps/transactions/models.py:67 apps/transactions/models.py:87
|
||||
#: apps/transactions/models.py:106
|
||||
#: apps/transactions/models.py:111 apps/transactions/models.py:131
|
||||
#: apps/transactions/models.py:150
|
||||
#: templates/account_groups/fragments/list.html:25
|
||||
#: templates/accounts/fragments/list.html:25
|
||||
#: templates/categories/fragments/table.html:16
|
||||
@@ -112,7 +114,7 @@ msgstr ""
|
||||
|
||||
#: apps/accounts/models.py:13 templates/account_groups/fragments/list.html:5
|
||||
#: templates/account_groups/pages/index.html:4
|
||||
#: templates/includes/navbar.html:115
|
||||
#: templates/includes/navbar.html:118
|
||||
msgid "Account Groups"
|
||||
msgstr ""
|
||||
|
||||
@@ -153,15 +155,15 @@ msgstr ""
|
||||
#: apps/accounts/models.py:59 apps/rules/forms.py:160 apps/rules/forms.py:173
|
||||
#: apps/rules/models.py:24 apps/rules/models.py:236
|
||||
#: apps/transactions/forms.py:59 apps/transactions/forms.py:463
|
||||
#: apps/transactions/forms.py:708 apps/transactions/models.py:132
|
||||
#: apps/transactions/models.py:288 apps/transactions/models.py:490
|
||||
#: apps/transactions/forms.py:708 apps/transactions/models.py:176
|
||||
#: apps/transactions/models.py:338 apps/transactions/models.py:540
|
||||
msgid "Account"
|
||||
msgstr ""
|
||||
|
||||
#: apps/accounts/models.py:60 apps/transactions/filters.py:53
|
||||
#: templates/accounts/fragments/list.html:5
|
||||
#: templates/accounts/pages/index.html:4 templates/includes/navbar.html:111
|
||||
#: templates/includes/navbar.html:113
|
||||
#: templates/accounts/pages/index.html:4 templates/includes/navbar.html:114
|
||||
#: templates/includes/navbar.html:116
|
||||
#: templates/monthly_overview/pages/overview.html:94
|
||||
#: templates/transactions/fragments/summary.html:12
|
||||
#: templates/transactions/pages/transactions.html:72
|
||||
@@ -232,13 +234,13 @@ msgstr ""
|
||||
msgid "Either 'date' or 'reference_date' must be provided."
|
||||
msgstr ""
|
||||
|
||||
#: apps/common/fields/forms/dynamic_select.py:127
|
||||
#: apps/common/fields/forms/dynamic_select.py:163
|
||||
#: apps/common/fields/forms/dynamic_select.py:131
|
||||
#: apps/common/fields/forms/dynamic_select.py:167
|
||||
msgid "Error creating new instance"
|
||||
msgstr ""
|
||||
|
||||
#: apps/common/fields/forms/grouped_select.py:24
|
||||
#: apps/common/widgets/tom_select.py:91 apps/common/widgets/tom_select.py:94
|
||||
#: apps/common/widgets/tom_select.py:92 apps/common/widgets/tom_select.py:95
|
||||
msgid "Ungrouped"
|
||||
msgstr ""
|
||||
|
||||
@@ -330,6 +332,7 @@ msgid "Cache cleared successfully"
|
||||
msgstr ""
|
||||
|
||||
#: apps/common/widgets/datepicker.py:47 apps/common/widgets/datepicker.py:186
|
||||
#: apps/common/widgets/datepicker.py:244
|
||||
msgid "Today"
|
||||
msgstr ""
|
||||
|
||||
@@ -337,18 +340,18 @@ msgstr ""
|
||||
msgid "Now"
|
||||
msgstr ""
|
||||
|
||||
#: apps/common/widgets/tom_select.py:10
|
||||
#: apps/common/widgets/tom_select.py:11
|
||||
msgid "Remove"
|
||||
msgstr ""
|
||||
|
||||
#: apps/common/widgets/tom_select.py:14
|
||||
#: apps/common/widgets/tom_select.py:15
|
||||
#: templates/mini_tools/unit_price_calculator.html:174
|
||||
#: templates/monthly_overview/pages/overview.html:172
|
||||
#: templates/transactions/pages/transactions.html:17
|
||||
msgid "Clear"
|
||||
msgstr ""
|
||||
|
||||
#: apps/common/widgets/tom_select.py:15
|
||||
#: apps/common/widgets/tom_select.py:16
|
||||
msgid "No results..."
|
||||
msgstr ""
|
||||
|
||||
@@ -363,7 +366,7 @@ msgstr ""
|
||||
#: apps/currencies/forms.py:69 apps/dca/models.py:156 apps/rules/forms.py:163
|
||||
#: apps/rules/forms.py:176 apps/rules/models.py:27 apps/rules/models.py:248
|
||||
#: apps/transactions/forms.py:63 apps/transactions/forms.py:319
|
||||
#: apps/transactions/models.py:142
|
||||
#: apps/transactions/models.py:186
|
||||
#: templates/dca/fragments/strategy/details.html:52
|
||||
#: templates/exchange_rates/fragments/table.html:10
|
||||
#: templates/exchange_rates_services/fragments/table.html:10
|
||||
@@ -384,8 +387,8 @@ msgstr ""
|
||||
|
||||
#: apps/currencies/models.py:40 apps/transactions/filters.py:60
|
||||
#: templates/currencies/fragments/list.html:5
|
||||
#: templates/currencies/pages/index.html:4 templates/includes/navbar.html:119
|
||||
#: templates/includes/navbar.html:121
|
||||
#: templates/currencies/pages/index.html:4 templates/includes/navbar.html:122
|
||||
#: templates/includes/navbar.html:124
|
||||
#: templates/monthly_overview/pages/overview.html:81
|
||||
#: templates/transactions/fragments/summary.html:8
|
||||
#: templates/transactions/pages/transactions.html:59
|
||||
@@ -414,7 +417,7 @@ msgstr ""
|
||||
|
||||
#: apps/currencies/models.py:74 templates/exchange_rates/fragments/list.html:6
|
||||
#: templates/exchange_rates/pages/index.html:4
|
||||
#: templates/includes/navbar.html:123
|
||||
#: templates/includes/navbar.html:126
|
||||
msgid "Exchange Rates"
|
||||
msgstr ""
|
||||
|
||||
@@ -442,8 +445,8 @@ msgstr ""
|
||||
msgid "Service Type"
|
||||
msgstr ""
|
||||
|
||||
#: apps/currencies/models.py:107 apps/transactions/models.py:71
|
||||
#: apps/transactions/models.py:90 apps/transactions/models.py:109
|
||||
#: apps/currencies/models.py:107 apps/transactions/models.py:115
|
||||
#: apps/transactions/models.py:134 apps/transactions/models.py:153
|
||||
#: templates/categories/fragments/list.html:21
|
||||
#: templates/entities/fragments/list.html:21
|
||||
#: templates/recurring_transactions/fragments/list.html:21
|
||||
@@ -559,6 +562,48 @@ msgstr ""
|
||||
msgid "Services queued successfully"
|
||||
msgstr ""
|
||||
|
||||
#: apps/dca/forms.py:65 apps/dca/forms.py:164
|
||||
msgid "Create transaction"
|
||||
msgstr ""
|
||||
|
||||
#: apps/dca/forms.py:70 apps/transactions/forms.py:266
|
||||
msgid "From Account"
|
||||
msgstr ""
|
||||
|
||||
#: apps/dca/forms.py:76 apps/transactions/forms.py:271
|
||||
msgid "To Account"
|
||||
msgstr ""
|
||||
|
||||
#: apps/dca/forms.py:116 apps/dca/models.py:169
|
||||
msgid "Expense Transaction"
|
||||
msgstr ""
|
||||
|
||||
#: apps/dca/forms.py:120 apps/dca/forms.py:130
|
||||
msgid "Type to search for a transaction to link to this entry"
|
||||
msgstr ""
|
||||
|
||||
#: apps/dca/forms.py:126 apps/dca/models.py:177
|
||||
msgid "Income Transaction"
|
||||
msgstr ""
|
||||
|
||||
#: apps/dca/forms.py:210
|
||||
msgid "Link transaction"
|
||||
msgstr ""
|
||||
|
||||
#: apps/dca/forms.py:279 apps/dca/forms.py:280 apps/dca/forms.py:285
|
||||
#: apps/dca/forms.py:289
|
||||
msgid "You must provide an account."
|
||||
msgstr ""
|
||||
|
||||
#: apps/dca/forms.py:294 apps/transactions/forms.py:413
|
||||
msgid "From and To accounts must be different."
|
||||
msgstr ""
|
||||
|
||||
#: apps/dca/forms.py:308
|
||||
#, python-format
|
||||
msgid "DCA for %(strategy_name)s"
|
||||
msgstr ""
|
||||
|
||||
#: apps/dca/models.py:17
|
||||
msgid "Target Currency"
|
||||
msgstr ""
|
||||
@@ -569,8 +614,8 @@ msgstr ""
|
||||
|
||||
#: apps/dca/models.py:27 apps/dca/models.py:179 apps/rules/forms.py:167
|
||||
#: apps/rules/forms.py:182 apps/rules/models.py:31 apps/rules/models.py:264
|
||||
#: apps/transactions/forms.py:333 apps/transactions/models.py:155
|
||||
#: apps/transactions/models.py:337 apps/transactions/models.py:518
|
||||
#: apps/transactions/forms.py:333 apps/transactions/models.py:199
|
||||
#: apps/transactions/models.py:387 apps/transactions/models.py:568
|
||||
msgid "Notes"
|
||||
msgstr ""
|
||||
|
||||
@@ -594,14 +639,6 @@ msgstr ""
|
||||
msgid "Amount Received"
|
||||
msgstr ""
|
||||
|
||||
#: apps/dca/models.py:169
|
||||
msgid "Expense Transaction"
|
||||
msgstr ""
|
||||
|
||||
#: apps/dca/models.py:177
|
||||
msgid "Income Transaction"
|
||||
msgstr ""
|
||||
|
||||
#: apps/dca/models.py:184
|
||||
msgid "DCA Entry"
|
||||
msgstr ""
|
||||
@@ -622,15 +659,15 @@ msgstr ""
|
||||
msgid "DCA strategy deleted successfully"
|
||||
msgstr ""
|
||||
|
||||
#: apps/dca/views.py:163
|
||||
#: apps/dca/views.py:161
|
||||
msgid "Entry added successfully"
|
||||
msgstr ""
|
||||
|
||||
#: apps/dca/views.py:190
|
||||
#: apps/dca/views.py:188
|
||||
msgid "Entry updated successfully"
|
||||
msgstr ""
|
||||
|
||||
#: apps/dca/views.py:216
|
||||
#: apps/dca/views.py:214
|
||||
msgid "Entry deleted successfully"
|
||||
msgstr ""
|
||||
|
||||
@@ -640,7 +677,7 @@ msgstr ""
|
||||
|
||||
#: apps/import_app/forms.py:61
|
||||
#: templates/import_app/fragments/profiles/list.html:62
|
||||
#: templates/includes/navbar.html:131
|
||||
#: templates/includes/navbar.html:134
|
||||
msgid "Import"
|
||||
msgstr ""
|
||||
|
||||
@@ -708,6 +745,15 @@ msgstr ""
|
||||
msgid "Run deleted successfully"
|
||||
msgstr ""
|
||||
|
||||
#: apps/insights/utils/sankey.py:37 apps/insights/utils/sankey.py:154
|
||||
msgid "Uncategorized"
|
||||
msgstr ""
|
||||
|
||||
#: apps/insights/utils/sankey.py:118 apps/insights/utils/sankey.py:119
|
||||
#: apps/insights/utils/sankey.py:234 apps/insights/utils/sankey.py:235
|
||||
msgid "Saved"
|
||||
msgstr ""
|
||||
|
||||
#: apps/rules/forms.py:20
|
||||
msgid "Run on creation"
|
||||
msgstr ""
|
||||
@@ -724,7 +770,7 @@ msgstr ""
|
||||
msgid "Set field"
|
||||
msgstr ""
|
||||
|
||||
#: apps/rules/forms.py:65
|
||||
#: apps/rules/forms.py:65 templates/insights/fragments/sankey.html:84
|
||||
msgid "To"
|
||||
msgstr ""
|
||||
|
||||
@@ -741,14 +787,14 @@ msgid "Operator"
|
||||
msgstr ""
|
||||
|
||||
#: apps/rules/forms.py:161 apps/rules/forms.py:174 apps/rules/models.py:25
|
||||
#: apps/rules/models.py:240 apps/transactions/models.py:139
|
||||
#: apps/transactions/models.py:293 apps/transactions/models.py:496
|
||||
#: apps/rules/models.py:240 apps/transactions/models.py:183
|
||||
#: apps/transactions/models.py:343 apps/transactions/models.py:546
|
||||
msgid "Type"
|
||||
msgstr ""
|
||||
|
||||
#: apps/rules/forms.py:162 apps/rules/forms.py:175 apps/rules/models.py:26
|
||||
#: apps/rules/models.py:244 apps/transactions/filters.py:23
|
||||
#: apps/transactions/models.py:141 templates/cotton/transaction/item.html:21
|
||||
#: apps/transactions/models.py:185 templates/cotton/transaction/item.html:21
|
||||
#: templates/cotton/transaction/item.html:31
|
||||
#: templates/transactions/widgets/paid_toggle_button.html:12
|
||||
#: templates/transactions/widgets/unselectable_paid_toggle_button.html:16
|
||||
@@ -758,41 +804,41 @@ msgstr ""
|
||||
#: apps/rules/forms.py:164 apps/rules/forms.py:177 apps/rules/models.py:28
|
||||
#: apps/rules/models.py:252 apps/transactions/forms.py:66
|
||||
#: apps/transactions/forms.py:322 apps/transactions/forms.py:492
|
||||
#: apps/transactions/models.py:143 apps/transactions/models.py:311
|
||||
#: apps/transactions/models.py:520
|
||||
#: apps/transactions/models.py:187 apps/transactions/models.py:361
|
||||
#: apps/transactions/models.py:570
|
||||
msgid "Reference Date"
|
||||
msgstr ""
|
||||
|
||||
#: apps/rules/forms.py:165 apps/rules/forms.py:178 apps/rules/models.py:29
|
||||
#: apps/rules/models.py:256 apps/transactions/models.py:148
|
||||
#: apps/transactions/models.py:501
|
||||
#: apps/rules/models.py:256 apps/transactions/models.py:192
|
||||
#: apps/transactions/models.py:551 templates/insights/fragments/sankey.html:85
|
||||
msgid "Amount"
|
||||
msgstr ""
|
||||
|
||||
#: apps/rules/forms.py:166 apps/rules/forms.py:179 apps/rules/models.py:11
|
||||
#: apps/rules/models.py:30 apps/rules/models.py:260
|
||||
#: apps/transactions/forms.py:325 apps/transactions/models.py:153
|
||||
#: apps/transactions/models.py:295 apps/transactions/models.py:504
|
||||
#: apps/transactions/forms.py:325 apps/transactions/models.py:197
|
||||
#: apps/transactions/models.py:345 apps/transactions/models.py:554
|
||||
msgid "Description"
|
||||
msgstr ""
|
||||
|
||||
#: apps/rules/forms.py:169 apps/rules/forms.py:184 apps/rules/models.py:268
|
||||
#: apps/transactions/models.py:192
|
||||
#: apps/transactions/models.py:236
|
||||
msgid "Internal Note"
|
||||
msgstr ""
|
||||
|
||||
#: apps/rules/forms.py:170 apps/rules/forms.py:185 apps/rules/models.py:272
|
||||
#: apps/transactions/models.py:194
|
||||
#: apps/transactions/models.py:238
|
||||
msgid "Internal ID"
|
||||
msgstr ""
|
||||
|
||||
#: apps/rules/forms.py:172 apps/rules/forms.py:181 apps/rules/models.py:34
|
||||
#: apps/rules/models.py:276 apps/transactions/filters.py:81
|
||||
#: apps/transactions/forms.py:55 apps/transactions/forms.py:486
|
||||
#: apps/transactions/forms.py:731 apps/transactions/models.py:117
|
||||
#: apps/transactions/models.py:170 apps/transactions/models.py:333
|
||||
#: apps/transactions/models.py:515 templates/entities/fragments/list.html:5
|
||||
#: templates/entities/pages/index.html:4 templates/includes/navbar.html:107
|
||||
#: apps/transactions/forms.py:731 apps/transactions/models.py:161
|
||||
#: apps/transactions/models.py:214 apps/transactions/models.py:383
|
||||
#: apps/transactions/models.py:565 templates/entities/fragments/list.html:5
|
||||
#: templates/entities/pages/index.html:4 templates/includes/navbar.html:110
|
||||
msgid "Entities"
|
||||
msgstr ""
|
||||
|
||||
@@ -946,7 +992,7 @@ msgid "Transaction Type"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/filters.py:67 templates/categories/fragments/list.html:5
|
||||
#: templates/categories/pages/index.html:4 templates/includes/navbar.html:103
|
||||
#: templates/categories/pages/index.html:4 templates/includes/navbar.html:106
|
||||
msgid "Categories"
|
||||
msgstr ""
|
||||
|
||||
@@ -974,14 +1020,6 @@ msgstr ""
|
||||
msgid "More"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/forms.py:266
|
||||
msgid "From Account"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/forms.py:271
|
||||
msgid "To Account"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/forms.py:278
|
||||
msgid "From Amount"
|
||||
msgstr ""
|
||||
@@ -995,10 +1033,6 @@ msgstr ""
|
||||
msgid "Transfer"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/forms.py:413
|
||||
msgid "From and To accounts must be different."
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/forms.py:610
|
||||
msgid "Tag name"
|
||||
msgstr ""
|
||||
@@ -1019,44 +1053,44 @@ msgstr ""
|
||||
msgid "End date should be after the start date"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:68
|
||||
#: apps/transactions/models.py:112
|
||||
msgid "Mute"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:73
|
||||
#: apps/transactions/models.py:117
|
||||
msgid ""
|
||||
"Deactivated categories won't be able to be selected when creating new "
|
||||
"transactions"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:78
|
||||
#: apps/transactions/models.py:122
|
||||
msgid "Transaction Category"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:79
|
||||
#: apps/transactions/models.py:123
|
||||
msgid "Transaction Categories"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:92
|
||||
#: apps/transactions/models.py:136
|
||||
msgid ""
|
||||
"Deactivated tags won't be able to be selected when creating new transactions"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:97 apps/transactions/models.py:98
|
||||
#: apps/transactions/models.py:141 apps/transactions/models.py:142
|
||||
msgid "Transaction Tags"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:111
|
||||
#: apps/transactions/models.py:155
|
||||
msgid ""
|
||||
"Deactivated entities won't be able to be selected when creating new "
|
||||
"transactions"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:116
|
||||
#: apps/transactions/models.py:160
|
||||
msgid "Entity"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:126
|
||||
#: apps/transactions/models.py:170
|
||||
#: templates/calendar_view/fragments/list.html:42
|
||||
#: templates/calendar_view/fragments/list.html:44
|
||||
#: templates/calendar_view/fragments/list.html:52
|
||||
@@ -1066,7 +1100,7 @@ msgstr ""
|
||||
msgid "Income"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:127
|
||||
#: apps/transactions/models.py:171
|
||||
#: templates/calendar_view/fragments/list.html:46
|
||||
#: templates/calendar_view/fragments/list.html:48
|
||||
#: templates/calendar_view/fragments/list.html:56
|
||||
@@ -1075,123 +1109,135 @@ msgstr ""
|
||||
msgid "Expense"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:181 apps/transactions/models.py:340
|
||||
#: apps/transactions/models.py:225 apps/transactions/models.py:390
|
||||
msgid "Installment Plan"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:190 apps/transactions/models.py:541
|
||||
#: apps/transactions/models.py:234 apps/transactions/models.py:591
|
||||
msgid "Recurring Transaction"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:198
|
||||
#: apps/transactions/models.py:242
|
||||
msgid "Deleted"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:203
|
||||
#: apps/transactions/models.py:247
|
||||
msgid "Deleted At"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:211
|
||||
#: apps/transactions/models.py:255
|
||||
msgid "Transaction"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:212 templates/includes/navbar.html:54
|
||||
#: templates/includes/navbar.html:101
|
||||
#: apps/transactions/models.py:256 templates/includes/navbar.html:57
|
||||
#: templates/includes/navbar.html:104
|
||||
#: templates/recurring_transactions/fragments/list_transactions.html:5
|
||||
#: templates/recurring_transactions/fragments/table.html:37
|
||||
#: templates/transactions/pages/transactions.html:5
|
||||
msgid "Transactions"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:282
|
||||
#: apps/transactions/models.py:323 templates/tags/fragments/table.html:53
|
||||
msgid "No tags"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:324
|
||||
msgid "No category"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:326
|
||||
msgid "No description"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:332
|
||||
msgid "Yearly"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:283 apps/users/models.py:26
|
||||
#: apps/transactions/models.py:333 apps/users/models.py:26
|
||||
#: templates/includes/navbar.html:26
|
||||
msgid "Monthly"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:284
|
||||
#: apps/transactions/models.py:334
|
||||
msgid "Weekly"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:285
|
||||
#: apps/transactions/models.py:335
|
||||
msgid "Daily"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:298
|
||||
#: apps/transactions/models.py:348
|
||||
msgid "Number of Installments"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:303
|
||||
#: apps/transactions/models.py:353
|
||||
msgid "Installment Start"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:304
|
||||
#: apps/transactions/models.py:354
|
||||
msgid "The installment number to start counting from"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:309 apps/transactions/models.py:524
|
||||
#: apps/transactions/models.py:359 apps/transactions/models.py:574
|
||||
msgid "Start Date"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:313 apps/transactions/models.py:525
|
||||
#: apps/transactions/models.py:363 apps/transactions/models.py:575
|
||||
msgid "End Date"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:318
|
||||
#: apps/transactions/models.py:368
|
||||
msgid "Recurrence"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:321
|
||||
#: apps/transactions/models.py:371
|
||||
msgid "Installment Amount"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:341 templates/includes/navbar.html:69
|
||||
#: apps/transactions/models.py:391 templates/includes/navbar.html:72
|
||||
#: templates/installment_plans/fragments/list.html:5
|
||||
#: templates/installment_plans/pages/index.html:4
|
||||
msgid "Installment Plans"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:483
|
||||
#: apps/transactions/models.py:533
|
||||
msgid "day(s)"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:484
|
||||
#: apps/transactions/models.py:534
|
||||
msgid "week(s)"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:485
|
||||
#: apps/transactions/models.py:535
|
||||
msgid "month(s)"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:486
|
||||
#: apps/transactions/models.py:536
|
||||
msgid "year(s)"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:488
|
||||
#: apps/transactions/models.py:538
|
||||
#: templates/recurring_transactions/fragments/list.html:24
|
||||
msgid "Paused"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:527
|
||||
#: apps/transactions/models.py:577
|
||||
msgid "Recurrence Type"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:530
|
||||
#: apps/transactions/models.py:580
|
||||
msgid "Recurrence Interval"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:534
|
||||
#: apps/transactions/models.py:584
|
||||
msgid "Last Generated Date"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:537
|
||||
#: apps/transactions/models.py:587
|
||||
msgid "Last Generated Reference Date"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:542 templates/includes/navbar.html:71
|
||||
#: apps/transactions/models.py:592 templates/includes/navbar.html:74
|
||||
#: templates/recurring_transactions/fragments/list.html:5
|
||||
#: templates/recurring_transactions/pages/index.html:4
|
||||
msgid "Recurring Transactions"
|
||||
@@ -1207,35 +1253,35 @@ msgstr ""
|
||||
msgid "%(value)s is not a non-negative number"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/views/actions.py:23
|
||||
#: apps/transactions/views/actions.py:24
|
||||
#, python-format
|
||||
msgid "%(count)s transaction marked as paid"
|
||||
msgid_plural "%(count)s transactions marked as paid"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: apps/transactions/views/actions.py:47
|
||||
#: apps/transactions/views/actions.py:48
|
||||
#, python-format
|
||||
msgid "%(count)s transaction marked as not paid"
|
||||
msgid_plural "%(count)s transactions marked as not paid"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: apps/transactions/views/actions.py:71
|
||||
#: apps/transactions/views/actions.py:72
|
||||
#, python-format
|
||||
msgid "%(count)s transaction deleted successfully"
|
||||
msgid_plural "%(count)s transactions deleted successfully"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: apps/transactions/views/actions.py:95
|
||||
#: apps/transactions/views/actions.py:96
|
||||
#, python-format
|
||||
msgid "%(count)s transaction restored successfully"
|
||||
msgid_plural "%(count)s transactions restored successfully"
|
||||
msgstr[0] ""
|
||||
msgstr[1] ""
|
||||
|
||||
#: apps/transactions/views/actions.py:130
|
||||
#: apps/transactions/views/actions.py:131
|
||||
#, python-format
|
||||
msgid "%(count)s transaction duplicated successfully"
|
||||
msgid_plural "%(count)s transactions duplicated successfully"
|
||||
@@ -1496,7 +1542,7 @@ msgstr ""
|
||||
#: templates/account_groups/fragments/list.html:36
|
||||
#: templates/accounts/fragments/list.html:41
|
||||
#: templates/categories/fragments/table.html:29
|
||||
#: templates/cotton/transaction/item.html:127
|
||||
#: templates/cotton/transaction/item.html:130
|
||||
#: templates/cotton/ui/transactions_action_bar.html:49
|
||||
#: templates/currencies/fragments/list.html:37
|
||||
#: templates/dca/fragments/strategy/details.html:67
|
||||
@@ -1518,8 +1564,8 @@ msgstr ""
|
||||
#: templates/account_groups/fragments/list.html:43
|
||||
#: templates/accounts/fragments/list.html:48
|
||||
#: templates/categories/fragments/table.html:36
|
||||
#: templates/cotton/transaction/item.html:142
|
||||
#: templates/cotton/transaction/item.html:161
|
||||
#: templates/cotton/transaction/item.html:145
|
||||
#: templates/cotton/transaction/item.html:164
|
||||
#: templates/cotton/ui/deleted_transactions_action_bar.html:55
|
||||
#: templates/cotton/ui/transactions_action_bar.html:86
|
||||
#: templates/currencies/fragments/list.html:44
|
||||
@@ -1544,8 +1590,8 @@ msgstr ""
|
||||
#: templates/account_groups/fragments/list.html:47
|
||||
#: templates/accounts/fragments/list.html:52
|
||||
#: templates/categories/fragments/table.html:41
|
||||
#: templates/cotton/transaction/item.html:146
|
||||
#: templates/cotton/transaction/item.html:165
|
||||
#: templates/cotton/transaction/item.html:149
|
||||
#: templates/cotton/transaction/item.html:168
|
||||
#: templates/cotton/ui/deleted_transactions_action_bar.html:57
|
||||
#: templates/cotton/ui/transactions_action_bar.html:88
|
||||
#: templates/currencies/fragments/list.html:48
|
||||
@@ -1573,8 +1619,8 @@ msgstr ""
|
||||
#: templates/account_groups/fragments/list.html:48
|
||||
#: templates/accounts/fragments/list.html:53
|
||||
#: templates/categories/fragments/table.html:42
|
||||
#: templates/cotton/transaction/item.html:147
|
||||
#: templates/cotton/transaction/item.html:166
|
||||
#: templates/cotton/transaction/item.html:150
|
||||
#: templates/cotton/transaction/item.html:169
|
||||
#: templates/cotton/ui/deleted_transactions_action_bar.html:58
|
||||
#: templates/cotton/ui/transactions_action_bar.html:89
|
||||
#: templates/currencies/fragments/list.html:49
|
||||
@@ -1595,8 +1641,8 @@ msgstr ""
|
||||
#: templates/account_groups/fragments/list.html:49
|
||||
#: templates/accounts/fragments/list.html:54
|
||||
#: templates/categories/fragments/table.html:43
|
||||
#: templates/cotton/transaction/item.html:148
|
||||
#: templates/cotton/transaction/item.html:167
|
||||
#: templates/cotton/transaction/item.html:151
|
||||
#: templates/cotton/transaction/item.html:170
|
||||
#: templates/currencies/fragments/list.html:50
|
||||
#: templates/dca/fragments/strategy/details.html:82
|
||||
#: templates/dca/fragments/strategy/list.html:48
|
||||
@@ -1647,7 +1693,7 @@ msgstr ""
|
||||
msgid "Is Asset"
|
||||
msgstr ""
|
||||
|
||||
#: templates/accounts/fragments/list.html:70
|
||||
#: templates/accounts/fragments/list.html:69
|
||||
msgid "No accounts"
|
||||
msgstr ""
|
||||
|
||||
@@ -1725,12 +1771,16 @@ msgstr ""
|
||||
msgid "Select"
|
||||
msgstr ""
|
||||
|
||||
#: templates/cotton/transaction/item.html:134
|
||||
#: templates/cotton/transaction/item.html:56
|
||||
msgid "DCA"
|
||||
msgstr ""
|
||||
|
||||
#: templates/cotton/transaction/item.html:137
|
||||
#: templates/cotton/ui/transactions_action_bar.html:78
|
||||
msgid "Duplicate"
|
||||
msgstr ""
|
||||
|
||||
#: templates/cotton/transaction/item.html:155
|
||||
#: templates/cotton/transaction/item.html:158
|
||||
#: templates/cotton/ui/deleted_transactions_action_bar.html:47
|
||||
msgid "Restore"
|
||||
msgstr ""
|
||||
@@ -2030,7 +2080,7 @@ msgid "Edit exchange rate"
|
||||
msgstr ""
|
||||
|
||||
#: templates/exchange_rates/fragments/list.html:25
|
||||
#: templates/includes/navbar.html:58
|
||||
#: templates/includes/navbar.html:61
|
||||
#: templates/installment_plans/fragments/list.html:21
|
||||
#: templates/yearly_overview/pages/overview_by_account.html:92
|
||||
#: templates/yearly_overview/pages/overview_by_currency.html:94
|
||||
@@ -2060,7 +2110,7 @@ msgstr ""
|
||||
|
||||
#: templates/exchange_rates_services/fragments/list.html:6
|
||||
#: templates/exchange_rates_services/pages/index.html:4
|
||||
#: templates/includes/navbar.html:133
|
||||
#: templates/includes/navbar.html:136
|
||||
msgid "Automatic Exchange Rates"
|
||||
msgstr ""
|
||||
|
||||
@@ -2197,52 +2247,56 @@ msgstr ""
|
||||
msgid "Current"
|
||||
msgstr ""
|
||||
|
||||
#: templates/includes/navbar.html:63
|
||||
#: templates/includes/navbar.html:50
|
||||
msgid "Insights"
|
||||
msgstr ""
|
||||
|
||||
#: templates/includes/navbar.html:66
|
||||
msgid "Trash Can"
|
||||
msgstr ""
|
||||
|
||||
#: templates/includes/navbar.html:79
|
||||
#: templates/includes/navbar.html:82
|
||||
msgid "Tools"
|
||||
msgstr ""
|
||||
|
||||
#: templates/includes/navbar.html:83
|
||||
#: templates/includes/navbar.html:86
|
||||
msgid "Dollar Cost Average Tracker"
|
||||
msgstr ""
|
||||
|
||||
#: templates/includes/navbar.html:86
|
||||
#: templates/includes/navbar.html:89
|
||||
#: templates/mini_tools/unit_price_calculator.html:5
|
||||
#: templates/mini_tools/unit_price_calculator.html:10
|
||||
msgid "Unit Price Calculator"
|
||||
msgstr ""
|
||||
|
||||
#: templates/includes/navbar.html:89
|
||||
#: templates/includes/navbar.html:92
|
||||
#: templates/mini_tools/currency_converter/currency_converter.html:8
|
||||
#: templates/mini_tools/currency_converter/currency_converter.html:15
|
||||
msgid "Currency Converter"
|
||||
msgstr ""
|
||||
|
||||
#: templates/includes/navbar.html:98
|
||||
#: templates/includes/navbar.html:101
|
||||
msgid "Management"
|
||||
msgstr ""
|
||||
|
||||
#: templates/includes/navbar.html:127
|
||||
#: templates/includes/navbar.html:130
|
||||
msgid "Automation"
|
||||
msgstr ""
|
||||
|
||||
#: templates/includes/navbar.html:129 templates/rules/fragments/list.html:5
|
||||
#: templates/includes/navbar.html:132 templates/rules/fragments/list.html:5
|
||||
#: templates/rules/pages/index.html:4
|
||||
msgid "Rules"
|
||||
msgstr ""
|
||||
|
||||
#: templates/includes/navbar.html:143
|
||||
#: templates/includes/navbar.html:146
|
||||
msgid "Only use this if you know what you're doing"
|
||||
msgstr ""
|
||||
|
||||
#: templates/includes/navbar.html:144
|
||||
#: templates/includes/navbar.html:147
|
||||
msgid "Django Admin"
|
||||
msgstr ""
|
||||
|
||||
#: templates/includes/navbar.html:153
|
||||
#: templates/includes/navbar.html:156
|
||||
msgid "Calculator"
|
||||
msgstr ""
|
||||
|
||||
@@ -2274,6 +2328,44 @@ msgstr ""
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: templates/insights/fragments/sankey.html:83
|
||||
msgid "From"
|
||||
msgstr ""
|
||||
|
||||
#: templates/insights/fragments/sankey.html:86
|
||||
msgid "Percentage"
|
||||
msgstr ""
|
||||
|
||||
#: templates/insights/pages/index.html:33
|
||||
msgid "Month"
|
||||
msgstr ""
|
||||
|
||||
#: templates/insights/pages/index.html:36
|
||||
#: templates/yearly_overview/pages/overview_by_account.html:61
|
||||
#: templates/yearly_overview/pages/overview_by_currency.html:63
|
||||
msgid "Year"
|
||||
msgstr ""
|
||||
|
||||
#: templates/insights/pages/index.html:39
|
||||
msgid "Month Range"
|
||||
msgstr ""
|
||||
|
||||
#: templates/insights/pages/index.html:42
|
||||
msgid "Year Range"
|
||||
msgstr ""
|
||||
|
||||
#: templates/insights/pages/index.html:45
|
||||
msgid "Date Range"
|
||||
msgstr ""
|
||||
|
||||
#: templates/insights/pages/index.html:74
|
||||
msgid "Account Flow"
|
||||
msgstr ""
|
||||
|
||||
#: templates/insights/pages/index.html:81
|
||||
msgid "Currency Flow"
|
||||
msgstr ""
|
||||
|
||||
#: templates/installment_plans/fragments/add.html:5
|
||||
msgid "Add installment plan"
|
||||
msgstr ""
|
||||
@@ -2575,10 +2667,6 @@ msgstr ""
|
||||
msgid "Edit tag"
|
||||
msgstr ""
|
||||
|
||||
#: templates/tags/fragments/table.html:53
|
||||
msgid "No tags"
|
||||
msgstr ""
|
||||
|
||||
#: templates/transactions/fragments/add.html:5
|
||||
#: templates/transactions/pages/add.html:5
|
||||
msgid "New transaction"
|
||||
@@ -2642,8 +2730,3 @@ msgstr ""
|
||||
#: templates/yearly_overview/pages/overview_by_currency.html:9
|
||||
msgid "Yearly Overview"
|
||||
msgstr ""
|
||||
|
||||
#: templates/yearly_overview/pages/overview_by_account.html:61
|
||||
#: templates/yearly_overview/pages/overview_by_currency.html:63
|
||||
msgid "Year"
|
||||
msgstr ""
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -8,8 +8,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-02-09 17:27-0300\n"
|
||||
"PO-Revision-Date: 2025-02-09 17:30-0300\n"
|
||||
"POT-Creation-Date: 2025-02-16 00:03-0300\n"
|
||||
"PO-Revision-Date: 2025-02-16 00:04-0300\n"
|
||||
"Last-Translator: Herculino Trotta\n"
|
||||
"Language-Team: \n"
|
||||
"Language: pt_BR\n"
|
||||
@@ -25,7 +25,7 @@ msgstr "Nome do grupo"
|
||||
|
||||
#: apps/accounts/forms.py:40 apps/accounts/forms.py:96
|
||||
#: apps/currencies/forms.py:53 apps/currencies/forms.py:91
|
||||
#: apps/currencies/forms.py:142 apps/dca/forms.py:41 apps/dca/forms.py:93
|
||||
#: apps/currencies/forms.py:142 apps/dca/forms.py:49 apps/dca/forms.py:224
|
||||
#: apps/import_app/forms.py:34 apps/rules/forms.py:45 apps/rules/forms.py:87
|
||||
#: apps/rules/forms.py:359 apps/transactions/forms.py:190
|
||||
#: apps/transactions/forms.py:257 apps/transactions/forms.py:581
|
||||
@@ -35,9 +35,9 @@ msgid "Update"
|
||||
msgstr "Atualizar"
|
||||
|
||||
#: apps/accounts/forms.py:48 apps/accounts/forms.py:104
|
||||
#: apps/common/widgets/tom_select.py:12 apps/currencies/forms.py:61
|
||||
#: apps/common/widgets/tom_select.py:13 apps/currencies/forms.py:61
|
||||
#: apps/currencies/forms.py:99 apps/currencies/forms.py:150
|
||||
#: apps/dca/forms.py:49 apps/dca/forms.py:102 apps/import_app/forms.py:42
|
||||
#: apps/dca/forms.py:57 apps/dca/forms.py:232 apps/import_app/forms.py:42
|
||||
#: apps/rules/forms.py:53 apps/rules/forms.py:95 apps/rules/forms.py:367
|
||||
#: apps/transactions/forms.py:174 apps/transactions/forms.py:199
|
||||
#: apps/transactions/forms.py:589 apps/transactions/forms.py:632
|
||||
@@ -69,30 +69,32 @@ msgstr "Grupo da Conta"
|
||||
msgid "New balance"
|
||||
msgstr "Novo saldo"
|
||||
|
||||
#: apps/accounts/forms.py:119 apps/rules/forms.py:168 apps/rules/forms.py:183
|
||||
#: apps/rules/models.py:32 apps/rules/models.py:280
|
||||
#: apps/transactions/forms.py:39 apps/transactions/forms.py:291
|
||||
#: apps/transactions/forms.py:298 apps/transactions/forms.py:478
|
||||
#: apps/transactions/forms.py:723 apps/transactions/models.py:159
|
||||
#: apps/transactions/models.py:328 apps/transactions/models.py:508
|
||||
#: apps/accounts/forms.py:119 apps/dca/forms.py:85 apps/dca/forms.py:92
|
||||
#: apps/rules/forms.py:168 apps/rules/forms.py:183 apps/rules/models.py:32
|
||||
#: apps/rules/models.py:280 apps/transactions/forms.py:39
|
||||
#: apps/transactions/forms.py:291 apps/transactions/forms.py:298
|
||||
#: apps/transactions/forms.py:478 apps/transactions/forms.py:723
|
||||
#: apps/transactions/models.py:203 apps/transactions/models.py:378
|
||||
#: apps/transactions/models.py:558
|
||||
msgid "Category"
|
||||
msgstr "Categoria"
|
||||
|
||||
#: apps/accounts/forms.py:126 apps/rules/forms.py:171 apps/rules/forms.py:180
|
||||
#: apps/rules/models.py:33 apps/rules/models.py:284
|
||||
#: apps/transactions/filters.py:74 apps/transactions/forms.py:47
|
||||
#: apps/transactions/forms.py:307 apps/transactions/forms.py:315
|
||||
#: apps/transactions/forms.py:471 apps/transactions/forms.py:716
|
||||
#: apps/transactions/models.py:165 apps/transactions/models.py:330
|
||||
#: apps/transactions/models.py:512 templates/includes/navbar.html:105
|
||||
#: templates/tags/fragments/list.html:5 templates/tags/pages/index.html:4
|
||||
#: apps/accounts/forms.py:126 apps/dca/forms.py:101 apps/dca/forms.py:109
|
||||
#: apps/rules/forms.py:171 apps/rules/forms.py:180 apps/rules/models.py:33
|
||||
#: apps/rules/models.py:284 apps/transactions/filters.py:74
|
||||
#: apps/transactions/forms.py:47 apps/transactions/forms.py:307
|
||||
#: apps/transactions/forms.py:315 apps/transactions/forms.py:471
|
||||
#: apps/transactions/forms.py:716 apps/transactions/models.py:209
|
||||
#: apps/transactions/models.py:380 apps/transactions/models.py:562
|
||||
#: templates/includes/navbar.html:108 templates/tags/fragments/list.html:5
|
||||
#: templates/tags/pages/index.html:4
|
||||
msgid "Tags"
|
||||
msgstr "Tags"
|
||||
|
||||
#: apps/accounts/models.py:9 apps/accounts/models.py:21 apps/dca/models.py:14
|
||||
#: apps/import_app/models.py:14 apps/rules/models.py:10
|
||||
#: apps/transactions/models.py:67 apps/transactions/models.py:87
|
||||
#: apps/transactions/models.py:106
|
||||
#: apps/transactions/models.py:111 apps/transactions/models.py:131
|
||||
#: apps/transactions/models.py:150
|
||||
#: templates/account_groups/fragments/list.html:25
|
||||
#: templates/accounts/fragments/list.html:25
|
||||
#: templates/categories/fragments/table.html:16
|
||||
@@ -113,7 +115,7 @@ msgstr "Grupo da Conta"
|
||||
|
||||
#: apps/accounts/models.py:13 templates/account_groups/fragments/list.html:5
|
||||
#: templates/account_groups/pages/index.html:4
|
||||
#: templates/includes/navbar.html:115
|
||||
#: templates/includes/navbar.html:118
|
||||
msgid "Account Groups"
|
||||
msgstr "Grupos da Conta"
|
||||
|
||||
@@ -157,15 +159,15 @@ msgstr ""
|
||||
#: apps/accounts/models.py:59 apps/rules/forms.py:160 apps/rules/forms.py:173
|
||||
#: apps/rules/models.py:24 apps/rules/models.py:236
|
||||
#: apps/transactions/forms.py:59 apps/transactions/forms.py:463
|
||||
#: apps/transactions/forms.py:708 apps/transactions/models.py:132
|
||||
#: apps/transactions/models.py:288 apps/transactions/models.py:490
|
||||
#: apps/transactions/forms.py:708 apps/transactions/models.py:176
|
||||
#: apps/transactions/models.py:338 apps/transactions/models.py:540
|
||||
msgid "Account"
|
||||
msgstr "Conta"
|
||||
|
||||
#: apps/accounts/models.py:60 apps/transactions/filters.py:53
|
||||
#: templates/accounts/fragments/list.html:5
|
||||
#: templates/accounts/pages/index.html:4 templates/includes/navbar.html:111
|
||||
#: templates/includes/navbar.html:113
|
||||
#: templates/accounts/pages/index.html:4 templates/includes/navbar.html:114
|
||||
#: templates/includes/navbar.html:116
|
||||
#: templates/monthly_overview/pages/overview.html:94
|
||||
#: templates/transactions/fragments/summary.html:12
|
||||
#: templates/transactions/pages/transactions.html:72
|
||||
@@ -236,13 +238,13 @@ msgstr "Dados da entidade inválidos. Forneça um ID ou nome."
|
||||
msgid "Either 'date' or 'reference_date' must be provided."
|
||||
msgstr "É necessário fornecer “date” ou “reference_date”."
|
||||
|
||||
#: apps/common/fields/forms/dynamic_select.py:127
|
||||
#: apps/common/fields/forms/dynamic_select.py:163
|
||||
#: apps/common/fields/forms/dynamic_select.py:131
|
||||
#: apps/common/fields/forms/dynamic_select.py:167
|
||||
msgid "Error creating new instance"
|
||||
msgstr "Erro criando nova instância"
|
||||
|
||||
#: apps/common/fields/forms/grouped_select.py:24
|
||||
#: apps/common/widgets/tom_select.py:91 apps/common/widgets/tom_select.py:94
|
||||
#: apps/common/widgets/tom_select.py:92 apps/common/widgets/tom_select.py:95
|
||||
msgid "Ungrouped"
|
||||
msgstr "Não agrupado"
|
||||
|
||||
@@ -334,6 +336,7 @@ msgid "Cache cleared successfully"
|
||||
msgstr "Cache limpo com sucesso"
|
||||
|
||||
#: apps/common/widgets/datepicker.py:47 apps/common/widgets/datepicker.py:186
|
||||
#: apps/common/widgets/datepicker.py:244
|
||||
msgid "Today"
|
||||
msgstr "Hoje"
|
||||
|
||||
@@ -341,18 +344,18 @@ msgstr "Hoje"
|
||||
msgid "Now"
|
||||
msgstr "Agora"
|
||||
|
||||
#: apps/common/widgets/tom_select.py:10
|
||||
#: apps/common/widgets/tom_select.py:11
|
||||
msgid "Remove"
|
||||
msgstr "Remover"
|
||||
|
||||
#: apps/common/widgets/tom_select.py:14
|
||||
#: apps/common/widgets/tom_select.py:15
|
||||
#: templates/mini_tools/unit_price_calculator.html:174
|
||||
#: templates/monthly_overview/pages/overview.html:172
|
||||
#: templates/transactions/pages/transactions.html:17
|
||||
msgid "Clear"
|
||||
msgstr "Limpar"
|
||||
|
||||
#: apps/common/widgets/tom_select.py:15
|
||||
#: apps/common/widgets/tom_select.py:16
|
||||
msgid "No results..."
|
||||
msgstr "Sem resultados..."
|
||||
|
||||
@@ -367,7 +370,7 @@ msgstr "Sufixo"
|
||||
#: apps/currencies/forms.py:69 apps/dca/models.py:156 apps/rules/forms.py:163
|
||||
#: apps/rules/forms.py:176 apps/rules/models.py:27 apps/rules/models.py:248
|
||||
#: apps/transactions/forms.py:63 apps/transactions/forms.py:319
|
||||
#: apps/transactions/models.py:142
|
||||
#: apps/transactions/models.py:186
|
||||
#: templates/dca/fragments/strategy/details.html:52
|
||||
#: templates/exchange_rates/fragments/table.html:10
|
||||
#: templates/exchange_rates_services/fragments/table.html:10
|
||||
@@ -388,8 +391,8 @@ msgstr "Casas Decimais"
|
||||
|
||||
#: apps/currencies/models.py:40 apps/transactions/filters.py:60
|
||||
#: templates/currencies/fragments/list.html:5
|
||||
#: templates/currencies/pages/index.html:4 templates/includes/navbar.html:119
|
||||
#: templates/includes/navbar.html:121
|
||||
#: templates/currencies/pages/index.html:4 templates/includes/navbar.html:122
|
||||
#: templates/includes/navbar.html:124
|
||||
#: templates/monthly_overview/pages/overview.html:81
|
||||
#: templates/transactions/fragments/summary.html:8
|
||||
#: templates/transactions/pages/transactions.html:59
|
||||
@@ -418,7 +421,7 @@ msgstr "Data e Tempo"
|
||||
|
||||
#: apps/currencies/models.py:74 templates/exchange_rates/fragments/list.html:6
|
||||
#: templates/exchange_rates/pages/index.html:4
|
||||
#: templates/includes/navbar.html:123
|
||||
#: templates/includes/navbar.html:126
|
||||
msgid "Exchange Rates"
|
||||
msgstr "Taxas de Câmbio"
|
||||
|
||||
@@ -446,8 +449,8 @@ msgstr "Nome do Serviço"
|
||||
msgid "Service Type"
|
||||
msgstr "Tipo de Serviço"
|
||||
|
||||
#: apps/currencies/models.py:107 apps/transactions/models.py:71
|
||||
#: apps/transactions/models.py:90 apps/transactions/models.py:109
|
||||
#: apps/currencies/models.py:107 apps/transactions/models.py:115
|
||||
#: apps/transactions/models.py:134 apps/transactions/models.py:153
|
||||
#: templates/categories/fragments/list.html:21
|
||||
#: templates/entities/fragments/list.html:21
|
||||
#: templates/recurring_transactions/fragments/list.html:21
|
||||
@@ -573,6 +576,48 @@ msgstr "Serviço apagado com sucesso"
|
||||
msgid "Services queued successfully"
|
||||
msgstr "Serviços marcados para execução com sucesso"
|
||||
|
||||
#: apps/dca/forms.py:65 apps/dca/forms.py:164
|
||||
msgid "Create transaction"
|
||||
msgstr "Criar transação"
|
||||
|
||||
#: apps/dca/forms.py:70 apps/transactions/forms.py:266
|
||||
msgid "From Account"
|
||||
msgstr "Conta de origem"
|
||||
|
||||
#: apps/dca/forms.py:76 apps/transactions/forms.py:271
|
||||
msgid "To Account"
|
||||
msgstr "Conta de destino"
|
||||
|
||||
#: apps/dca/forms.py:116 apps/dca/models.py:169
|
||||
msgid "Expense Transaction"
|
||||
msgstr "Transação de saída"
|
||||
|
||||
#: apps/dca/forms.py:120 apps/dca/forms.py:130
|
||||
msgid "Type to search for a transaction to link to this entry"
|
||||
msgstr "Digite para buscar uma transação para conectar à esta entrada"
|
||||
|
||||
#: apps/dca/forms.py:126 apps/dca/models.py:177
|
||||
msgid "Income Transaction"
|
||||
msgstr "Transação de entrada"
|
||||
|
||||
#: apps/dca/forms.py:210
|
||||
msgid "Link transaction"
|
||||
msgstr "Conectar transação"
|
||||
|
||||
#: apps/dca/forms.py:279 apps/dca/forms.py:280 apps/dca/forms.py:285
|
||||
#: apps/dca/forms.py:289
|
||||
msgid "You must provide an account."
|
||||
msgstr "Você deve informar uma conta."
|
||||
|
||||
#: apps/dca/forms.py:294 apps/transactions/forms.py:413
|
||||
msgid "From and To accounts must be different."
|
||||
msgstr "As contas De e Para devem ser diferentes."
|
||||
|
||||
#: apps/dca/forms.py:308
|
||||
#, python-format
|
||||
msgid "DCA for %(strategy_name)s"
|
||||
msgstr "CMP para %(strategy_name)s"
|
||||
|
||||
#: apps/dca/models.py:17
|
||||
msgid "Target Currency"
|
||||
msgstr "Moeda de destino"
|
||||
@@ -583,8 +628,8 @@ msgstr "Moeda de pagamento"
|
||||
|
||||
#: apps/dca/models.py:27 apps/dca/models.py:179 apps/rules/forms.py:167
|
||||
#: apps/rules/forms.py:182 apps/rules/models.py:31 apps/rules/models.py:264
|
||||
#: apps/transactions/forms.py:333 apps/transactions/models.py:155
|
||||
#: apps/transactions/models.py:337 apps/transactions/models.py:518
|
||||
#: apps/transactions/forms.py:333 apps/transactions/models.py:199
|
||||
#: apps/transactions/models.py:387 apps/transactions/models.py:568
|
||||
msgid "Notes"
|
||||
msgstr "Notas"
|
||||
|
||||
@@ -608,14 +653,6 @@ msgstr "Quantia paga"
|
||||
msgid "Amount Received"
|
||||
msgstr "Quantia recebida"
|
||||
|
||||
#: apps/dca/models.py:169
|
||||
msgid "Expense Transaction"
|
||||
msgstr "Transação de saída"
|
||||
|
||||
#: apps/dca/models.py:177
|
||||
msgid "Income Transaction"
|
||||
msgstr "Transação de entrada"
|
||||
|
||||
#: apps/dca/models.py:184
|
||||
msgid "DCA Entry"
|
||||
msgstr "Entrada CMP"
|
||||
@@ -636,15 +673,15 @@ msgstr "Estratégia CMP atualizada com sucesso"
|
||||
msgid "DCA strategy deleted successfully"
|
||||
msgstr "Estratégia CMP apagada com sucesso"
|
||||
|
||||
#: apps/dca/views.py:163
|
||||
#: apps/dca/views.py:161
|
||||
msgid "Entry added successfully"
|
||||
msgstr "Entrada adicionada com sucesso"
|
||||
|
||||
#: apps/dca/views.py:190
|
||||
#: apps/dca/views.py:188
|
||||
msgid "Entry updated successfully"
|
||||
msgstr "Entrada atualizada com sucesso"
|
||||
|
||||
#: apps/dca/views.py:216
|
||||
#: apps/dca/views.py:214
|
||||
msgid "Entry deleted successfully"
|
||||
msgstr "Entrada apagada com sucesso"
|
||||
|
||||
@@ -654,7 +691,7 @@ msgstr "Selecione um arquivo"
|
||||
|
||||
#: apps/import_app/forms.py:61
|
||||
#: templates/import_app/fragments/profiles/list.html:62
|
||||
#: templates/includes/navbar.html:131
|
||||
#: templates/includes/navbar.html:134
|
||||
msgid "Import"
|
||||
msgstr "Importar"
|
||||
|
||||
@@ -722,6 +759,15 @@ msgstr "Importação adicionada à fila com sucesso"
|
||||
msgid "Run deleted successfully"
|
||||
msgstr "Importação apagada com sucesso"
|
||||
|
||||
#: apps/insights/utils/sankey.py:37 apps/insights/utils/sankey.py:154
|
||||
msgid "Uncategorized"
|
||||
msgstr "Sem categoria"
|
||||
|
||||
#: apps/insights/utils/sankey.py:118 apps/insights/utils/sankey.py:119
|
||||
#: apps/insights/utils/sankey.py:234 apps/insights/utils/sankey.py:235
|
||||
msgid "Saved"
|
||||
msgstr "Salvo"
|
||||
|
||||
#: apps/rules/forms.py:20
|
||||
msgid "Run on creation"
|
||||
msgstr "Rodar na criação"
|
||||
@@ -738,7 +784,7 @@ msgstr "Se..."
|
||||
msgid "Set field"
|
||||
msgstr "Definir campo"
|
||||
|
||||
#: apps/rules/forms.py:65
|
||||
#: apps/rules/forms.py:65 templates/insights/fragments/sankey.html:84
|
||||
msgid "To"
|
||||
msgstr "Para"
|
||||
|
||||
@@ -755,14 +801,14 @@ msgid "Operator"
|
||||
msgstr "Operador"
|
||||
|
||||
#: apps/rules/forms.py:161 apps/rules/forms.py:174 apps/rules/models.py:25
|
||||
#: apps/rules/models.py:240 apps/transactions/models.py:139
|
||||
#: apps/transactions/models.py:293 apps/transactions/models.py:496
|
||||
#: apps/rules/models.py:240 apps/transactions/models.py:183
|
||||
#: apps/transactions/models.py:343 apps/transactions/models.py:546
|
||||
msgid "Type"
|
||||
msgstr "Tipo"
|
||||
|
||||
#: apps/rules/forms.py:162 apps/rules/forms.py:175 apps/rules/models.py:26
|
||||
#: apps/rules/models.py:244 apps/transactions/filters.py:23
|
||||
#: apps/transactions/models.py:141 templates/cotton/transaction/item.html:21
|
||||
#: apps/transactions/models.py:185 templates/cotton/transaction/item.html:21
|
||||
#: templates/cotton/transaction/item.html:31
|
||||
#: templates/transactions/widgets/paid_toggle_button.html:12
|
||||
#: templates/transactions/widgets/unselectable_paid_toggle_button.html:16
|
||||
@@ -772,41 +818,41 @@ msgstr "Pago"
|
||||
#: apps/rules/forms.py:164 apps/rules/forms.py:177 apps/rules/models.py:28
|
||||
#: apps/rules/models.py:252 apps/transactions/forms.py:66
|
||||
#: apps/transactions/forms.py:322 apps/transactions/forms.py:492
|
||||
#: apps/transactions/models.py:143 apps/transactions/models.py:311
|
||||
#: apps/transactions/models.py:520
|
||||
#: apps/transactions/models.py:187 apps/transactions/models.py:361
|
||||
#: apps/transactions/models.py:570
|
||||
msgid "Reference Date"
|
||||
msgstr "Data de Referência"
|
||||
|
||||
#: apps/rules/forms.py:165 apps/rules/forms.py:178 apps/rules/models.py:29
|
||||
#: apps/rules/models.py:256 apps/transactions/models.py:148
|
||||
#: apps/transactions/models.py:501
|
||||
#: apps/rules/models.py:256 apps/transactions/models.py:192
|
||||
#: apps/transactions/models.py:551 templates/insights/fragments/sankey.html:85
|
||||
msgid "Amount"
|
||||
msgstr "Quantia"
|
||||
|
||||
#: apps/rules/forms.py:166 apps/rules/forms.py:179 apps/rules/models.py:11
|
||||
#: apps/rules/models.py:30 apps/rules/models.py:260
|
||||
#: apps/transactions/forms.py:325 apps/transactions/models.py:153
|
||||
#: apps/transactions/models.py:295 apps/transactions/models.py:504
|
||||
#: apps/transactions/forms.py:325 apps/transactions/models.py:197
|
||||
#: apps/transactions/models.py:345 apps/transactions/models.py:554
|
||||
msgid "Description"
|
||||
msgstr "Descrição"
|
||||
|
||||
#: apps/rules/forms.py:169 apps/rules/forms.py:184 apps/rules/models.py:268
|
||||
#: apps/transactions/models.py:192
|
||||
#: apps/transactions/models.py:236
|
||||
msgid "Internal Note"
|
||||
msgstr "Nota Interna"
|
||||
|
||||
#: apps/rules/forms.py:170 apps/rules/forms.py:185 apps/rules/models.py:272
|
||||
#: apps/transactions/models.py:194
|
||||
#: apps/transactions/models.py:238
|
||||
msgid "Internal ID"
|
||||
msgstr "ID Interna"
|
||||
|
||||
#: apps/rules/forms.py:172 apps/rules/forms.py:181 apps/rules/models.py:34
|
||||
#: apps/rules/models.py:276 apps/transactions/filters.py:81
|
||||
#: apps/transactions/forms.py:55 apps/transactions/forms.py:486
|
||||
#: apps/transactions/forms.py:731 apps/transactions/models.py:117
|
||||
#: apps/transactions/models.py:170 apps/transactions/models.py:333
|
||||
#: apps/transactions/models.py:515 templates/entities/fragments/list.html:5
|
||||
#: templates/entities/pages/index.html:4 templates/includes/navbar.html:107
|
||||
#: apps/transactions/forms.py:731 apps/transactions/models.py:161
|
||||
#: apps/transactions/models.py:214 apps/transactions/models.py:383
|
||||
#: apps/transactions/models.py:565 templates/entities/fragments/list.html:5
|
||||
#: templates/entities/pages/index.html:4 templates/includes/navbar.html:110
|
||||
msgid "Entities"
|
||||
msgstr "Entidades"
|
||||
|
||||
@@ -962,7 +1008,7 @@ msgid "Transaction Type"
|
||||
msgstr "Tipo de Transação"
|
||||
|
||||
#: apps/transactions/filters.py:67 templates/categories/fragments/list.html:5
|
||||
#: templates/categories/pages/index.html:4 templates/includes/navbar.html:103
|
||||
#: templates/categories/pages/index.html:4 templates/includes/navbar.html:106
|
||||
msgid "Categories"
|
||||
msgstr "Categorias"
|
||||
|
||||
@@ -990,14 +1036,6 @@ msgstr "Quantia máxima"
|
||||
msgid "More"
|
||||
msgstr "Mais"
|
||||
|
||||
#: apps/transactions/forms.py:266
|
||||
msgid "From Account"
|
||||
msgstr "Conta de origem"
|
||||
|
||||
#: apps/transactions/forms.py:271
|
||||
msgid "To Account"
|
||||
msgstr "Conta de destino"
|
||||
|
||||
#: apps/transactions/forms.py:278
|
||||
msgid "From Amount"
|
||||
msgstr "Quantia de origem"
|
||||
@@ -1011,10 +1049,6 @@ msgstr "Quantia de destino"
|
||||
msgid "Transfer"
|
||||
msgstr "Transferir"
|
||||
|
||||
#: apps/transactions/forms.py:413
|
||||
msgid "From and To accounts must be different."
|
||||
msgstr "As contas De e Para devem ser diferentes."
|
||||
|
||||
#: apps/transactions/forms.py:610
|
||||
msgid "Tag name"
|
||||
msgstr "Nome da Tag"
|
||||
@@ -1035,11 +1069,11 @@ msgstr "As categorias silenciadas não serão contabilizadas em seu total mensal
|
||||
msgid "End date should be after the start date"
|
||||
msgstr "Data final deve ser após data inicial"
|
||||
|
||||
#: apps/transactions/models.py:68
|
||||
#: apps/transactions/models.py:112
|
||||
msgid "Mute"
|
||||
msgstr "Silenciada"
|
||||
|
||||
#: apps/transactions/models.py:73
|
||||
#: apps/transactions/models.py:117
|
||||
msgid ""
|
||||
"Deactivated categories won't be able to be selected when creating new "
|
||||
"transactions"
|
||||
@@ -1047,25 +1081,25 @@ msgstr ""
|
||||
"As categorias desativadas não poderão ser selecionadas ao criar novas "
|
||||
"transações"
|
||||
|
||||
#: apps/transactions/models.py:78
|
||||
#: apps/transactions/models.py:122
|
||||
msgid "Transaction Category"
|
||||
msgstr "Categoria da Transação"
|
||||
|
||||
#: apps/transactions/models.py:79
|
||||
#: apps/transactions/models.py:123
|
||||
msgid "Transaction Categories"
|
||||
msgstr "Categorias da Trasanção"
|
||||
|
||||
#: apps/transactions/models.py:92
|
||||
#: apps/transactions/models.py:136
|
||||
msgid ""
|
||||
"Deactivated tags won't be able to be selected when creating new transactions"
|
||||
msgstr ""
|
||||
"As tags desativadas não poderão ser selecionadas ao criar novas transações"
|
||||
|
||||
#: apps/transactions/models.py:97 apps/transactions/models.py:98
|
||||
#: apps/transactions/models.py:141 apps/transactions/models.py:142
|
||||
msgid "Transaction Tags"
|
||||
msgstr "Tags da Transação"
|
||||
|
||||
#: apps/transactions/models.py:111
|
||||
#: apps/transactions/models.py:155
|
||||
msgid ""
|
||||
"Deactivated entities won't be able to be selected when creating new "
|
||||
"transactions"
|
||||
@@ -1073,11 +1107,11 @@ msgstr ""
|
||||
"As entidades desativadas não poderão ser selecionadas ao criar novas "
|
||||
"transações"
|
||||
|
||||
#: apps/transactions/models.py:116
|
||||
#: apps/transactions/models.py:160
|
||||
msgid "Entity"
|
||||
msgstr "Entidade"
|
||||
|
||||
#: apps/transactions/models.py:126
|
||||
#: apps/transactions/models.py:170
|
||||
#: templates/calendar_view/fragments/list.html:42
|
||||
#: templates/calendar_view/fragments/list.html:44
|
||||
#: templates/calendar_view/fragments/list.html:52
|
||||
@@ -1087,7 +1121,7 @@ msgstr "Entidade"
|
||||
msgid "Income"
|
||||
msgstr "Renda"
|
||||
|
||||
#: apps/transactions/models.py:127
|
||||
#: apps/transactions/models.py:171
|
||||
#: templates/calendar_view/fragments/list.html:46
|
||||
#: templates/calendar_view/fragments/list.html:48
|
||||
#: templates/calendar_view/fragments/list.html:56
|
||||
@@ -1096,123 +1130,135 @@ msgstr "Renda"
|
||||
msgid "Expense"
|
||||
msgstr "Despesa"
|
||||
|
||||
#: apps/transactions/models.py:181 apps/transactions/models.py:340
|
||||
#: apps/transactions/models.py:225 apps/transactions/models.py:390
|
||||
msgid "Installment Plan"
|
||||
msgstr "Parcelamento"
|
||||
|
||||
#: apps/transactions/models.py:190 apps/transactions/models.py:541
|
||||
#: apps/transactions/models.py:234 apps/transactions/models.py:591
|
||||
msgid "Recurring Transaction"
|
||||
msgstr "Transação Recorrente"
|
||||
|
||||
#: apps/transactions/models.py:198
|
||||
#: apps/transactions/models.py:242
|
||||
msgid "Deleted"
|
||||
msgstr "Apagado"
|
||||
|
||||
#: apps/transactions/models.py:203
|
||||
#: apps/transactions/models.py:247
|
||||
msgid "Deleted At"
|
||||
msgstr "Apagado Em"
|
||||
|
||||
#: apps/transactions/models.py:211
|
||||
#: apps/transactions/models.py:255
|
||||
msgid "Transaction"
|
||||
msgstr "Transação"
|
||||
|
||||
#: apps/transactions/models.py:212 templates/includes/navbar.html:54
|
||||
#: templates/includes/navbar.html:101
|
||||
#: apps/transactions/models.py:256 templates/includes/navbar.html:57
|
||||
#: templates/includes/navbar.html:104
|
||||
#: templates/recurring_transactions/fragments/list_transactions.html:5
|
||||
#: templates/recurring_transactions/fragments/table.html:37
|
||||
#: templates/transactions/pages/transactions.html:5
|
||||
msgid "Transactions"
|
||||
msgstr "Transações"
|
||||
|
||||
#: apps/transactions/models.py:282
|
||||
#: apps/transactions/models.py:323 templates/tags/fragments/table.html:53
|
||||
msgid "No tags"
|
||||
msgstr "Nenhuma tag"
|
||||
|
||||
#: apps/transactions/models.py:324
|
||||
msgid "No category"
|
||||
msgstr "Sem categoria"
|
||||
|
||||
#: apps/transactions/models.py:326
|
||||
msgid "No description"
|
||||
msgstr "Sem descrição"
|
||||
|
||||
#: apps/transactions/models.py:332
|
||||
msgid "Yearly"
|
||||
msgstr "Anual"
|
||||
|
||||
#: apps/transactions/models.py:283 apps/users/models.py:26
|
||||
#: apps/transactions/models.py:333 apps/users/models.py:26
|
||||
#: templates/includes/navbar.html:26
|
||||
msgid "Monthly"
|
||||
msgstr "Mensal"
|
||||
|
||||
#: apps/transactions/models.py:284
|
||||
#: apps/transactions/models.py:334
|
||||
msgid "Weekly"
|
||||
msgstr "Semanal"
|
||||
|
||||
#: apps/transactions/models.py:285
|
||||
#: apps/transactions/models.py:335
|
||||
msgid "Daily"
|
||||
msgstr "Diária"
|
||||
|
||||
#: apps/transactions/models.py:298
|
||||
#: apps/transactions/models.py:348
|
||||
msgid "Number of Installments"
|
||||
msgstr "Número de Parcelas"
|
||||
|
||||
#: apps/transactions/models.py:303
|
||||
#: apps/transactions/models.py:353
|
||||
msgid "Installment Start"
|
||||
msgstr "Parcela inicial"
|
||||
|
||||
#: apps/transactions/models.py:304
|
||||
#: apps/transactions/models.py:354
|
||||
msgid "The installment number to start counting from"
|
||||
msgstr "O número da parcela a partir do qual se inicia a contagem"
|
||||
|
||||
#: apps/transactions/models.py:309 apps/transactions/models.py:524
|
||||
#: apps/transactions/models.py:359 apps/transactions/models.py:574
|
||||
msgid "Start Date"
|
||||
msgstr "Data de Início"
|
||||
|
||||
#: apps/transactions/models.py:313 apps/transactions/models.py:525
|
||||
#: apps/transactions/models.py:363 apps/transactions/models.py:575
|
||||
msgid "End Date"
|
||||
msgstr "Data Final"
|
||||
|
||||
#: apps/transactions/models.py:318
|
||||
#: apps/transactions/models.py:368
|
||||
msgid "Recurrence"
|
||||
msgstr "Recorrência"
|
||||
|
||||
#: apps/transactions/models.py:321
|
||||
#: apps/transactions/models.py:371
|
||||
msgid "Installment Amount"
|
||||
msgstr "Valor da Parcela"
|
||||
|
||||
#: apps/transactions/models.py:341 templates/includes/navbar.html:69
|
||||
#: apps/transactions/models.py:391 templates/includes/navbar.html:72
|
||||
#: templates/installment_plans/fragments/list.html:5
|
||||
#: templates/installment_plans/pages/index.html:4
|
||||
msgid "Installment Plans"
|
||||
msgstr "Parcelamentos"
|
||||
|
||||
#: apps/transactions/models.py:483
|
||||
#: apps/transactions/models.py:533
|
||||
msgid "day(s)"
|
||||
msgstr "dia(s)"
|
||||
|
||||
#: apps/transactions/models.py:484
|
||||
#: apps/transactions/models.py:534
|
||||
msgid "week(s)"
|
||||
msgstr "semana(s)"
|
||||
|
||||
#: apps/transactions/models.py:485
|
||||
#: apps/transactions/models.py:535
|
||||
msgid "month(s)"
|
||||
msgstr "mês(es)"
|
||||
|
||||
#: apps/transactions/models.py:486
|
||||
#: apps/transactions/models.py:536
|
||||
msgid "year(s)"
|
||||
msgstr "ano(s)"
|
||||
|
||||
#: apps/transactions/models.py:488
|
||||
#: apps/transactions/models.py:538
|
||||
#: templates/recurring_transactions/fragments/list.html:24
|
||||
msgid "Paused"
|
||||
msgstr "Pausado"
|
||||
|
||||
#: apps/transactions/models.py:527
|
||||
#: apps/transactions/models.py:577
|
||||
msgid "Recurrence Type"
|
||||
msgstr "Tipo de recorrência"
|
||||
|
||||
#: apps/transactions/models.py:530
|
||||
#: apps/transactions/models.py:580
|
||||
msgid "Recurrence Interval"
|
||||
msgstr "Intervalo de recorrência"
|
||||
|
||||
#: apps/transactions/models.py:534
|
||||
#: apps/transactions/models.py:584
|
||||
msgid "Last Generated Date"
|
||||
msgstr "Última data gerada"
|
||||
|
||||
#: apps/transactions/models.py:537
|
||||
#: apps/transactions/models.py:587
|
||||
msgid "Last Generated Reference Date"
|
||||
msgstr "Última data de referência gerada"
|
||||
|
||||
#: apps/transactions/models.py:542 templates/includes/navbar.html:71
|
||||
#: apps/transactions/models.py:592 templates/includes/navbar.html:74
|
||||
#: templates/recurring_transactions/fragments/list.html:5
|
||||
#: templates/recurring_transactions/pages/index.html:4
|
||||
msgid "Recurring Transactions"
|
||||
@@ -1228,35 +1274,35 @@ msgstr "%(value)s tem muitas casas decimais. O máximo é 30."
|
||||
msgid "%(value)s is not a non-negative number"
|
||||
msgstr "%(value)s não é um número positivo"
|
||||
|
||||
#: apps/transactions/views/actions.py:23
|
||||
#: apps/transactions/views/actions.py:24
|
||||
#, python-format
|
||||
msgid "%(count)s transaction marked as paid"
|
||||
msgid_plural "%(count)s transactions marked as paid"
|
||||
msgstr[0] "%(count)s transação marcada como paga"
|
||||
msgstr[1] "%(count)s transações marcadas como paga"
|
||||
|
||||
#: apps/transactions/views/actions.py:47
|
||||
#: apps/transactions/views/actions.py:48
|
||||
#, python-format
|
||||
msgid "%(count)s transaction marked as not paid"
|
||||
msgid_plural "%(count)s transactions marked as not paid"
|
||||
msgstr[0] "%(count)s transação marcada como não paga"
|
||||
msgstr[1] "%(count)s transações marcadas como não paga"
|
||||
|
||||
#: apps/transactions/views/actions.py:71
|
||||
#: apps/transactions/views/actions.py:72
|
||||
#, python-format
|
||||
msgid "%(count)s transaction deleted successfully"
|
||||
msgid_plural "%(count)s transactions deleted successfully"
|
||||
msgstr[0] "%(count)s transação apagada com sucesso"
|
||||
msgstr[1] "%(count)s transações apagadas com sucesso"
|
||||
|
||||
#: apps/transactions/views/actions.py:95
|
||||
#: apps/transactions/views/actions.py:96
|
||||
#, python-format
|
||||
msgid "%(count)s transaction restored successfully"
|
||||
msgid_plural "%(count)s transactions restored successfully"
|
||||
msgstr[0] "%(count)s transação restaurada com sucesso"
|
||||
msgstr[1] "%(count)s transações restauradas com sucesso"
|
||||
|
||||
#: apps/transactions/views/actions.py:130
|
||||
#: apps/transactions/views/actions.py:131
|
||||
#, python-format
|
||||
msgid "%(count)s transaction duplicated successfully"
|
||||
msgid_plural "%(count)s transactions duplicated successfully"
|
||||
@@ -1517,7 +1563,7 @@ msgstr "Ações"
|
||||
#: templates/account_groups/fragments/list.html:36
|
||||
#: templates/accounts/fragments/list.html:41
|
||||
#: templates/categories/fragments/table.html:29
|
||||
#: templates/cotton/transaction/item.html:127
|
||||
#: templates/cotton/transaction/item.html:130
|
||||
#: templates/cotton/ui/transactions_action_bar.html:49
|
||||
#: templates/currencies/fragments/list.html:37
|
||||
#: templates/dca/fragments/strategy/details.html:67
|
||||
@@ -1539,8 +1585,8 @@ msgstr "Editar"
|
||||
#: templates/account_groups/fragments/list.html:43
|
||||
#: templates/accounts/fragments/list.html:48
|
||||
#: templates/categories/fragments/table.html:36
|
||||
#: templates/cotton/transaction/item.html:142
|
||||
#: templates/cotton/transaction/item.html:161
|
||||
#: templates/cotton/transaction/item.html:145
|
||||
#: templates/cotton/transaction/item.html:164
|
||||
#: templates/cotton/ui/deleted_transactions_action_bar.html:55
|
||||
#: templates/cotton/ui/transactions_action_bar.html:86
|
||||
#: templates/currencies/fragments/list.html:44
|
||||
@@ -1565,8 +1611,8 @@ msgstr "Apagar"
|
||||
#: templates/account_groups/fragments/list.html:47
|
||||
#: templates/accounts/fragments/list.html:52
|
||||
#: templates/categories/fragments/table.html:41
|
||||
#: templates/cotton/transaction/item.html:146
|
||||
#: templates/cotton/transaction/item.html:165
|
||||
#: templates/cotton/transaction/item.html:149
|
||||
#: templates/cotton/transaction/item.html:168
|
||||
#: templates/cotton/ui/deleted_transactions_action_bar.html:57
|
||||
#: templates/cotton/ui/transactions_action_bar.html:88
|
||||
#: templates/currencies/fragments/list.html:48
|
||||
@@ -1594,8 +1640,8 @@ msgstr "Tem certeza?"
|
||||
#: templates/account_groups/fragments/list.html:48
|
||||
#: templates/accounts/fragments/list.html:53
|
||||
#: templates/categories/fragments/table.html:42
|
||||
#: templates/cotton/transaction/item.html:147
|
||||
#: templates/cotton/transaction/item.html:166
|
||||
#: templates/cotton/transaction/item.html:150
|
||||
#: templates/cotton/transaction/item.html:169
|
||||
#: templates/cotton/ui/deleted_transactions_action_bar.html:58
|
||||
#: templates/cotton/ui/transactions_action_bar.html:89
|
||||
#: templates/currencies/fragments/list.html:49
|
||||
@@ -1616,8 +1662,8 @@ msgstr "Você não será capaz de reverter isso!"
|
||||
#: templates/account_groups/fragments/list.html:49
|
||||
#: templates/accounts/fragments/list.html:54
|
||||
#: templates/categories/fragments/table.html:43
|
||||
#: templates/cotton/transaction/item.html:148
|
||||
#: templates/cotton/transaction/item.html:167
|
||||
#: templates/cotton/transaction/item.html:151
|
||||
#: templates/cotton/transaction/item.html:170
|
||||
#: templates/currencies/fragments/list.html:50
|
||||
#: templates/dca/fragments/strategy/details.html:82
|
||||
#: templates/dca/fragments/strategy/list.html:48
|
||||
@@ -1668,7 +1714,7 @@ msgstr "Editar conta"
|
||||
msgid "Is Asset"
|
||||
msgstr "É ativo"
|
||||
|
||||
#: templates/accounts/fragments/list.html:70
|
||||
#: templates/accounts/fragments/list.html:69
|
||||
msgid "No accounts"
|
||||
msgstr "Nenhuma conta"
|
||||
|
||||
@@ -1746,12 +1792,16 @@ msgstr "Buscar"
|
||||
msgid "Select"
|
||||
msgstr "Selecionar"
|
||||
|
||||
#: templates/cotton/transaction/item.html:134
|
||||
#: templates/cotton/transaction/item.html:56
|
||||
msgid "DCA"
|
||||
msgstr "CMP"
|
||||
|
||||
#: templates/cotton/transaction/item.html:137
|
||||
#: templates/cotton/ui/transactions_action_bar.html:78
|
||||
msgid "Duplicate"
|
||||
msgstr "Duplicar"
|
||||
|
||||
#: templates/cotton/transaction/item.html:155
|
||||
#: templates/cotton/transaction/item.html:158
|
||||
#: templates/cotton/ui/deleted_transactions_action_bar.html:47
|
||||
msgid "Restore"
|
||||
msgstr "Restaurar"
|
||||
@@ -2052,7 +2102,7 @@ msgid "Edit exchange rate"
|
||||
msgstr "Editar taxa de câmbio"
|
||||
|
||||
#: templates/exchange_rates/fragments/list.html:25
|
||||
#: templates/includes/navbar.html:58
|
||||
#: templates/includes/navbar.html:61
|
||||
#: templates/installment_plans/fragments/list.html:21
|
||||
#: templates/yearly_overview/pages/overview_by_account.html:92
|
||||
#: templates/yearly_overview/pages/overview_by_currency.html:94
|
||||
@@ -2082,7 +2132,7 @@ msgstr "Navegação por página"
|
||||
|
||||
#: templates/exchange_rates_services/fragments/list.html:6
|
||||
#: templates/exchange_rates_services/pages/index.html:4
|
||||
#: templates/includes/navbar.html:133
|
||||
#: templates/includes/navbar.html:136
|
||||
msgid "Automatic Exchange Rates"
|
||||
msgstr "Taxas de Câmbio Automáticas"
|
||||
|
||||
@@ -2221,52 +2271,56 @@ msgstr "Patrimônio"
|
||||
msgid "Current"
|
||||
msgstr "Atual"
|
||||
|
||||
#: templates/includes/navbar.html:63
|
||||
#: templates/includes/navbar.html:50
|
||||
msgid "Insights"
|
||||
msgstr "Insights"
|
||||
|
||||
#: templates/includes/navbar.html:66
|
||||
msgid "Trash Can"
|
||||
msgstr "Lixeira"
|
||||
|
||||
#: templates/includes/navbar.html:79
|
||||
#: templates/includes/navbar.html:82
|
||||
msgid "Tools"
|
||||
msgstr "Ferramentas"
|
||||
|
||||
#: templates/includes/navbar.html:83
|
||||
#: templates/includes/navbar.html:86
|
||||
msgid "Dollar Cost Average Tracker"
|
||||
msgstr "Rastreador de Custo Médio Ponderado"
|
||||
|
||||
#: templates/includes/navbar.html:86
|
||||
#: templates/includes/navbar.html:89
|
||||
#: templates/mini_tools/unit_price_calculator.html:5
|
||||
#: templates/mini_tools/unit_price_calculator.html:10
|
||||
msgid "Unit Price Calculator"
|
||||
msgstr "Calculadora de preço unitário"
|
||||
|
||||
#: templates/includes/navbar.html:89
|
||||
#: templates/includes/navbar.html:92
|
||||
#: templates/mini_tools/currency_converter/currency_converter.html:8
|
||||
#: templates/mini_tools/currency_converter/currency_converter.html:15
|
||||
msgid "Currency Converter"
|
||||
msgstr "Conversor de Moeda"
|
||||
|
||||
#: templates/includes/navbar.html:98
|
||||
#: templates/includes/navbar.html:101
|
||||
msgid "Management"
|
||||
msgstr "Gerenciar"
|
||||
|
||||
#: templates/includes/navbar.html:127
|
||||
#: templates/includes/navbar.html:130
|
||||
msgid "Automation"
|
||||
msgstr "Automação"
|
||||
|
||||
#: templates/includes/navbar.html:129 templates/rules/fragments/list.html:5
|
||||
#: templates/includes/navbar.html:132 templates/rules/fragments/list.html:5
|
||||
#: templates/rules/pages/index.html:4
|
||||
msgid "Rules"
|
||||
msgstr "Regras"
|
||||
|
||||
#: templates/includes/navbar.html:143
|
||||
#: templates/includes/navbar.html:146
|
||||
msgid "Only use this if you know what you're doing"
|
||||
msgstr "Só use isso se você souber o que está fazendo"
|
||||
|
||||
#: templates/includes/navbar.html:144
|
||||
#: templates/includes/navbar.html:147
|
||||
msgid "Django Admin"
|
||||
msgstr "Django Admin"
|
||||
|
||||
#: templates/includes/navbar.html:153
|
||||
#: templates/includes/navbar.html:156
|
||||
msgid "Calculator"
|
||||
msgstr "Calculadora"
|
||||
|
||||
@@ -2299,6 +2353,44 @@ msgstr "Cancelar"
|
||||
msgid "Confirm"
|
||||
msgstr "Confirmar"
|
||||
|
||||
#: templates/insights/fragments/sankey.html:83
|
||||
msgid "From"
|
||||
msgstr "De"
|
||||
|
||||
#: templates/insights/fragments/sankey.html:86
|
||||
msgid "Percentage"
|
||||
msgstr "Porcentagem"
|
||||
|
||||
#: templates/insights/pages/index.html:33
|
||||
msgid "Month"
|
||||
msgstr "Mês"
|
||||
|
||||
#: templates/insights/pages/index.html:36
|
||||
#: templates/yearly_overview/pages/overview_by_account.html:61
|
||||
#: templates/yearly_overview/pages/overview_by_currency.html:63
|
||||
msgid "Year"
|
||||
msgstr "Ano"
|
||||
|
||||
#: templates/insights/pages/index.html:39
|
||||
msgid "Month Range"
|
||||
msgstr "Intervalo de Mês"
|
||||
|
||||
#: templates/insights/pages/index.html:42
|
||||
msgid "Year Range"
|
||||
msgstr "Intervalo de Ano"
|
||||
|
||||
#: templates/insights/pages/index.html:45
|
||||
msgid "Date Range"
|
||||
msgstr "Intervalo de Data"
|
||||
|
||||
#: templates/insights/pages/index.html:74
|
||||
msgid "Account Flow"
|
||||
msgstr "Fluxo de Conta"
|
||||
|
||||
#: templates/insights/pages/index.html:81
|
||||
msgid "Currency Flow"
|
||||
msgstr "Fluxo de Moeda"
|
||||
|
||||
#: templates/installment_plans/fragments/add.html:5
|
||||
msgid "Add installment plan"
|
||||
msgstr "Adicionar parcelamento"
|
||||
@@ -2605,10 +2697,6 @@ msgstr "Adicionar tag"
|
||||
msgid "Edit tag"
|
||||
msgstr "Editar tag"
|
||||
|
||||
#: templates/tags/fragments/table.html:53
|
||||
msgid "No tags"
|
||||
msgstr "Nenhuma tag"
|
||||
|
||||
#: templates/transactions/fragments/add.html:5
|
||||
#: templates/transactions/pages/add.html:5
|
||||
msgid "New transaction"
|
||||
@@ -2673,10 +2761,10 @@ msgstr "Mostrar valores"
|
||||
msgid "Yearly Overview"
|
||||
msgstr "Visão Anual"
|
||||
|
||||
#: templates/yearly_overview/pages/overview_by_account.html:61
|
||||
#: templates/yearly_overview/pages/overview_by_currency.html:63
|
||||
msgid "Year"
|
||||
msgstr "Ano"
|
||||
#, fuzzy
|
||||
#~| msgid "Tags"
|
||||
#~ msgid "No Tags"
|
||||
#~ msgstr "Tags"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Start Date"
|
||||
@@ -2872,9 +2960,6 @@ msgstr "Ano"
|
||||
#~ msgid "Is an asset account?"
|
||||
#~ msgstr "É uma conta de ativos?"
|
||||
|
||||
#~ msgid "Month"
|
||||
#~ msgstr "Mês"
|
||||
|
||||
#~ msgid ""
|
||||
#~ "This transaction is part of a Installment Plan, you can't delete it "
|
||||
#~ "directly."
|
||||
|
||||
@@ -57,9 +57,8 @@
|
||||
</td>
|
||||
<td class="col">{{ account.name }}</td>
|
||||
<td class="col">{{ account.group.name }}</td>
|
||||
<td class="col">{{ account.currency }} ({{ account.currency.code }})</td>
|
||||
<td class="col">{% if account.exchange_currency %}{{ account.exchange_currency }} (
|
||||
{{ account.exchange_currency.code }}){% else %}-{% endif %}</td>
|
||||
<td class="col">{{ account.currency }}</td>
|
||||
<td class="col">{% if account.exchange_currency %}{{ account.exchange_currency }}{% else %}-{% endif %}</td>
|
||||
<td class="col">{% if account.is_asset %}<i class="fa-solid fa-solid fa-check text-success"></i>{% endif %}</td>
|
||||
<td class="col">{% if account.is_archived %}<i class="fa-solid fa-solid fa-check text-success"></i>{% endif %}</td>
|
||||
</tr>
|
||||
|
||||
@@ -44,13 +44,16 @@
|
||||
{# Description#}
|
||||
<div class="mb-2 mb-lg-1 text-white tw-text-base">
|
||||
{% spaceless %}
|
||||
<span>{{ transaction.description }}</span>
|
||||
<span class="{% if transaction.description %}me-2{% endif %}">{{ transaction.description }}</span>
|
||||
{% if transaction.installment_plan and transaction.installment_id %}
|
||||
<span
|
||||
class="badge text-bg-secondary ms-2">{{ transaction.installment_id }}/{{ transaction.installment_plan.installment_total_number }}</span>
|
||||
class="badge text-bg-secondary">{{ transaction.installment_id }}/{{ transaction.installment_plan.installment_total_number }}</span>
|
||||
{% endif %}
|
||||
{% if transaction.recurring_transaction %}
|
||||
<span class="text-primary tw-text-xs ms-2"><i class="fa-solid fa-arrows-rotate fa-fw"></i></span>
|
||||
<span class="text-primary tw-text-xs"><i class="fa-solid fa-arrows-rotate fa-fw"></i></span>
|
||||
{% endif %}
|
||||
{% if transaction.dca_expense_entries.all or transaction.dca_income_entries.all %}
|
||||
<span class="badge text-bg-secondary">{% trans 'DCA' %}</span>
|
||||
{% endif %}
|
||||
{% endspaceless %}
|
||||
</div>
|
||||
|
||||
@@ -46,8 +46,11 @@
|
||||
href="{% url 'net_worth_projected' %}">{% translate 'Projected' %}</a></li>
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link {% active_link views='insights_index' %}" href="{% url 'insights_index' %}">{% trans 'Insights' %}</a>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle {% active_link views='installment_plans_index||recurring_trasanctions_index||transactions_all_index' %}"
|
||||
<a class="nav-link dropdown-toggle {% active_link views='installment_plans_index||recurring_trasanctions_index||transactions_all_index||transactions_trash_index' %}"
|
||||
href="#" role="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false">
|
||||
@@ -91,7 +94,7 @@
|
||||
</ul>
|
||||
</li>
|
||||
<li class="nav-item dropdown">
|
||||
<a class="nav-link dropdown-toggle {% active_link views='tags_index||entities_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||entities_index||categories_index||accounts_index||account_groups_index||currencies_index||exchange_rates_index||rules_index||import_profiles_index||automatic_exchange_rates_index' %}"
|
||||
href="#" role="button"
|
||||
data-bs-toggle="dropdown"
|
||||
aria-expanded="false">
|
||||
|
||||
@@ -17,6 +17,10 @@
|
||||
for x in datepickers
|
||||
MonthYearPicker(it)
|
||||
end
|
||||
set datepickers to <.airyearpickerinput/> in me
|
||||
for x in datepickers
|
||||
YearPicker(it)
|
||||
end
|
||||
end
|
||||
end
|
||||
</script>
|
||||
|
||||
120
app/templates/insights/fragments/sankey.html
Normal file
120
app/templates/insights/fragments/sankey.html
Normal file
@@ -0,0 +1,120 @@
|
||||
{% load i18n %}
|
||||
|
||||
{% if type == 'account' %}
|
||||
<div class="show-loading" hx-get="{% url 'insights_sankey_by_account' %}" hx-trigger="updated from:window" hx-swap="outerHTML" hx-include="#picker-form, #picker-type">
|
||||
{% else %}
|
||||
<div class="show-loading" hx-get="{% url 'insights_sankey_by_currency' %}" hx-trigger="updated from:window" hx-swap="outerHTML" hx-include="#picker-form, #picker-type">
|
||||
{% endif %}
|
||||
<div class="chart-container position-relative tw-min-h-[60vh] tw-max-h-[60vh] tw-h-full tw-w-full"
|
||||
id="sankeyContainer"
|
||||
_="init call setupSankeyChart() end">
|
||||
<canvas id="sankeyChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
var data = {{ sankey_data|safe }};
|
||||
|
||||
function setupSankeyChart(chartId = 'sankeyChart') {
|
||||
function formatCurrency(value, currency) {
|
||||
return new Intl.NumberFormat(undefined, {
|
||||
minimumFractionDigits: currency.decimal_places,
|
||||
maximumFractionDigits: currency.decimal_places
|
||||
}).format(value);
|
||||
}
|
||||
|
||||
// Create labels object mapping node IDs to display names
|
||||
const labels = data.nodes.reduce((acc, node) => {
|
||||
acc[node.id] = node.name;
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
// Define colors for each node based on its type
|
||||
const colors = {};
|
||||
data.nodes.forEach(node => {
|
||||
if (node.id.startsWith('income_')) {
|
||||
colors[node.id] = '#4dde80'; // Green for income
|
||||
} else if (node.id.startsWith('expense_')) {
|
||||
colors[node.id] = '#f87171'; // Red for expenses
|
||||
} else {
|
||||
colors[node.id] = '#fbb700'; // Primary for others
|
||||
}
|
||||
});
|
||||
|
||||
// Color getter functions
|
||||
const getColor = (nodeId) => colors[nodeId];
|
||||
const getHover = (nodeId) => colors[nodeId];
|
||||
|
||||
// Format data for Chart.js
|
||||
const chartData = {
|
||||
datasets: [{
|
||||
data: data.flows.map(flow => ({
|
||||
from: flow.from_node,
|
||||
to: flow.to_node,
|
||||
flow: flow.flow
|
||||
})),
|
||||
labels: labels,
|
||||
colorFrom: (c) => getColor(c.dataset.data[c.dataIndex].from),
|
||||
colorTo: (c) => getColor(c.dataset.data[c.dataIndex].to),
|
||||
hoverColorFrom: (c) => getHover(c.dataset.data[c.dataIndex].from),
|
||||
hoverColorTo: (c) => getHover(c.dataset.data[c.dataIndex].to),
|
||||
colorMode: 'gradient',
|
||||
alpha: 0.5,
|
||||
size: 'max',
|
||||
color: "white"
|
||||
}]
|
||||
};
|
||||
|
||||
const config = {
|
||||
type: 'sankey',
|
||||
data: chartData,
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
plugins: {
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: (context) => {
|
||||
const flow = data.flows[context.dataIndex];
|
||||
const fromNode = data.nodes.find(n => n.id === flow.from_node);
|
||||
const toNode = data.nodes.find(n => n.id === flow.to_node);
|
||||
const formattedValue = formatCurrency(flow.original_amount, flow.currency);
|
||||
return [
|
||||
`{% trans 'From' %}: ${fromNode.name}`,
|
||||
`{% trans 'To' %}: ${toNode.name}`,
|
||||
`{% trans 'Amount' %}: ${flow.currency.prefix}${formattedValue}${flow.currency.suffix}`,
|
||||
`{% trans 'Percentage' %}: ${flow.percentage.toFixed(2)}%`
|
||||
];
|
||||
}
|
||||
}
|
||||
},
|
||||
legend: {
|
||||
display: false
|
||||
},
|
||||
title: {
|
||||
display: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// Destroy existing chart if it exists
|
||||
const existingChart = Chart.getChart(chartId);
|
||||
if (existingChart) {
|
||||
existingChart.destroy();
|
||||
}
|
||||
|
||||
// Create new chart
|
||||
var chart = new Chart(
|
||||
document.getElementById(chartId),
|
||||
config
|
||||
);
|
||||
window.addEventListener('resize', () => {
|
||||
chart.resize();
|
||||
});
|
||||
document.addEventListener('fullscreenchange', function () {
|
||||
console.log('oi');
|
||||
chart.resize();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
94
app/templates/insights/pages/index.html
Normal file
94
app/templates/insights/pages/index.html
Normal file
@@ -0,0 +1,94 @@
|
||||
{% extends "layouts/base.html" %}
|
||||
{% load crispy_forms_tags %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row my-3">
|
||||
<div class="col-lg-2 col-md-3 mb-3 mb-md-0">
|
||||
<div class="">
|
||||
<div class="mb-2 w-100 d-lg-inline-flex d-grid gap-2 flex-wrap justify-content-lg-center" role="group"
|
||||
_="on change
|
||||
set type to event.target.value
|
||||
add .tw-hidden to <#picker-form > div:not(.tw-hidden)/>
|
||||
|
||||
if type == 'month'
|
||||
remove .tw-hidden from #month-form
|
||||
end
|
||||
if type == 'year'
|
||||
remove .tw-hidden from #year-form
|
||||
end
|
||||
if type == 'month-range'
|
||||
remove .tw-hidden from #month-range-form
|
||||
end
|
||||
if type == 'year-range'
|
||||
remove .tw-hidden from #year-range-form
|
||||
end
|
||||
if type == 'date-range'
|
||||
remove .tw-hidden from #date-range-form
|
||||
end
|
||||
then trigger updated"
|
||||
id="picker-type">
|
||||
<input type="radio" class="btn-check" name="type" value="month" id="monthradio" autocomplete="off" checked>
|
||||
<label class="btn btn-sm btn-outline-primary flex-grow-1" for="monthradio">{% translate 'Month' %}</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="type" value="year" id="yearradio" autocomplete="off">
|
||||
<label class="btn btn-sm btn-outline-primary flex-grow-1" for="yearradio">{% translate 'Year' %}</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="type" value="month-range" id="monthrangeradio" autocomplete="off">
|
||||
<label class="btn btn-sm btn-outline-primary flex-grow-1" for="monthrangeradio">{% translate 'Month Range' %}</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="type" value="year-range" id="yearrangeradio" autocomplete="off">
|
||||
<label class="btn btn-sm btn-outline-primary flex-grow-1" for="yearrangeradio">{% translate 'Year Range' %}</label>
|
||||
|
||||
<input type="radio" class="btn-check" name="type" value="date-range" id="daterangeradio" autocomplete="off">
|
||||
<label class="btn btn-sm btn-outline-primary flex-grow-1" for="daterangeradio">{% translate 'Date Range' %}</label>
|
||||
</div>
|
||||
<form id="picker-form"
|
||||
_="install init_datepicker
|
||||
on change trigger updated">
|
||||
<div id="month-form" class="">
|
||||
{% crispy month_form %}
|
||||
</div>
|
||||
<div id="year-form" class="tw-hidden">
|
||||
{% crispy year_form %}
|
||||
</div>
|
||||
<div id="month-range-form" class="tw-hidden">
|
||||
{% crispy month_range_form %}
|
||||
</div>
|
||||
<div id="year-range-form" class="tw-hidden">
|
||||
{% crispy year_range_form %}
|
||||
</div>
|
||||
<div id="date-range-form" class="tw-hidden">
|
||||
{% crispy date_range_form %}
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
<hr class="mt-0">
|
||||
<div class="nav flex-column nav-pills" id="v-pills-tab" role="tablist"
|
||||
aria-orientation="vertical">
|
||||
<button class="nav-link" id="v-pills-tab" data-bs-toggle="pill" data-bs-target="#v-pills-content"
|
||||
type="button" role="tab" aria-controls="v-pills-content" aria-selected="false"
|
||||
hx-get="{% url 'insights_sankey_by_account' %}" hx-include="#picker-form, #picker-type"
|
||||
hx-indicator="#tab-content"
|
||||
hx-target="#tab-content">{% trans 'Account Flow' %}
|
||||
</button>
|
||||
<button class="nav-link" id="v-pills-tab" data-bs-toggle="pill" data-bs-target="#v-pills-content"
|
||||
type="button" role="tab" aria-controls="v-pills-content" aria-selected="false"
|
||||
hx-get="{% url 'insights_sankey_by_currency' %}"
|
||||
hx-include="#picker-form, #picker-type"
|
||||
hx-indicator="#tab-content"
|
||||
hx-target="#tab-content">{% trans 'Currency Flow' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9 col-lg-10">
|
||||
<div class="tab-content w-100" id="v-pills-tabContent">
|
||||
<div class="tab-pane fade" id="v-pills-content" role="tabpanel" tabindex="0">
|
||||
<div id="tab-content" class="show-loading"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -256,7 +256,7 @@
|
||||
<div class="col">
|
||||
<c-ui.info-card color="yellow" icon="fa-solid fa-percent" title="{% trans 'Distribution' %}">
|
||||
{% for p in percentages.values %}
|
||||
<p class="tw-text-gray-400 mb-2 {% if not forloop.first %}mt-3{% endif %}">{{ p.currency.name }} ({{ p.currency.code }})</p>
|
||||
<p class="tw-text-gray-400 mb-2 {% if not forloop.first %}mt-3{% endif %}">{{ p.currency.name }}</p>
|
||||
<c-ui.percentage-distribution :percentage="p"></c-ui.percentage-distribution>
|
||||
{% endfor %}
|
||||
</c-ui.info-card>
|
||||
|
||||
@@ -10,6 +10,4 @@ until [ -f /tmp/migrations_complete ]; do
|
||||
sleep 2
|
||||
done
|
||||
|
||||
rm -f /tmp/migrations_complete
|
||||
|
||||
exec python manage.py procrastinate worker
|
||||
|
||||
10
frontend/package-lock.json
generated
10
frontend/package-lock.json
generated
@@ -24,6 +24,7 @@
|
||||
"babel-loader": "^8.2.3",
|
||||
"bootstrap": "^5.3.3",
|
||||
"chart.js": "^4.4.6",
|
||||
"chartjs-chart-sankey": "^0.14.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"core-js": "^3.20.3",
|
||||
@@ -3235,6 +3236,15 @@
|
||||
"pnpm": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chartjs-chart-sankey": {
|
||||
"version": "0.14.0",
|
||||
"resolved": "https://registry.npmjs.org/chartjs-chart-sankey/-/chartjs-chart-sankey-0.14.0.tgz",
|
||||
"integrity": "sha512-MrU3lE73TE9kALy4MjWFlfcwf4R1EN/DBvhHxmv9n4AHap//JLKjlJTLIZwHsUjDsYo0B8PuMkrJODwfirEZUA==",
|
||||
"license": "MIT",
|
||||
"peerDependencies": {
|
||||
"chart.js": ">=3.3.0"
|
||||
}
|
||||
},
|
||||
"node_modules/chokidar": {
|
||||
"version": "3.6.0",
|
||||
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
|
||||
|
||||
@@ -37,6 +37,7 @@
|
||||
"babel-loader": "^8.2.3",
|
||||
"bootstrap": "^5.3.3",
|
||||
"chart.js": "^4.4.6",
|
||||
"chartjs-chart-sankey": "^0.14.0",
|
||||
"clean-webpack-plugin": "^4.0.0",
|
||||
"copy-webpack-plugin": "^11.0.0",
|
||||
"core-js": "^3.20.3",
|
||||
|
||||
@@ -1,2 +1,5 @@
|
||||
import Chart from 'chart.js/auto';
|
||||
import {SankeyController, Flow} from 'chartjs-chart-sankey';
|
||||
|
||||
Chart.register(SankeyController, Flow);
|
||||
window.Chart = Chart;
|
||||
|
||||
@@ -168,3 +168,75 @@ window.MonthYearPicker = function createDynamicDatePicker(element) {
|
||||
}
|
||||
return new AirDatepicker(element, opts);
|
||||
};
|
||||
|
||||
window.YearPicker = function createDynamicDatePicker(element) {
|
||||
let todayButton = {
|
||||
content: element.dataset.nowButtonTxt,
|
||||
onClick: (dp) => {
|
||||
let date = new Date();
|
||||
dp.selectDate(date, {updateTime: true});
|
||||
dp.setViewDate(date);
|
||||
}
|
||||
}
|
||||
|
||||
let isOnMobile = isMobile();
|
||||
|
||||
let baseOpts = {
|
||||
isMobile: isOnMobile,
|
||||
view: 'years',
|
||||
minView: 'years',
|
||||
dateFormat: 'yyyy',
|
||||
autoClose: element.dataset.autoClose === 'true',
|
||||
buttons: element.dataset.clearButton === 'true' ? ['clear', todayButton] : [todayButton],
|
||||
locale: locales[element.dataset.language],
|
||||
onSelect: ({date, formattedDate, datepicker}) => {
|
||||
const _event = new CustomEvent("change", {
|
||||
bubbles: true,
|
||||
});
|
||||
datepicker.$el.dispatchEvent(_event);
|
||||
}
|
||||
};
|
||||
|
||||
const positionConfig = !isOnMobile ? {
|
||||
position({$datepicker, $target, $pointer, done}) {
|
||||
let popper = createPopper($target, $datepicker, {
|
||||
placement: 'bottom',
|
||||
modifiers: [
|
||||
{
|
||||
name: 'flip',
|
||||
options: {
|
||||
padding: {
|
||||
top: 64
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'offset',
|
||||
options: {
|
||||
offset: [0, 20]
|
||||
}
|
||||
},
|
||||
{
|
||||
name: 'arrow',
|
||||
options: {
|
||||
element: $pointer
|
||||
}
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
return function completeHide() {
|
||||
popper.destroy();
|
||||
done();
|
||||
};
|
||||
}
|
||||
} : {};
|
||||
|
||||
let opts = {...baseOpts, ...positionConfig};
|
||||
|
||||
if (element.dataset.value) {
|
||||
opts["selectedDates"] = [new Date(element.dataset.value + "T00:00:00")];
|
||||
opts["startDate"] = [new Date(element.dataset.value + "T00:00:00")];
|
||||
}
|
||||
return new AirDatepicker(element, opts);
|
||||
};
|
||||
|
||||
@@ -3,71 +3,84 @@ import * as Popper from "@popperjs/core";
|
||||
|
||||
|
||||
window.TomSelect = function createDynamicTomSelect(element) {
|
||||
// Basic configuration
|
||||
const config = {
|
||||
plugins: {},
|
||||
// Basic configuration
|
||||
const config = {
|
||||
plugins: {},
|
||||
|
||||
// Extract 'create' option from data attribute
|
||||
create: element.dataset.create === 'true',
|
||||
copyClassesToDropdown: true,
|
||||
allowEmptyOption: element.dataset.allowEmptyOption === 'true',
|
||||
render: {
|
||||
no_results: function () {
|
||||
return `<div class="no-results">${element.dataset.txtNoResults || 'No results...'}</div>`;
|
||||
// Extract 'create' option from data attribute
|
||||
create: element.dataset.create === 'true',
|
||||
copyClassesToDropdown: true,
|
||||
allowEmptyOption: element.dataset.allowEmptyOption === 'true',
|
||||
render: {
|
||||
no_results: function () {
|
||||
return `<div class="no-results">${element.dataset.txtNoResults || 'No results...'}</div>`;
|
||||
},
|
||||
option_create: function (data, escape) {
|
||||
return `<div class="create">${element.dataset.txtCreate || 'Add'} <strong>${escape(data.input)}</strong>…</div>`;
|
||||
},
|
||||
},
|
||||
option_create: function(data, escape) {
|
||||
return `<div class="create">${element.dataset.txtCreate || 'Add'} <strong>${escape(data.input)}</strong>…</div>`;
|
||||
},
|
||||
},
|
||||
|
||||
onInitialize: function () {
|
||||
this.popper = Popper.createPopper(this.control, this.dropdown, {
|
||||
placement: "bottom-start",
|
||||
modifiers: [
|
||||
{
|
||||
name: "sameWidth",
|
||||
enabled: true,
|
||||
fn: ({state}) => {
|
||||
state.styles.popper.width = `${state.rects.reference.width}px`;
|
||||
onInitialize: function () {
|
||||
this.popper = Popper.createPopper(this.control, this.dropdown, {
|
||||
placement: "bottom-start",
|
||||
modifiers: [
|
||||
{
|
||||
name: "sameWidth",
|
||||
enabled: true,
|
||||
fn: ({state}) => {
|
||||
state.styles.popper.width = `${state.rects.reference.width}px`;
|
||||
},
|
||||
phase: "beforeWrite",
|
||||
requires: ["computeStyles"],
|
||||
},
|
||||
phase: "beforeWrite",
|
||||
requires: ["computeStyles"],
|
||||
},
|
||||
{
|
||||
name: 'flip',
|
||||
options: {
|
||||
fallbackPlacements: ['top-start'],
|
||||
{
|
||||
name: 'flip',
|
||||
options: {
|
||||
fallbackPlacements: ['top-start'],
|
||||
},
|
||||
},
|
||||
},
|
||||
]
|
||||
]
|
||||
|
||||
});
|
||||
});
|
||||
|
||||
},
|
||||
onDropdownOpen: function () {
|
||||
this.popper.update();
|
||||
}
|
||||
};
|
||||
},
|
||||
onDropdownOpen: function () {
|
||||
this.popper.update();
|
||||
}
|
||||
};
|
||||
|
||||
if (element.dataset.checkboxes === 'true') {
|
||||
config.plugins.checkbox_options = {
|
||||
if (element.dataset.checkboxes === 'true') {
|
||||
config.plugins.checkbox_options = {
|
||||
'checkedClassNames': ['ts-checked'],
|
||||
'uncheckedClassNames': ['ts-unchecked'],
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (element.dataset.clearButton === 'true') {
|
||||
config.plugins.clear_button = {
|
||||
if (element.dataset.clearButton === 'true') {
|
||||
config.plugins.clear_button = {
|
||||
'title': element.dataset.txtClear || 'Clear',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
if (element.dataset.removeButton === 'true') {
|
||||
config.plugins.remove_button = {
|
||||
if (element.dataset.removeButton === 'true') {
|
||||
config.plugins.remove_button = {
|
||||
'title': element.dataset.txtRemove || 'Remove',
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Create and return the TomSelect instance
|
||||
return new TomSelect(element, config);
|
||||
if (element.dataset.load) {
|
||||
config.load = function (query, callback) {
|
||||
let url = element.dataset.load + '?q=' + encodeURIComponent(query);
|
||||
fetch(url)
|
||||
.then(response => response.json())
|
||||
.then(json => {
|
||||
callback(json);
|
||||
}).catch(() => {
|
||||
callback();
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
// Create and return the TomSelect instance
|
||||
return new TomSelect(element, config);
|
||||
};
|
||||
|
||||
@@ -78,6 +78,7 @@
|
||||
.show-loading.htmx-request {
|
||||
position: relative;
|
||||
top: 0;
|
||||
min-height: 100px;
|
||||
|
||||
&::before {
|
||||
content: "";
|
||||
|
||||
@@ -50,7 +50,7 @@ select[multiple] {
|
||||
padding: 0 8px !important;
|
||||
}
|
||||
|
||||
.transaction:has(input[type="checkbox"]:checked) > .transaction-item {
|
||||
.transaction:has(input[type="checkbox"]:checked) > div > .transaction-item {
|
||||
background-color: $primary-bg-subtle-dark;
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user