mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-07-10 06:42:49 +02:00
feat(insights:category-overview): display entities on table
This commit is contained in:
@@ -9,7 +9,9 @@ from apps.currencies.models import Currency
|
|||||||
from apps.currencies.utils.convert import convert
|
from apps.currencies.utils.convert import convert
|
||||||
|
|
||||||
|
|
||||||
def get_categories_totals(transactions_queryset, ignore_empty=False):
|
def get_categories_totals(
|
||||||
|
transactions_queryset, ignore_empty=False, show_entities=False
|
||||||
|
):
|
||||||
# First get the category totals as before
|
# First get the category totals as before
|
||||||
category_currency_metrics = (
|
category_currency_metrics = (
|
||||||
transactions_queryset.values(
|
transactions_queryset.values(
|
||||||
@@ -240,6 +242,7 @@ def get_categories_totals(transactions_queryset, ignore_empty=False):
|
|||||||
result[category_id]["tags"][tag_key] = {
|
result[category_id]["tags"][tag_key] = {
|
||||||
"name": tag_name,
|
"name": tag_name,
|
||||||
"currencies": {},
|
"currencies": {},
|
||||||
|
"entities": {},
|
||||||
}
|
}
|
||||||
|
|
||||||
currency_id = tag_metric["account__currency"]
|
currency_id = tag_metric["account__currency"]
|
||||||
@@ -319,4 +322,173 @@ def get_categories_totals(transactions_queryset, ignore_empty=False):
|
|||||||
currency_id
|
currency_id
|
||||||
] = tag_currency_data
|
] = tag_currency_data
|
||||||
|
|
||||||
|
if show_entities:
|
||||||
|
entity_metrics = transactions_queryset.values(
|
||||||
|
"category",
|
||||||
|
"tags",
|
||||||
|
"entities",
|
||||||
|
"entities__name",
|
||||||
|
"account__currency",
|
||||||
|
"account__currency__code",
|
||||||
|
"account__currency__name",
|
||||||
|
"account__currency__decimal_places",
|
||||||
|
"account__currency__prefix",
|
||||||
|
"account__currency__suffix",
|
||||||
|
"account__currency__exchange_currency",
|
||||||
|
).annotate(
|
||||||
|
expense_current=Coalesce(
|
||||||
|
Sum(
|
||||||
|
Case(
|
||||||
|
When(
|
||||||
|
type=Transaction.Type.EXPENSE, is_paid=True, then="amount"
|
||||||
|
),
|
||||||
|
default=Value(0),
|
||||||
|
output_field=models.DecimalField(),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Decimal("0"),
|
||||||
|
),
|
||||||
|
expense_projected=Coalesce(
|
||||||
|
Sum(
|
||||||
|
Case(
|
||||||
|
When(
|
||||||
|
type=Transaction.Type.EXPENSE, is_paid=False, then="amount"
|
||||||
|
),
|
||||||
|
default=Value(0),
|
||||||
|
output_field=models.DecimalField(),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Decimal("0"),
|
||||||
|
),
|
||||||
|
income_current=Coalesce(
|
||||||
|
Sum(
|
||||||
|
Case(
|
||||||
|
When(type=Transaction.Type.INCOME, is_paid=True, then="amount"),
|
||||||
|
default=Value(0),
|
||||||
|
output_field=models.DecimalField(),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Decimal("0"),
|
||||||
|
),
|
||||||
|
income_projected=Coalesce(
|
||||||
|
Sum(
|
||||||
|
Case(
|
||||||
|
When(
|
||||||
|
type=Transaction.Type.INCOME, is_paid=False, then="amount"
|
||||||
|
),
|
||||||
|
default=Value(0),
|
||||||
|
output_field=models.DecimalField(),
|
||||||
|
)
|
||||||
|
),
|
||||||
|
Decimal("0"),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
for entity_metric in entity_metrics:
|
||||||
|
category_id = entity_metric["category"]
|
||||||
|
tag_id = entity_metric["tags"]
|
||||||
|
entity_id = entity_metric["entities"]
|
||||||
|
|
||||||
|
if not entity_id:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if category_id in result:
|
||||||
|
tag_key = tag_id if tag_id is not None else "untagged"
|
||||||
|
if tag_key in result[category_id]["tags"]:
|
||||||
|
entity_key = entity_id
|
||||||
|
entity_name = entity_metric["entities__name"]
|
||||||
|
|
||||||
|
if "entities" not in result[category_id]["tags"][tag_key]:
|
||||||
|
result[category_id]["tags"][tag_key]["entities"] = {}
|
||||||
|
|
||||||
|
if (
|
||||||
|
entity_key
|
||||||
|
not in result[category_id]["tags"][tag_key]["entities"]
|
||||||
|
):
|
||||||
|
result[category_id]["tags"][tag_key]["entities"][entity_key] = {
|
||||||
|
"name": entity_name,
|
||||||
|
"currencies": {},
|
||||||
|
}
|
||||||
|
|
||||||
|
currency_id = entity_metric["account__currency"]
|
||||||
|
|
||||||
|
entity_total_current = (
|
||||||
|
entity_metric["income_current"]
|
||||||
|
- entity_metric["expense_current"]
|
||||||
|
)
|
||||||
|
entity_total_projected = (
|
||||||
|
entity_metric["income_projected"]
|
||||||
|
- entity_metric["expense_projected"]
|
||||||
|
)
|
||||||
|
entity_total_income = (
|
||||||
|
entity_metric["income_current"]
|
||||||
|
+ entity_metric["income_projected"]
|
||||||
|
)
|
||||||
|
entity_total_expense = (
|
||||||
|
entity_metric["expense_current"]
|
||||||
|
+ entity_metric["expense_projected"]
|
||||||
|
)
|
||||||
|
entity_total_final = entity_total_current + entity_total_projected
|
||||||
|
|
||||||
|
entity_currency_data = {
|
||||||
|
"currency": {
|
||||||
|
"code": entity_metric["account__currency__code"],
|
||||||
|
"name": entity_metric["account__currency__name"],
|
||||||
|
"decimal_places": entity_metric[
|
||||||
|
"account__currency__decimal_places"
|
||||||
|
],
|
||||||
|
"prefix": entity_metric["account__currency__prefix"],
|
||||||
|
"suffix": entity_metric["account__currency__suffix"],
|
||||||
|
},
|
||||||
|
"expense_current": entity_metric["expense_current"],
|
||||||
|
"expense_projected": entity_metric["expense_projected"],
|
||||||
|
"total_expense": entity_total_expense,
|
||||||
|
"income_current": entity_metric["income_current"],
|
||||||
|
"income_projected": entity_metric["income_projected"],
|
||||||
|
"total_income": entity_total_income,
|
||||||
|
"total_current": entity_total_current,
|
||||||
|
"total_projected": entity_total_projected,
|
||||||
|
"total_final": entity_total_final,
|
||||||
|
}
|
||||||
|
|
||||||
|
if entity_metric["account__currency__exchange_currency"]:
|
||||||
|
from_currency = Currency.objects.get(id=currency_id)
|
||||||
|
exchange_currency = Currency.objects.get(
|
||||||
|
id=entity_metric["account__currency__exchange_currency"]
|
||||||
|
)
|
||||||
|
|
||||||
|
exchanged = {}
|
||||||
|
for field in [
|
||||||
|
"expense_current",
|
||||||
|
"expense_projected",
|
||||||
|
"income_current",
|
||||||
|
"income_projected",
|
||||||
|
"total_income",
|
||||||
|
"total_expense",
|
||||||
|
"total_current",
|
||||||
|
"total_projected",
|
||||||
|
"total_final",
|
||||||
|
]:
|
||||||
|
amount, prefix, suffix, decimal_places = convert(
|
||||||
|
amount=entity_currency_data[field],
|
||||||
|
from_currency=from_currency,
|
||||||
|
to_currency=exchange_currency,
|
||||||
|
)
|
||||||
|
if amount is not None:
|
||||||
|
exchanged[field] = amount
|
||||||
|
if "currency" not in exchanged:
|
||||||
|
exchanged["currency"] = {
|
||||||
|
"prefix": prefix,
|
||||||
|
"suffix": suffix,
|
||||||
|
"decimal_places": decimal_places,
|
||||||
|
"code": exchange_currency.code,
|
||||||
|
"name": exchange_currency.name,
|
||||||
|
}
|
||||||
|
if exchanged:
|
||||||
|
entity_currency_data["exchanged"] = exchanged
|
||||||
|
|
||||||
|
result[category_id]["tags"][tag_key]["entities"][entity_key][
|
||||||
|
"currencies"
|
||||||
|
][currency_id] = entity_currency_data
|
||||||
|
|
||||||
return result
|
return result
|
||||||
|
|||||||
@@ -180,6 +180,14 @@ def category_overview(request):
|
|||||||
else:
|
else:
|
||||||
show_tags = request.session.get("insights_category_explorer_show_tags", True)
|
show_tags = request.session.get("insights_category_explorer_show_tags", True)
|
||||||
|
|
||||||
|
if "show_entities" in request.GET:
|
||||||
|
show_entities = request.GET["show_entities"] == "on"
|
||||||
|
request.session["insights_category_explorer_show_entities"] = show_entities
|
||||||
|
else:
|
||||||
|
show_entities = request.session.get(
|
||||||
|
"insights_category_explorer_show_entities", False
|
||||||
|
)
|
||||||
|
|
||||||
if "showing" in request.GET:
|
if "showing" in request.GET:
|
||||||
showing = request.GET["showing"]
|
showing = request.GET["showing"]
|
||||||
request.session["insights_category_explorer_showing"] = showing
|
request.session["insights_category_explorer_showing"] = showing
|
||||||
@@ -190,7 +198,9 @@ def category_overview(request):
|
|||||||
transactions = get_transactions(request, include_silent=True)
|
transactions = get_transactions(request, include_silent=True)
|
||||||
|
|
||||||
total_table = get_categories_totals(
|
total_table = get_categories_totals(
|
||||||
transactions_queryset=transactions, ignore_empty=False
|
transactions_queryset=transactions,
|
||||||
|
ignore_empty=False,
|
||||||
|
show_entities=show_entities,
|
||||||
)
|
)
|
||||||
|
|
||||||
return render(
|
return render(
|
||||||
@@ -200,6 +210,7 @@ def category_overview(request):
|
|||||||
"total_table": total_table,
|
"total_table": total_table,
|
||||||
"view_type": view_type,
|
"view_type": view_type,
|
||||||
"show_tags": show_tags,
|
"show_tags": show_tags,
|
||||||
|
"show_entities": show_entities,
|
||||||
"showing": showing,
|
"showing": showing,
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
|
|
||||||
<div hx-get="{% url 'category_overview' %}" hx-trigger="updated from:window" class="show-loading" hx-swap="outerHTML"
|
<div hx-get="{% url 'category_overview' %}" hx-trigger="updated from:window" class="show-loading" hx-swap="outerHTML"
|
||||||
hx-include="#picker-form, #picker-type, #view-type, #show-tags, #showing">
|
hx-include="#picker-form, #picker-type, #view-type, #show-tags, #showing, #show-entities">
|
||||||
<div class="h-100 text-center mb-4">
|
<div class="h-100 text-center mb-4">
|
||||||
<div class="btn-group gap-3" role="group" id="view-type" _="on change trigger updated">
|
<div class="btn-group gap-3" role="group" id="view-type" _="on change trigger updated">
|
||||||
<input type="radio" class="btn-check"
|
<input type="radio" class="btn-check"
|
||||||
@@ -25,19 +25,34 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="mt-3 mb-1 d-flex flex-column flex-md-row justify-content-between">
|
<div class="mt-3 mb-1 d-flex flex-column flex-md-row justify-content-between">
|
||||||
<div class="form-check form-switch" id="show-tags">
|
<div class="d-flex gap-4">
|
||||||
{% if view_type == 'table' %}
|
{% if view_type == 'table' %}
|
||||||
<input type="hidden" name="show_tags" value="off">
|
<div class="form-check form-switch" id="show-tags">
|
||||||
<input class="form-check-input" type="checkbox" role="switch" id="show-tags-switch" name="show_tags"
|
<input type="hidden" name="show_tags" value="off">
|
||||||
_="on change trigger updated" {% if show_tags %}checked{% endif %}>
|
<input class="form-check-input" type="checkbox" role="switch" id="show-tags-switch" name="show_tags"
|
||||||
{% spaceless %}
|
_="on change trigger updated" {% if show_tags %}checked{% endif %}>
|
||||||
<label class="form-check-label" for="show-tags-switch">
|
{% spaceless %}
|
||||||
{% trans 'Tags' %}
|
<label class="form-check-label" for="show-tags-switch">
|
||||||
</label>
|
{% trans 'Tags' %}
|
||||||
<c-ui.help-icon
|
</label>
|
||||||
content="{% trans 'Transaction amounts associated with multiple tags will be counted once for each tag' %}"
|
<c-ui.help-icon
|
||||||
icon="fa-solid fa-circle-exclamation"></c-ui.help-icon>
|
content="{% trans 'Transaction amounts associated with multiple tags will be counted once for each tag' %}"
|
||||||
{% endspaceless %}
|
icon="fa-solid fa-circle-exclamation"></c-ui.help-icon>
|
||||||
|
{% endspaceless %}
|
||||||
|
</div>
|
||||||
|
<div class="form-check form-switch" id="show-entities" {% if not show_tags %}style="display: none;"{% endif %}>
|
||||||
|
<input type="hidden" name="show_entities" value="off">
|
||||||
|
<input class="form-check-input" type="checkbox" role="switch" id="show-entities-switch" name="show_entities"
|
||||||
|
_="on change trigger updated" {% if show_entities %}checked{% endif %}>
|
||||||
|
{% spaceless %}
|
||||||
|
<label class="form-check-label" for="show-entities-switch">
|
||||||
|
{% trans 'Entities' %}
|
||||||
|
</label>
|
||||||
|
<c-ui.help-icon
|
||||||
|
content="{% trans 'Transaction amounts associated with multiple tags and entities will be counted once for each tag' %}"
|
||||||
|
icon="fa-solid fa-circle-exclamation"></c-ui.help-icon>
|
||||||
|
{% endspaceless %}
|
||||||
|
</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
</div>
|
</div>
|
||||||
<div class="btn-group btn-group-sm" role="group" id="showing" _="on change trigger updated">
|
<div class="btn-group btn-group-sm" role="group" id="showing" _="on change trigger updated">
|
||||||
@@ -250,6 +265,100 @@
|
|||||||
{% endfor %}
|
{% endfor %}
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
|
<!-- Entity rows -->
|
||||||
|
{% if show_entities %}
|
||||||
|
{% for entity_id, entity in tag.entities.items %}
|
||||||
|
<tr class="table-row-nested-2">
|
||||||
|
<td class="ps-5">
|
||||||
|
<i class="fa-solid fa-user-group fa-fw me-2 text-muted"></i>{{ entity.name }}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% for currency in entity.currencies.values %}
|
||||||
|
{% if showing == 'current' and currency.income_current != 0 %}
|
||||||
|
<c-amount.display
|
||||||
|
:amount="currency.income_current"
|
||||||
|
:prefix="currency.currency.prefix"
|
||||||
|
:suffix="currency.currency.suffix"
|
||||||
|
:decimal_places="currency.currency.decimal_places"
|
||||||
|
color="green"></c-amount.display>
|
||||||
|
{% elif showing == 'projected' and currency.income_projected != 0 %}
|
||||||
|
<c-amount.display
|
||||||
|
:amount="currency.income_projected"
|
||||||
|
:prefix="currency.currency.prefix"
|
||||||
|
:suffix="currency.currency.suffix"
|
||||||
|
:decimal_places="currency.currency.decimal_places"
|
||||||
|
color="green"></c-amount.display>
|
||||||
|
{% elif showing == 'final' and currency.total_income != 0 %}
|
||||||
|
<c-amount.display
|
||||||
|
:amount="currency.total_income"
|
||||||
|
:prefix="currency.currency.prefix"
|
||||||
|
:suffix="currency.currency.suffix"
|
||||||
|
:decimal_places="currency.currency.decimal_places"
|
||||||
|
color="green"></c-amount.display>
|
||||||
|
{% else %}
|
||||||
|
<div>-</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% for currency in entity.currencies.values %}
|
||||||
|
{% if showing == 'current' and currency.expense_current != 0 %}
|
||||||
|
<c-amount.display
|
||||||
|
:amount="currency.expense_current"
|
||||||
|
:prefix="currency.currency.prefix"
|
||||||
|
:suffix="currency.currency.suffix"
|
||||||
|
:decimal_places="currency.currency.decimal_places"
|
||||||
|
color="red"></c-amount.display>
|
||||||
|
{% elif showing == 'projected' and currency.expense_projected != 0 %}
|
||||||
|
<c-amount.display
|
||||||
|
:amount="currency.expense_projected"
|
||||||
|
:prefix="currency.currency.prefix"
|
||||||
|
:suffix="currency.currency.suffix"
|
||||||
|
:decimal_places="currency.currency.decimal_places"
|
||||||
|
color="red"></c-amount.display>
|
||||||
|
{% elif showing == 'final' and currency.total_expense != 0 %}
|
||||||
|
<c-amount.display
|
||||||
|
:amount="currency.total_expense"
|
||||||
|
:prefix="currency.currency.prefix"
|
||||||
|
:suffix="currency.currency.suffix"
|
||||||
|
:decimal_places="currency.currency.decimal_places"
|
||||||
|
color="red"></c-amount.display>
|
||||||
|
{% else %}
|
||||||
|
<div>-</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
<td>
|
||||||
|
{% for currency in entity.currencies.values %}
|
||||||
|
{% if showing == 'current' and currency.total_current != 0 %}
|
||||||
|
<c-amount.display
|
||||||
|
:amount="currency.total_current"
|
||||||
|
:prefix="currency.currency.prefix"
|
||||||
|
:suffix="currency.currency.suffix"
|
||||||
|
:decimal_places="currency.currency.decimal_places"
|
||||||
|
color="{% if currency.total_final < 0 %}red{% else %}green{% endif %}"></c-amount.display>
|
||||||
|
{% elif showing == 'projected' and currency.total_projected != 0 %}
|
||||||
|
<c-amount.display
|
||||||
|
:amount="currency.total_projected"
|
||||||
|
:prefix="currency.currency.prefix"
|
||||||
|
:suffix="currency.currency.suffix"
|
||||||
|
:decimal_places="currency.currency.decimal_places"
|
||||||
|
color="{% if currency.total_final < 0 %}red{% else %}green{% endif %}"></c-amount.display>
|
||||||
|
{% elif showing == 'final' and currency.total_final != 0 %}
|
||||||
|
<c-amount.display
|
||||||
|
:amount="currency.total_final"
|
||||||
|
:prefix="currency.currency.prefix"
|
||||||
|
:suffix="currency.currency.suffix"
|
||||||
|
:decimal_places="currency.currency.decimal_places"
|
||||||
|
color="{% if currency.total_final < 0 %}red{% else %}green{% endif %}"></c-amount.display>
|
||||||
|
{% else %}
|
||||||
|
<div>-</div>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user