mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-07-08 13:55:21 +02:00
feat: improve calculations for net_worth
This commit is contained in:
@@ -19,133 +19,6 @@ from apps.currencies.utils.convert import convert
|
|||||||
from apps.transactions.models import Transaction
|
from apps.transactions.models import Transaction
|
||||||
|
|
||||||
|
|
||||||
def calculate_account_net_worth():
|
|
||||||
ungrouped_id = None # Special ID for ungrouped accounts
|
|
||||||
|
|
||||||
# Subquery to calculate balance for each account
|
|
||||||
balance_subquery = (
|
|
||||||
Transaction.objects.filter(account=OuterRef("pk"), is_paid=True)
|
|
||||||
.values("account")
|
|
||||||
.annotate(
|
|
||||||
balance=Sum(
|
|
||||||
Case(
|
|
||||||
When(type=Transaction.Type.INCOME, then=F("amount")),
|
|
||||||
When(type=Transaction.Type.EXPENSE, then=-F("amount")),
|
|
||||||
default=0,
|
|
||||||
output_field=DecimalField(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.values("balance")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Main query to fetch all account data
|
|
||||||
accounts_data = (
|
|
||||||
Account.objects.filter(is_archived=False)
|
|
||||||
.annotate(balance=Coalesce(Subquery(balance_subquery), Decimal("0")))
|
|
||||||
.select_related("currency", "exchange_currency", "group")
|
|
||||||
)
|
|
||||||
|
|
||||||
account_net_worth = {ungrouped_id: {"name": _("Ungrouped"), "accounts": {}}}
|
|
||||||
|
|
||||||
for account in accounts_data:
|
|
||||||
account_data = {
|
|
||||||
"name": account.name,
|
|
||||||
"balance": account.balance,
|
|
||||||
"currency": {
|
|
||||||
"code": account.currency.code,
|
|
||||||
"name": account.currency.name,
|
|
||||||
"prefix": account.currency.prefix,
|
|
||||||
"suffix": account.currency.suffix,
|
|
||||||
"decimal_places": account.currency.decimal_places,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
if account.exchange_currency:
|
|
||||||
converted_amount, prefix, suffix, decimal_places = convert(
|
|
||||||
amount=account.balance,
|
|
||||||
from_currency=account.currency,
|
|
||||||
to_currency=account.exchange_currency,
|
|
||||||
)
|
|
||||||
if converted_amount:
|
|
||||||
account_data["exchange"] = {
|
|
||||||
"amount": converted_amount,
|
|
||||||
"prefix": prefix,
|
|
||||||
"suffix": suffix,
|
|
||||||
"decimal_places": decimal_places,
|
|
||||||
}
|
|
||||||
|
|
||||||
group_id = account.group.id if account.group else ungrouped_id
|
|
||||||
group_name = account.group.name if account.group else _("Ungrouped")
|
|
||||||
|
|
||||||
if group_id not in account_net_worth:
|
|
||||||
account_net_worth[group_id] = {"name": group_name, "accounts": {}}
|
|
||||||
|
|
||||||
account_net_worth[group_id]["accounts"][account.id] = account_data
|
|
||||||
|
|
||||||
# Remove the "Ungrouped" category if it's empty
|
|
||||||
if not account_net_worth[ungrouped_id]["accounts"]:
|
|
||||||
del account_net_worth[ungrouped_id]
|
|
||||||
|
|
||||||
return account_net_worth
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_currency_net_worth():
|
|
||||||
# Subquery to calculate balance for each currency
|
|
||||||
balance_subquery = (
|
|
||||||
Transaction.objects.filter(account__currency=OuterRef("pk"), is_paid=True)
|
|
||||||
.values("account__currency")
|
|
||||||
.annotate(
|
|
||||||
balance=Sum(
|
|
||||||
Case(
|
|
||||||
When(type=Transaction.Type.INCOME, then=F("amount")),
|
|
||||||
When(type=Transaction.Type.EXPENSE, then=-F("amount")),
|
|
||||||
default=0,
|
|
||||||
output_field=DecimalField(),
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
.values("balance")
|
|
||||||
)
|
|
||||||
|
|
||||||
# Fetch all currencies with their balances in a single query
|
|
||||||
currencies_data = Currency.objects.annotate(
|
|
||||||
balance=Coalesce(Subquery(balance_subquery), Decimal("0"))
|
|
||||||
).select_related(
|
|
||||||
"exchange_currency"
|
|
||||||
) # Optimize by pre-fetching exchange_currency
|
|
||||||
|
|
||||||
net_worth = {}
|
|
||||||
for currency in currencies_data:
|
|
||||||
# Skip conversion if no exchange currency is set
|
|
||||||
exchanged_value = None
|
|
||||||
if currency.exchange_currency:
|
|
||||||
exchanged_amount, ex_prefix, ex_suffix, ex_decimal_places = convert(
|
|
||||||
amount=currency.balance,
|
|
||||||
from_currency=currency,
|
|
||||||
to_currency=currency.exchange_currency,
|
|
||||||
)
|
|
||||||
exchanged_value = {
|
|
||||||
"amount": exchanged_amount,
|
|
||||||
"name": currency.exchange_currency.name,
|
|
||||||
"prefix": ex_prefix,
|
|
||||||
"suffix": ex_suffix,
|
|
||||||
"decimal_places": ex_decimal_places,
|
|
||||||
}
|
|
||||||
|
|
||||||
net_worth[currency.name] = {
|
|
||||||
"amount": currency.balance,
|
|
||||||
"code": currency.code,
|
|
||||||
"name": currency.name,
|
|
||||||
"prefix": currency.prefix,
|
|
||||||
"suffix": currency.suffix,
|
|
||||||
"decimal_places": currency.decimal_places,
|
|
||||||
"exchanged": exchanged_value,
|
|
||||||
}
|
|
||||||
|
|
||||||
return net_worth
|
|
||||||
|
|
||||||
|
|
||||||
def calculate_historical_currency_net_worth():
|
def calculate_historical_currency_net_worth():
|
||||||
# Get all currencies and date range in a single query
|
# Get all currencies and date range in a single query
|
||||||
aggregates = Transaction.objects.aggregate(
|
aggregates = Transaction.objects.aggregate(
|
||||||
|
|||||||
@@ -9,11 +9,24 @@ from apps.net_worth.utils.calculate_net_worth import (
|
|||||||
calculate_account_net_worth,
|
calculate_account_net_worth,
|
||||||
calculate_historical_account_balance,
|
calculate_historical_account_balance,
|
||||||
)
|
)
|
||||||
|
from apps.transactions.models import Transaction
|
||||||
|
from apps.transactions.utils.calculations import (
|
||||||
|
calculate_currency_totals,
|
||||||
|
calculate_account_totals,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def net_worth_main(request):
|
def net_worth_main(request):
|
||||||
currency_net_worth = calculate_currency_net_worth()
|
transactions_queryset = Transaction.objects.filter(is_paid=True).order_by(
|
||||||
account_net_worth = calculate_account_net_worth()
|
"account__group",
|
||||||
|
"account__name",
|
||||||
|
)
|
||||||
|
currency_net_worth = calculate_currency_totals(
|
||||||
|
transactions_queryset=transactions_queryset
|
||||||
|
)
|
||||||
|
account_net_worth = calculate_account_totals(
|
||||||
|
transactions_queryset=transactions_queryset
|
||||||
|
)
|
||||||
|
|
||||||
historical_currency_net_worth = calculate_historical_currency_net_worth()
|
historical_currency_net_worth = calculate_historical_currency_net_worth()
|
||||||
|
|
||||||
@@ -83,7 +96,7 @@ def net_worth_main(request):
|
|||||||
request,
|
request,
|
||||||
"net_worth/net_worth.html",
|
"net_worth/net_worth.html",
|
||||||
{
|
{
|
||||||
"currency_net_worth": currency_net_worth.values(),
|
"currency_net_worth": currency_net_worth,
|
||||||
"account_net_worth": account_net_worth,
|
"account_net_worth": account_net_worth,
|
||||||
"chart_data_currency_json": chart_data_currency_json,
|
"chart_data_currency_json": chart_data_currency_json,
|
||||||
"currencies": currencies,
|
"currencies": currencies,
|
||||||
|
|||||||
@@ -21,29 +21,29 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="tw-text-yellow-400 fw-bold mb-3">{% translate 'By currency' %}</h5>
|
<h5 class="tw-text-yellow-400 fw-bold mb-3">{% translate 'By currency' %}</h5>
|
||||||
{% for currency in currency_net_worth %}
|
{% for currency in currency_net_worth.values %}
|
||||||
<div class="d-flex justify-content-between mt-2">
|
<div class="d-flex justify-content-between mt-2">
|
||||||
<div class="d-flex align-items-baseline w-100">
|
<div class="d-flex align-items-baseline w-100">
|
||||||
<div class="currency-name text-start font-monospace tw-text-gray-300">{{ currency.name }}</div>
|
<div class="currency-name text-start font-monospace tw-text-gray-300">{{ currency.currency.name }}</div>
|
||||||
<div class="dotted-line flex-grow-1"></div>
|
<div class="dotted-line flex-grow-1"></div>
|
||||||
<div>
|
<div>
|
||||||
<c-amount.display
|
<c-amount.display
|
||||||
:amount="currency.amount"
|
:amount="currency.total_current"
|
||||||
:prefix="currency.prefix"
|
:prefix="currency.currency.prefix"
|
||||||
:suffix="currency.suffix"
|
:suffix="currency.currency.suffix"
|
||||||
:decimal_places="currency.decimal_places"
|
:decimal_places="currency.currency.decimal_places"
|
||||||
color="{% if currency.amount > 0 %}green{% elif currency.amount < 0 %}red{% endif %}"
|
color="{% if currency.total_current > 0 %}green{% elif currency.total_current < 0 %}red{% endif %}"
|
||||||
text-end></c-amount.display>
|
text-end></c-amount.display>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if currency.exchanged and currency.exchanged.amount %}
|
{% if currency.exchanged and currency.exchanged.total_current %}
|
||||||
<div>
|
<div>
|
||||||
<c-amount.display
|
<c-amount.display
|
||||||
:amount="currency.exchanged.amount"
|
:amount="currency.exchanged.total_current"
|
||||||
:prefix="currency.exchanged.prefix"
|
:prefix="currency.exchanged.currency.prefix"
|
||||||
:suffix="currency.exchanged.suffix"
|
:suffix="currency.exchanged.currency.suffix"
|
||||||
:decimal_places="currency.exchanged.decimal_places"
|
:decimal_places="currency.exchanged.currency.decimal_places"
|
||||||
text-end
|
text-end
|
||||||
color="grey"></c-amount.display>
|
color="grey"></c-amount.display>
|
||||||
</div>
|
</div>
|
||||||
@@ -60,62 +60,63 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
<h5 class="tw-text-blue-400 fw-bold mb-3">{% translate 'By account' %}</h5>
|
<h5 class="tw-text-blue-400 fw-bold mb-3">{% translate 'By account' %}</h5>
|
||||||
{% for group_id, group_data in account_net_worth.items %}
|
{% regroup account_net_worth.values by account.group as account_data %}
|
||||||
{% if group_id %}
|
{% for data in account_data %}
|
||||||
|
{% if data.grouper %}
|
||||||
<div class="d-flex justify-content-between mt-2">
|
<div class="d-flex justify-content-between mt-2">
|
||||||
<div class="d-flex align-items-baseline w-100">
|
<div class="d-flex align-items-baseline w-100">
|
||||||
<div class="text-start font-monospace tw-text-gray-300"><span class="badge text-bg-primary">
|
<div class="text-start font-monospace tw-text-gray-300"><span class="badge text-bg-primary">
|
||||||
{{ group_data.name }}</span></div>
|
{{ data.grouper }}</span></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% for account_id, account_data in group_data.accounts.items %}
|
{% for account in data.list %}
|
||||||
<div class="d-flex justify-content-between mt-2">
|
<div class="d-flex justify-content-between mt-2">
|
||||||
<div class="d-flex align-items-baseline w-100">
|
<div class="d-flex align-items-baseline w-100">
|
||||||
<div class="text-start font-monospace tw-text-gray-300">
|
<div class="text-start font-monospace tw-text-gray-300">
|
||||||
<span class="hierarchy-line-icon"></span>{{ account_data.name }}</div>
|
<span class="hierarchy-line-icon"></span>{{ account.account.name }}</div>
|
||||||
<div class="dotted-line flex-grow-1"></div>
|
<div class="dotted-line flex-grow-1"></div>
|
||||||
<div class="">
|
<div class="">
|
||||||
<c-amount.display
|
<c-amount.display
|
||||||
:amount="account_data.balance"
|
:amount="account.total_final"
|
||||||
:prefix="account_data.currency.prefix"
|
:prefix="account.currency.prefix"
|
||||||
:suffix="account_data.currency.suffix"
|
:suffix="account.currency.suffix"
|
||||||
:decimal_places="account_data.currency.decimal_places"
|
:decimal_places="account.currency.decimal_places"
|
||||||
color="{% if account_data.balance > 0 %}green{% elif account_data.balance < 0 %}red{% endif %}"></c-amount.display>
|
color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if account_data.exchange %}
|
{% if account.exchanged and account.exchanged.total_final %}
|
||||||
<c-amount.display
|
<c-amount.display
|
||||||
:amount="account_data.exchange.amount"
|
:amount="account.exchanged.total_final"
|
||||||
:prefix="account_data.exchange.prefix"
|
:prefix="account.exchanged.currency.prefix"
|
||||||
:suffix="account_data.exchange.suffix"
|
:suffix="account.exchanged.currency.suffix"
|
||||||
:decimal_places="account_data.exchange.decimal_places"
|
:decimal_places="account.exchanged.currency.decimal_places"
|
||||||
color="grey"
|
color="grey"
|
||||||
text-end></c-amount.display>
|
text-end></c-amount.display>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
{% endfor %}
|
{% endfor %}
|
||||||
{% else %}
|
{% else %}
|
||||||
{% for account_id, account_data in group_data.accounts.items %}
|
{% for account in data.list %}
|
||||||
<div class="d-flex justify-content-between mt-2">
|
<div class="d-flex justify-content-between mt-2">
|
||||||
<div class="d-flex align-items-baseline w-100">
|
<div class="d-flex align-items-baseline w-100">
|
||||||
<div class="currency-name text-start font-monospace tw-text-gray-300">{{ account_data.name }}</div>
|
<div class="currency-name text-start font-monospace tw-text-gray-300">{{ account.account.name }}</div>
|
||||||
<div class="dotted-line flex-grow-1"></div>
|
<div class="dotted-line flex-grow-1"></div>
|
||||||
<div>
|
<div>
|
||||||
<c-amount.display
|
<c-amount.display
|
||||||
:amount="account_data.balance"
|
:amount="account.total_final"
|
||||||
:prefix="account_data.currency.prefix"
|
:prefix="account.currency.prefix"
|
||||||
:suffix="account_data.currency.suffix"
|
:suffix="account.currency.suffix"
|
||||||
:decimal_places="account_data.currency.decimal_places"
|
:decimal_places="account.currency.decimal_places"
|
||||||
color="{% if account_data.balance > 0 %}green{% elif account_data.balance < 0 %}red{% endif %}"></c-amount.display>
|
color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
{% if account_data.exchange %}
|
{% if account.exchanged and account.exchanged.total_final %}
|
||||||
<c-amount.display
|
<c-amount.display
|
||||||
:amount="account_data.exchange.amount"
|
:amount="account.exchanged.total_final"
|
||||||
:prefix="account_data.exchange.prefix"
|
:prefix="account.exchanged.currency.prefix"
|
||||||
:suffix="account_data.exchange.suffix"
|
:suffix="account.exchanged.currency.suffix"
|
||||||
:decimal_places="account_data.exchange.decimal_places"
|
:decimal_places="account.exchanged.currency.decimal_places"
|
||||||
color="grey"
|
color="grey"
|
||||||
text-end></c-amount.display>
|
text-end></c-amount.display>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|||||||
Reference in New Issue
Block a user