mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-02-26 01:14:50 +01:00
Compare commits
6 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
84c047c5ab | ||
|
|
23f5d09bec | ||
|
|
2a19075e23 | ||
|
|
7f231175b2 | ||
|
|
062e84f864 | ||
|
|
5521eb20bf |
@@ -8,6 +8,7 @@ from apps.common.widgets.datepicker import (
|
||||
AirYearPickerInput,
|
||||
AirDatePickerInput,
|
||||
)
|
||||
from apps.transactions.models import TransactionCategory
|
||||
|
||||
|
||||
class SingleMonthForm(forms.Form):
|
||||
@@ -108,3 +109,20 @@ class DateRangeForm(forms.Form):
|
||||
css_class="mb-0",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
class CategoryForm(forms.Form):
|
||||
category = forms.ModelChoiceField(
|
||||
required=False,
|
||||
label=_("Category"),
|
||||
queryset=TransactionCategory.objects.filter(active=True),
|
||||
)
|
||||
|
||||
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("category")
|
||||
|
||||
@@ -14,4 +14,19 @@ urlpatterns = [
|
||||
views.sankey_by_currency,
|
||||
name="insights_sankey_by_currency",
|
||||
),
|
||||
path(
|
||||
"insights/category-explorer/",
|
||||
views.category_explorer_index,
|
||||
name="category_explorer_index",
|
||||
),
|
||||
path(
|
||||
"insights/category-explorer/account/",
|
||||
views.category_sum_by_account,
|
||||
name="category_sum_by_account",
|
||||
),
|
||||
path(
|
||||
"insights/category-explorer/currency/",
|
||||
views.category_sum_by_currency,
|
||||
name="category_sum_by_currency",
|
||||
),
|
||||
]
|
||||
|
||||
101
app/apps/insights/utils/category_explorer.py
Normal file
101
app/apps/insights/utils/category_explorer.py
Normal file
@@ -0,0 +1,101 @@
|
||||
from django.db.models import Sum, Case, When, F, DecimalField, Value
|
||||
from django.db.models.functions import Coalesce
|
||||
from django.utils.translation import gettext_lazy as _
|
||||
|
||||
|
||||
def get_category_sums_by_account(queryset, category):
|
||||
"""
|
||||
Returns income/expense sums per account for a specific category.
|
||||
"""
|
||||
sums = (
|
||||
queryset.filter(category=category)
|
||||
.values("account__name")
|
||||
.annotate(
|
||||
income=Coalesce(
|
||||
Sum(
|
||||
Case(
|
||||
When(type="IN", then="amount"),
|
||||
default=Value(0),
|
||||
output_field=DecimalField(max_digits=42, decimal_places=30),
|
||||
)
|
||||
),
|
||||
Value(0),
|
||||
output_field=DecimalField(max_digits=42, decimal_places=30),
|
||||
),
|
||||
expense=Coalesce(
|
||||
Sum(
|
||||
Case(
|
||||
When(type="EX", then=-F("amount")),
|
||||
default=Value(0),
|
||||
output_field=DecimalField(max_digits=42, decimal_places=30),
|
||||
)
|
||||
),
|
||||
Value(0),
|
||||
output_field=DecimalField(max_digits=42, decimal_places=30),
|
||||
),
|
||||
)
|
||||
.order_by("account__name")
|
||||
)
|
||||
|
||||
return {
|
||||
"labels": [item["account__name"] for item in sums],
|
||||
"datasets": [
|
||||
{
|
||||
"label": _("Income"),
|
||||
"data": [float(item["income"]) for item in sums],
|
||||
},
|
||||
{
|
||||
"label": _("Expenses"),
|
||||
"data": [float(item["expense"]) for item in sums],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
|
||||
def get_category_sums_by_currency(queryset, category):
|
||||
"""
|
||||
Returns income/expense sums per currency for a specific category.
|
||||
"""
|
||||
sums = (
|
||||
queryset.filter(category=category)
|
||||
.values("account__currency__code")
|
||||
.annotate(
|
||||
income=Coalesce(
|
||||
Sum(
|
||||
Case(
|
||||
When(type="IN", then="amount"),
|
||||
default=Value(0),
|
||||
output_field=DecimalField(max_digits=42, decimal_places=30),
|
||||
)
|
||||
),
|
||||
Value(0),
|
||||
output_field=DecimalField(max_digits=42, decimal_places=30),
|
||||
),
|
||||
expense=Coalesce(
|
||||
Sum(
|
||||
Case(
|
||||
When(type="EX", then=-F("amount")),
|
||||
default=Value(0),
|
||||
output_field=DecimalField(max_digits=42, decimal_places=30),
|
||||
)
|
||||
),
|
||||
Value(0),
|
||||
output_field=DecimalField(max_digits=42, decimal_places=30),
|
||||
),
|
||||
)
|
||||
.order_by("account__currency__code")
|
||||
)
|
||||
|
||||
return {
|
||||
"labels": [item["account__currency__code"] for item in sums],
|
||||
"datasets": [
|
||||
{
|
||||
"label": _("Income"),
|
||||
"data": [float(item["income"]) for item in sums],
|
||||
},
|
||||
{
|
||||
"label": _("Expenses"),
|
||||
"data": [float(item["expense"]) for item in sums],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -52,13 +52,29 @@ def generate_sankey_data_by_account(transactions_queryset):
|
||||
total_volume_by_currency.get(currency, Decimal("0")) + amount
|
||||
)
|
||||
|
||||
unique_accounts = {
|
||||
account_id: idx
|
||||
for idx, account_id in enumerate(
|
||||
transactions_queryset.values_list("account", flat=True).distinct()
|
||||
)
|
||||
}
|
||||
|
||||
def get_node_priority(node_id: str) -> int:
|
||||
"""Get priority based on the account ID embedded in the node ID."""
|
||||
account_id = int(node_id.split("_")[-1])
|
||||
return unique_accounts[account_id]
|
||||
|
||||
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}
|
||||
"""Add node with ID, display name and priority."""
|
||||
nodes[node_id] = {
|
||||
"id": node_id,
|
||||
"name": display_name,
|
||||
"priority": get_node_priority(node_id),
|
||||
}
|
||||
|
||||
def add_flow(
|
||||
from_node_id: str, to_node_id: str, amount: Decimal, currency, is_income: bool
|
||||
@@ -167,13 +183,29 @@ def generate_sankey_data_by_currency(transactions_queryset):
|
||||
total_volume_by_currency.get(currency, Decimal("0")) + amount
|
||||
)
|
||||
|
||||
unique_currencies = {
|
||||
currency_id: idx
|
||||
for idx, currency_id in enumerate(
|
||||
transactions_queryset.values_list("account__currency", flat=True).distinct()
|
||||
)
|
||||
}
|
||||
|
||||
def get_node_priority(node_id: str) -> int:
|
||||
"""Get priority based on the currency ID embedded in the node ID."""
|
||||
currency_id = int(node_id.split("_")[-1])
|
||||
return unique_currencies[currency_id]
|
||||
|
||||
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}
|
||||
"""Add node with ID, display name and priority."""
|
||||
nodes[node_id] = {
|
||||
"id": node_id,
|
||||
"name": display_name,
|
||||
"priority": get_node_priority(node_id),
|
||||
}
|
||||
|
||||
def add_flow(
|
||||
from_node_id: str, to_node_id: str, amount: Decimal, currency, is_income: bool
|
||||
|
||||
@@ -1,24 +1,28 @@
|
||||
from dateutil.relativedelta import relativedelta
|
||||
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.common.decorators.htmx import only_htmx
|
||||
from apps.insights.forms import (
|
||||
SingleMonthForm,
|
||||
SingleYearForm,
|
||||
MonthRangeForm,
|
||||
YearRangeForm,
|
||||
DateRangeForm,
|
||||
CategoryForm,
|
||||
)
|
||||
from apps.insights.utils.category_explorer import (
|
||||
get_category_sums_by_account,
|
||||
get_category_sums_by_currency,
|
||||
)
|
||||
from apps.insights.utils.sankey import (
|
||||
generate_sankey_data_by_account,
|
||||
generate_sankey_data_by_currency,
|
||||
)
|
||||
from apps.common.decorators.htmx import only_htmx
|
||||
from apps.insights.utils.transactions import get_transactions
|
||||
from apps.transactions.models import TransactionCategory
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -61,7 +65,7 @@ def index(request):
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET", "POST"])
|
||||
@require_http_methods(["GET"])
|
||||
def sankey_by_account(request):
|
||||
# Get filtered transactions
|
||||
|
||||
@@ -79,7 +83,7 @@ def sankey_by_account(request):
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET", "POST"])
|
||||
@require_http_methods(["GET"])
|
||||
def sankey_by_currency(request):
|
||||
# Get filtered transactions
|
||||
transactions = get_transactions(request)
|
||||
@@ -92,3 +96,66 @@ def sankey_by_currency(request):
|
||||
"insights/fragments/sankey.html",
|
||||
{"sankey_data": sankey_data, "type": "currency"},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def category_explorer_index(request):
|
||||
category_form = CategoryForm()
|
||||
|
||||
return render(
|
||||
request,
|
||||
"insights/fragments/category_explorer/index.html",
|
||||
{"category_form": category_form},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def category_sum_by_account(request):
|
||||
# Get filtered transactions
|
||||
transactions = get_transactions(request)
|
||||
|
||||
category = request.GET.get("category")
|
||||
|
||||
if category:
|
||||
category = TransactionCategory.objects.get(id=category)
|
||||
|
||||
# Generate data
|
||||
account_data = get_category_sums_by_account(transactions, category)
|
||||
else:
|
||||
account_data = None
|
||||
|
||||
return render(
|
||||
request,
|
||||
"insights/fragments/category_explorer/charts/account.html",
|
||||
{"account_data": account_data},
|
||||
)
|
||||
|
||||
|
||||
@only_htmx
|
||||
@login_required
|
||||
@require_http_methods(["GET"])
|
||||
def category_sum_by_currency(request):
|
||||
# Get filtered transactions
|
||||
transactions = get_transactions(request)
|
||||
|
||||
category = request.GET.get("category")
|
||||
|
||||
if category:
|
||||
category = TransactionCategory.objects.get(id=category)
|
||||
|
||||
# Generate data
|
||||
currency_data = get_category_sums_by_currency(transactions, category)
|
||||
else:
|
||||
currency_data = None
|
||||
|
||||
print(currency_data)
|
||||
|
||||
return render(
|
||||
request,
|
||||
"insights/fragments/category_explorer/charts/currency.html",
|
||||
{"currency_data": currency_data},
|
||||
)
|
||||
|
||||
@@ -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-16 00:03-0300\n"
|
||||
"POT-Creation-Date: 2025-02-16 13:04-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"
|
||||
@@ -69,12 +69,12 @@ msgid "New balance"
|
||||
msgstr ""
|
||||
|
||||
#: 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
|
||||
#: apps/insights/forms.py:117 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 ""
|
||||
|
||||
@@ -745,12 +745,33 @@ msgstr ""
|
||||
msgid "Run deleted successfully"
|
||||
msgstr ""
|
||||
|
||||
#: apps/insights/utils/sankey.py:37 apps/insights/utils/sankey.py:154
|
||||
#: apps/insights/utils/category_explorer.py:44
|
||||
#: apps/insights/utils/category_explorer.py:93 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
|
||||
#: templates/calendar_view/fragments/list.html:54
|
||||
#: templates/cotton/ui/quick_transactions_buttons.html:10
|
||||
#: templates/insights/fragments/category_explorer/charts/account.html:54
|
||||
#: templates/insights/fragments/category_explorer/charts/currency.html:55
|
||||
#: templates/monthly_overview/fragments/monthly_summary.html:39
|
||||
msgid "Income"
|
||||
msgstr ""
|
||||
|
||||
#: apps/insights/utils/category_explorer.py:48
|
||||
#: apps/insights/utils/category_explorer.py:97
|
||||
#: templates/insights/fragments/category_explorer/charts/account.html:60
|
||||
#: templates/insights/fragments/category_explorer/charts/currency.html:61
|
||||
#: templates/monthly_overview/fragments/monthly_summary.html:103
|
||||
msgid "Expenses"
|
||||
msgstr ""
|
||||
|
||||
#: apps/insights/utils/sankey.py:36 apps/insights/utils/sankey.py:167
|
||||
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
|
||||
#: apps/insights/utils/sankey.py:133 apps/insights/utils/sankey.py:134
|
||||
#: apps/insights/utils/sankey.py:263 apps/insights/utils/sankey.py:264
|
||||
msgid "Saved"
|
||||
msgstr ""
|
||||
|
||||
@@ -770,7 +791,7 @@ msgstr ""
|
||||
msgid "Set field"
|
||||
msgstr ""
|
||||
|
||||
#: apps/rules/forms.py:65 templates/insights/fragments/sankey.html:84
|
||||
#: apps/rules/forms.py:65 templates/insights/fragments/sankey.html:90
|
||||
msgid "To"
|
||||
msgstr ""
|
||||
|
||||
@@ -811,7 +832,7 @@ 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:192
|
||||
#: apps/transactions/models.py:551 templates/insights/fragments/sankey.html:85
|
||||
#: apps/transactions/models.py:551 templates/insights/fragments/sankey.html:91
|
||||
msgid "Amount"
|
||||
msgstr ""
|
||||
|
||||
@@ -1090,16 +1111,6 @@ msgstr ""
|
||||
msgid "Entity"
|
||||
msgstr ""
|
||||
|
||||
#: 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
|
||||
#: templates/calendar_view/fragments/list.html:54
|
||||
#: templates/cotton/ui/quick_transactions_buttons.html:10
|
||||
#: templates/monthly_overview/fragments/monthly_summary.html:39
|
||||
msgid "Income"
|
||||
msgstr ""
|
||||
|
||||
#: apps/transactions/models.py:171
|
||||
#: templates/calendar_view/fragments/list.html:46
|
||||
#: templates/calendar_view/fragments/list.html:48
|
||||
@@ -2328,11 +2339,19 @@ msgstr ""
|
||||
msgid "Confirm"
|
||||
msgstr ""
|
||||
|
||||
#: templates/insights/fragments/sankey.html:83
|
||||
#: templates/insights/fragments/category_explorer/index.html:13
|
||||
msgid "Income/Expense by Account"
|
||||
msgstr ""
|
||||
|
||||
#: templates/insights/fragments/category_explorer/index.html:25
|
||||
msgid "Income/Expense by Currency"
|
||||
msgstr ""
|
||||
|
||||
#: templates/insights/fragments/sankey.html:89
|
||||
msgid "From"
|
||||
msgstr ""
|
||||
|
||||
#: templates/insights/fragments/sankey.html:86
|
||||
#: templates/insights/fragments/sankey.html:92
|
||||
msgid "Percentage"
|
||||
msgstr ""
|
||||
|
||||
@@ -2366,6 +2385,10 @@ msgstr ""
|
||||
msgid "Currency Flow"
|
||||
msgstr ""
|
||||
|
||||
#: templates/insights/pages/index.html:88
|
||||
msgid "Category Explorer"
|
||||
msgstr ""
|
||||
|
||||
#: templates/installment_plans/fragments/add.html:5
|
||||
msgid "Add installment plan"
|
||||
msgstr ""
|
||||
@@ -2464,10 +2487,6 @@ msgstr ""
|
||||
msgid "projected"
|
||||
msgstr ""
|
||||
|
||||
#: templates/monthly_overview/fragments/monthly_summary.html:103
|
||||
msgid "Expenses"
|
||||
msgstr ""
|
||||
|
||||
#: templates/monthly_overview/fragments/monthly_summary.html:167
|
||||
msgid "Total"
|
||||
msgstr ""
|
||||
|
||||
@@ -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: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-02-16 00:03-0300\n"
|
||||
"POT-Creation-Date: 2025-02-16 13:04-0300\n"
|
||||
"PO-Revision-Date: 2025-02-12 06:58+0100\n"
|
||||
"Last-Translator: Dimitri Decrock <dimitri@fam-decrock.eu>\n"
|
||||
"Language-Team: \n"
|
||||
@@ -70,12 +70,12 @@ msgid "New balance"
|
||||
msgstr "Nieuw saldo"
|
||||
|
||||
#: 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
|
||||
#: apps/insights/forms.py:117 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 "Categorie"
|
||||
|
||||
@@ -765,14 +765,35 @@ msgstr "Importrun met succes in de wachtrij geplaatst"
|
||||
msgid "Run deleted successfully"
|
||||
msgstr "Run met succes verwijderd"
|
||||
|
||||
#: apps/insights/utils/sankey.py:37 apps/insights/utils/sankey.py:154
|
||||
#: apps/insights/utils/category_explorer.py:44
|
||||
#: apps/insights/utils/category_explorer.py:93 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
|
||||
#: templates/calendar_view/fragments/list.html:54
|
||||
#: templates/cotton/ui/quick_transactions_buttons.html:10
|
||||
#: templates/insights/fragments/category_explorer/charts/account.html:54
|
||||
#: templates/insights/fragments/category_explorer/charts/currency.html:55
|
||||
#: templates/monthly_overview/fragments/monthly_summary.html:39
|
||||
msgid "Income"
|
||||
msgstr "Ontvangsten Transactie"
|
||||
|
||||
#: apps/insights/utils/category_explorer.py:48
|
||||
#: apps/insights/utils/category_explorer.py:97
|
||||
#: templates/insights/fragments/category_explorer/charts/account.html:60
|
||||
#: templates/insights/fragments/category_explorer/charts/currency.html:61
|
||||
#: templates/monthly_overview/fragments/monthly_summary.html:103
|
||||
msgid "Expenses"
|
||||
msgstr "Uitgaven"
|
||||
|
||||
#: apps/insights/utils/sankey.py:36 apps/insights/utils/sankey.py:167
|
||||
#, fuzzy
|
||||
#| msgid "Categories"
|
||||
msgid "Uncategorized"
|
||||
msgstr "Categorieën"
|
||||
|
||||
#: apps/insights/utils/sankey.py:118 apps/insights/utils/sankey.py:119
|
||||
#: apps/insights/utils/sankey.py:234 apps/insights/utils/sankey.py:235
|
||||
#: apps/insights/utils/sankey.py:133 apps/insights/utils/sankey.py:134
|
||||
#: apps/insights/utils/sankey.py:263 apps/insights/utils/sankey.py:264
|
||||
#, fuzzy
|
||||
#| msgid "Save"
|
||||
msgid "Saved"
|
||||
@@ -794,7 +815,7 @@ msgstr "Als..."
|
||||
msgid "Set field"
|
||||
msgstr "Veld instellen"
|
||||
|
||||
#: apps/rules/forms.py:65 templates/insights/fragments/sankey.html:84
|
||||
#: apps/rules/forms.py:65 templates/insights/fragments/sankey.html:90
|
||||
msgid "To"
|
||||
msgstr "Naar"
|
||||
|
||||
@@ -835,7 +856,7 @@ msgstr "Referentiedatum"
|
||||
|
||||
#: apps/rules/forms.py:165 apps/rules/forms.py:178 apps/rules/models.py:29
|
||||
#: apps/rules/models.py:256 apps/transactions/models.py:192
|
||||
#: apps/transactions/models.py:551 templates/insights/fragments/sankey.html:85
|
||||
#: apps/transactions/models.py:551 templates/insights/fragments/sankey.html:91
|
||||
msgid "Amount"
|
||||
msgstr "Bedrag"
|
||||
|
||||
@@ -1123,16 +1144,6 @@ msgstr ""
|
||||
msgid "Entity"
|
||||
msgstr "Bedrijf"
|
||||
|
||||
#: 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
|
||||
#: templates/calendar_view/fragments/list.html:54
|
||||
#: templates/cotton/ui/quick_transactions_buttons.html:10
|
||||
#: templates/monthly_overview/fragments/monthly_summary.html:39
|
||||
msgid "Income"
|
||||
msgstr "Ontvangsten Transactie"
|
||||
|
||||
#: apps/transactions/models.py:171
|
||||
#: templates/calendar_view/fragments/list.html:46
|
||||
#: templates/calendar_view/fragments/list.html:48
|
||||
@@ -2368,11 +2379,21 @@ msgstr "Annuleer"
|
||||
msgid "Confirm"
|
||||
msgstr "Bevestig"
|
||||
|
||||
#: templates/insights/fragments/sankey.html:83
|
||||
#: templates/insights/fragments/category_explorer/index.html:13
|
||||
msgid "Income/Expense by Account"
|
||||
msgstr ""
|
||||
|
||||
#: templates/insights/fragments/category_explorer/index.html:25
|
||||
#, fuzzy
|
||||
#| msgid "Exchange Currency"
|
||||
msgid "Income/Expense by Currency"
|
||||
msgstr "Eenheid Wisselgeld"
|
||||
|
||||
#: templates/insights/fragments/sankey.html:89
|
||||
msgid "From"
|
||||
msgstr ""
|
||||
|
||||
#: templates/insights/fragments/sankey.html:86
|
||||
#: templates/insights/fragments/sankey.html:92
|
||||
msgid "Percentage"
|
||||
msgstr ""
|
||||
|
||||
@@ -2416,6 +2437,12 @@ msgstr "Rekening"
|
||||
msgid "Currency Flow"
|
||||
msgstr "Munteenheids Code"
|
||||
|
||||
#: templates/insights/pages/index.html:88
|
||||
#, fuzzy
|
||||
#| msgid "Category name"
|
||||
msgid "Category Explorer"
|
||||
msgstr "Naam van categorie"
|
||||
|
||||
#: templates/installment_plans/fragments/add.html:5
|
||||
msgid "Add installment plan"
|
||||
msgstr "Afbetalingsplan toevoegen"
|
||||
@@ -2516,10 +2543,6 @@ msgstr "actueel"
|
||||
msgid "projected"
|
||||
msgstr "verwacht"
|
||||
|
||||
#: templates/monthly_overview/fragments/monthly_summary.html:103
|
||||
msgid "Expenses"
|
||||
msgstr "Uitgaven"
|
||||
|
||||
#: templates/monthly_overview/fragments/monthly_summary.html:167
|
||||
msgid "Total"
|
||||
msgstr "Totaal"
|
||||
@@ -2879,11 +2902,6 @@ msgstr "Jaaroverzicht"
|
||||
#~ msgid "Search Category"
|
||||
#~ msgstr "Categorie"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Category name"
|
||||
#~ msgid "Category Operator"
|
||||
#~ msgstr "Naam van categorie"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Search"
|
||||
#~ msgid "Search Tags"
|
||||
|
||||
@@ -8,8 +8,8 @@ msgid ""
|
||||
msgstr ""
|
||||
"Project-Id-Version: \n"
|
||||
"Report-Msgid-Bugs-To: \n"
|
||||
"POT-Creation-Date: 2025-02-16 00:03-0300\n"
|
||||
"PO-Revision-Date: 2025-02-16 00:04-0300\n"
|
||||
"POT-Creation-Date: 2025-02-16 13:04-0300\n"
|
||||
"PO-Revision-Date: 2025-02-16 13:05-0300\n"
|
||||
"Last-Translator: Herculino Trotta\n"
|
||||
"Language-Team: \n"
|
||||
"Language: pt_BR\n"
|
||||
@@ -70,12 +70,12 @@ msgid "New balance"
|
||||
msgstr "Novo saldo"
|
||||
|
||||
#: 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
|
||||
#: apps/insights/forms.py:117 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"
|
||||
|
||||
@@ -759,12 +759,33 @@ 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
|
||||
#: apps/insights/utils/category_explorer.py:44
|
||||
#: apps/insights/utils/category_explorer.py:93 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
|
||||
#: templates/calendar_view/fragments/list.html:54
|
||||
#: templates/cotton/ui/quick_transactions_buttons.html:10
|
||||
#: templates/insights/fragments/category_explorer/charts/account.html:54
|
||||
#: templates/insights/fragments/category_explorer/charts/currency.html:55
|
||||
#: templates/monthly_overview/fragments/monthly_summary.html:39
|
||||
msgid "Income"
|
||||
msgstr "Renda"
|
||||
|
||||
#: apps/insights/utils/category_explorer.py:48
|
||||
#: apps/insights/utils/category_explorer.py:97
|
||||
#: templates/insights/fragments/category_explorer/charts/account.html:60
|
||||
#: templates/insights/fragments/category_explorer/charts/currency.html:61
|
||||
#: templates/monthly_overview/fragments/monthly_summary.html:103
|
||||
msgid "Expenses"
|
||||
msgstr "Despesas"
|
||||
|
||||
#: apps/insights/utils/sankey.py:36 apps/insights/utils/sankey.py:167
|
||||
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
|
||||
#: apps/insights/utils/sankey.py:133 apps/insights/utils/sankey.py:134
|
||||
#: apps/insights/utils/sankey.py:263 apps/insights/utils/sankey.py:264
|
||||
msgid "Saved"
|
||||
msgstr "Salvo"
|
||||
|
||||
@@ -784,7 +805,7 @@ msgstr "Se..."
|
||||
msgid "Set field"
|
||||
msgstr "Definir campo"
|
||||
|
||||
#: apps/rules/forms.py:65 templates/insights/fragments/sankey.html:84
|
||||
#: apps/rules/forms.py:65 templates/insights/fragments/sankey.html:90
|
||||
msgid "To"
|
||||
msgstr "Para"
|
||||
|
||||
@@ -825,7 +846,7 @@ 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:192
|
||||
#: apps/transactions/models.py:551 templates/insights/fragments/sankey.html:85
|
||||
#: apps/transactions/models.py:551 templates/insights/fragments/sankey.html:91
|
||||
msgid "Amount"
|
||||
msgstr "Quantia"
|
||||
|
||||
@@ -1111,16 +1132,6 @@ msgstr ""
|
||||
msgid "Entity"
|
||||
msgstr "Entidade"
|
||||
|
||||
#: 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
|
||||
#: templates/calendar_view/fragments/list.html:54
|
||||
#: templates/cotton/ui/quick_transactions_buttons.html:10
|
||||
#: templates/monthly_overview/fragments/monthly_summary.html:39
|
||||
msgid "Income"
|
||||
msgstr "Renda"
|
||||
|
||||
#: apps/transactions/models.py:171
|
||||
#: templates/calendar_view/fragments/list.html:46
|
||||
#: templates/calendar_view/fragments/list.html:48
|
||||
@@ -2353,11 +2364,19 @@ msgstr "Cancelar"
|
||||
msgid "Confirm"
|
||||
msgstr "Confirmar"
|
||||
|
||||
#: templates/insights/fragments/sankey.html:83
|
||||
#: templates/insights/fragments/category_explorer/index.html:13
|
||||
msgid "Income/Expense by Account"
|
||||
msgstr "Gasto/Despesa por Conta"
|
||||
|
||||
#: templates/insights/fragments/category_explorer/index.html:25
|
||||
msgid "Income/Expense by Currency"
|
||||
msgstr "Gasto/Despesa por Moeda"
|
||||
|
||||
#: templates/insights/fragments/sankey.html:89
|
||||
msgid "From"
|
||||
msgstr "De"
|
||||
|
||||
#: templates/insights/fragments/sankey.html:86
|
||||
#: templates/insights/fragments/sankey.html:92
|
||||
msgid "Percentage"
|
||||
msgstr "Porcentagem"
|
||||
|
||||
@@ -2391,6 +2410,10 @@ msgstr "Fluxo de Conta"
|
||||
msgid "Currency Flow"
|
||||
msgstr "Fluxo de Moeda"
|
||||
|
||||
#: templates/insights/pages/index.html:88
|
||||
msgid "Category Explorer"
|
||||
msgstr "Explorador de Categoria"
|
||||
|
||||
#: templates/installment_plans/fragments/add.html:5
|
||||
msgid "Add installment plan"
|
||||
msgstr "Adicionar parcelamento"
|
||||
@@ -2491,10 +2514,6 @@ msgstr "atual"
|
||||
msgid "projected"
|
||||
msgstr "previsto"
|
||||
|
||||
#: templates/monthly_overview/fragments/monthly_summary.html:103
|
||||
msgid "Expenses"
|
||||
msgstr "Despesas"
|
||||
|
||||
#: templates/monthly_overview/fragments/monthly_summary.html:167
|
||||
msgid "Total"
|
||||
msgstr "Total"
|
||||
@@ -2856,11 +2875,6 @@ msgstr "Visão Anual"
|
||||
#~ msgid "Search Category"
|
||||
#~ msgstr "Categoria"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Category name"
|
||||
#~ msgid "Category Operator"
|
||||
#~ msgstr "Nome da Categoria"
|
||||
|
||||
#, fuzzy
|
||||
#~| msgid "Search"
|
||||
#~ msgid "Search Tags"
|
||||
|
||||
@@ -0,0 +1,79 @@
|
||||
{% load i18n %}
|
||||
<div class="chart-container" style="position: relative; height:400px; width:100%" _="init call setupAccountChart() end">
|
||||
<canvas id="accountChart"></canvas>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Get the data from your Django view (passed as JSON)
|
||||
var accountData = {{ account_data|safe }};
|
||||
|
||||
function setupAccountChart() {
|
||||
var chartOptions = {
|
||||
indexAxis: 'y', // This makes the chart horizontal
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true, // Enable stacking on the x-axis
|
||||
title: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
stacked: true, // Enable stacking on the y-axis
|
||||
title: {
|
||||
display: false,
|
||||
},
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
if (context.parsed.x !== null) {
|
||||
return new Intl.NumberFormat(undefined).format(Math.abs(context.parsed.x)); // Using abs for display
|
||||
}
|
||||
return "";
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new Chart(
|
||||
document.getElementById('accountChart'),
|
||||
{
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: accountData.labels,
|
||||
datasets: [
|
||||
{
|
||||
label: "{% trans 'Income' %}",
|
||||
data: accountData.datasets[0].data,
|
||||
backgroundColor: '#4dde80',
|
||||
stack: 'stack0'
|
||||
},
|
||||
{
|
||||
label: "{% trans 'Expenses' %}",
|
||||
data: accountData.datasets[1].data,
|
||||
backgroundColor: '#f87171',
|
||||
stack: 'stack0'
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
...chartOptions,
|
||||
plugins: {
|
||||
...chartOptions.plugins,
|
||||
title: {
|
||||
display: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,80 @@
|
||||
{% load i18n %}
|
||||
<div class="chart-container" style="position: relative; height:400px; width:100%"
|
||||
_="init call setupCurrencyChart() end">
|
||||
<canvas id="currencyChart"></canvas>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Get the data from your Django view (passed as JSON)
|
||||
var currencyData = {{ currency_data|safe }};
|
||||
|
||||
function setupCurrencyChart() {
|
||||
var chartOptions = {
|
||||
indexAxis: 'y', // This makes the chart horizontal
|
||||
responsive: true,
|
||||
maintainAspectRatio: false,
|
||||
scales: {
|
||||
x: {
|
||||
stacked: true, // Enable stacking on the x-axis
|
||||
title: {
|
||||
display: false,
|
||||
},
|
||||
},
|
||||
y: {
|
||||
stacked: true, // Enable stacking on the y-axis
|
||||
title: {
|
||||
display: false,
|
||||
},
|
||||
}
|
||||
},
|
||||
plugins: {
|
||||
legend: {
|
||||
display: false,
|
||||
},
|
||||
tooltip: {
|
||||
callbacks: {
|
||||
label: function (context) {
|
||||
if (context.parsed.x !== null) {
|
||||
return new Intl.NumberFormat(undefined).format(Math.abs(context.parsed.x)); // Using abs for display
|
||||
}
|
||||
return "";
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
new Chart(
|
||||
document.getElementById('currencyChart'),
|
||||
{
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: currencyData.labels,
|
||||
datasets: [
|
||||
{
|
||||
label: "{% trans 'Income' %}",
|
||||
data: currencyData.datasets[0].data,
|
||||
backgroundColor: '#4dde80',
|
||||
stack: 'stack0'
|
||||
},
|
||||
{
|
||||
label: "{% trans 'Expenses' %}",
|
||||
data: currencyData.datasets[1].data,
|
||||
backgroundColor: '#f87171',
|
||||
stack: 'stack0'
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
...chartOptions,
|
||||
plugins: {
|
||||
...chartOptions.plugins,
|
||||
title: {
|
||||
display: false,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
</script>
|
||||
@@ -0,0 +1,37 @@
|
||||
{% load i18n %}
|
||||
{% load crispy_forms_tags %}
|
||||
|
||||
<form _="install init_tom_select
|
||||
on change trigger updated" id="category-form">
|
||||
{% crispy category_form %}
|
||||
</form>
|
||||
|
||||
<div class="row row-cols-1 row-cols-lg-2 gx-3 gy-3">
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
{% trans "Income/Expense by Account" %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="account-card" class="show-loading" hx-get="{% url 'category_sum_by_account' %}"
|
||||
hx-trigger="updated from:window" hx-include="#category-form, #picker-form, #picker-type">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
{% trans "Income/Expense by Currency" %}
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div id="currency-card" class="show-loading" hx-get="{% url 'category_sum_by_currency' %}"
|
||||
hx-trigger="updated from:window" hx-include="#category-form, #picker-form, #picker-type">
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
</div>
|
||||
@@ -1,15 +1,17 @@
|
||||
{% 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">
|
||||
<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">
|
||||
<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 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>
|
||||
@@ -61,7 +63,11 @@
|
||||
colorMode: 'gradient',
|
||||
alpha: 0.5,
|
||||
size: 'max',
|
||||
color: "white"
|
||||
color: "white",
|
||||
priority: data.nodes.reduce((acc, node) => {
|
||||
acc[node.id] = node.priority;
|
||||
return acc;
|
||||
}, {}),
|
||||
}]
|
||||
};
|
||||
|
||||
@@ -98,23 +104,10 @@
|
||||
}
|
||||
};
|
||||
|
||||
// Destroy existing chart if it exists
|
||||
const existingChart = Chart.getChart(chartId);
|
||||
if (existingChart) {
|
||||
existingChart.destroy();
|
||||
}
|
||||
|
||||
// Create new chart
|
||||
var chart = new Chart(
|
||||
new Chart(
|
||||
document.getElementById(chartId),
|
||||
config
|
||||
);
|
||||
window.addEventListener('resize', () => {
|
||||
chart.resize();
|
||||
});
|
||||
document.addEventListener('fullscreenchange', function () {
|
||||
console.log('oi');
|
||||
chart.resize();
|
||||
});
|
||||
}
|
||||
</script>
|
||||
|
||||
@@ -80,6 +80,13 @@
|
||||
hx-indicator="#tab-content"
|
||||
hx-target="#tab-content">{% trans 'Currency 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 'category_explorer_index' %}"
|
||||
hx-include="#picker-form, #picker-type"
|
||||
hx-indicator="#tab-content"
|
||||
hx-target="#tab-content">{% trans 'Category Explorer' %}
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-9 col-lg-10">
|
||||
|
||||
Reference in New Issue
Block a user