mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-03-17 23:13:57 +01: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
|
||||
|
||||
|
||||
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():
|
||||
# Get all currencies and date range in a single query
|
||||
aggregates = Transaction.objects.aggregate(
|
||||
|
||||
@@ -9,11 +9,24 @@ from apps.net_worth.utils.calculate_net_worth import (
|
||||
calculate_account_net_worth,
|
||||
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):
|
||||
currency_net_worth = calculate_currency_net_worth()
|
||||
account_net_worth = calculate_account_net_worth()
|
||||
transactions_queryset = Transaction.objects.filter(is_paid=True).order_by(
|
||||
"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()
|
||||
|
||||
@@ -83,7 +96,7 @@ def net_worth_main(request):
|
||||
request,
|
||||
"net_worth/net_worth.html",
|
||||
{
|
||||
"currency_net_worth": currency_net_worth.values(),
|
||||
"currency_net_worth": currency_net_worth,
|
||||
"account_net_worth": account_net_worth,
|
||||
"chart_data_currency_json": chart_data_currency_json,
|
||||
"currencies": currencies,
|
||||
|
||||
@@ -21,29 +21,29 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<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 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>
|
||||
<c-amount.display
|
||||
:amount="currency.amount"
|
||||
:prefix="currency.prefix"
|
||||
:suffix="currency.suffix"
|
||||
:decimal_places="currency.decimal_places"
|
||||
color="{% if currency.amount > 0 %}green{% elif currency.amount < 0 %}red{% endif %}"
|
||||
:amount="currency.total_current"
|
||||
:prefix="currency.currency.prefix"
|
||||
:suffix="currency.currency.suffix"
|
||||
:decimal_places="currency.currency.decimal_places"
|
||||
color="{% if currency.total_current > 0 %}green{% elif currency.total_current < 0 %}red{% endif %}"
|
||||
text-end></c-amount.display>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if currency.exchanged and currency.exchanged.amount %}
|
||||
{% if currency.exchanged and currency.exchanged.total_current %}
|
||||
<div>
|
||||
<c-amount.display
|
||||
:amount="currency.exchanged.amount"
|
||||
:prefix="currency.exchanged.prefix"
|
||||
:suffix="currency.exchanged.suffix"
|
||||
:decimal_places="currency.exchanged.decimal_places"
|
||||
:amount="currency.exchanged.total_current"
|
||||
:prefix="currency.exchanged.currency.prefix"
|
||||
:suffix="currency.exchanged.currency.suffix"
|
||||
:decimal_places="currency.exchanged.currency.decimal_places"
|
||||
text-end
|
||||
color="grey"></c-amount.display>
|
||||
</div>
|
||||
@@ -60,62 +60,63 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<h5 class="tw-text-blue-400 fw-bold mb-3">{% translate 'By account' %}</h5>
|
||||
{% for group_id, group_data in account_net_worth.items %}
|
||||
{% if group_id %}
|
||||
{% regroup account_net_worth.values by account.group as account_data %}
|
||||
{% for data in account_data %}
|
||||
{% if data.grouper %}
|
||||
<div class="d-flex justify-content-between mt-2">
|
||||
<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">
|
||||
{{ group_data.name }}</span></div>
|
||||
{{ data.grouper }}</span></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 align-items-baseline w-100">
|
||||
<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="">
|
||||
<c-amount.display
|
||||
:amount="account_data.balance"
|
||||
:prefix="account_data.currency.prefix"
|
||||
:suffix="account_data.currency.suffix"
|
||||
:decimal_places="account_data.currency.decimal_places"
|
||||
color="{% if account_data.balance > 0 %}green{% elif account_data.balance < 0 %}red{% endif %}"></c-amount.display>
|
||||
:amount="account.total_final"
|
||||
:prefix="account.currency.prefix"
|
||||
:suffix="account.currency.suffix"
|
||||
:decimal_places="account.currency.decimal_places"
|
||||
color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if account_data.exchange %}
|
||||
{% if account.exchanged and account.exchanged.total_final %}
|
||||
<c-amount.display
|
||||
:amount="account_data.exchange.amount"
|
||||
:prefix="account_data.exchange.prefix"
|
||||
:suffix="account_data.exchange.suffix"
|
||||
:decimal_places="account_data.exchange.decimal_places"
|
||||
:amount="account.exchanged.total_final"
|
||||
:prefix="account.exchanged.currency.prefix"
|
||||
:suffix="account.exchanged.currency.suffix"
|
||||
:decimal_places="account.exchanged.currency.decimal_places"
|
||||
color="grey"
|
||||
text-end></c-amount.display>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% 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 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>
|
||||
<c-amount.display
|
||||
:amount="account_data.balance"
|
||||
:prefix="account_data.currency.prefix"
|
||||
:suffix="account_data.currency.suffix"
|
||||
:decimal_places="account_data.currency.decimal_places"
|
||||
color="{% if account_data.balance > 0 %}green{% elif account_data.balance < 0 %}red{% endif %}"></c-amount.display>
|
||||
:amount="account.total_final"
|
||||
:prefix="account.currency.prefix"
|
||||
:suffix="account.currency.suffix"
|
||||
:decimal_places="account.currency.decimal_places"
|
||||
color="{% if account.total_final > 0 %}green{% elif account.total_final < 0 %}red{% endif %}"></c-amount.display>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if account_data.exchange %}
|
||||
{% if account.exchanged and account.exchanged.total_final %}
|
||||
<c-amount.display
|
||||
:amount="account_data.exchange.amount"
|
||||
:prefix="account_data.exchange.prefix"
|
||||
:suffix="account_data.exchange.suffix"
|
||||
:decimal_places="account_data.exchange.decimal_places"
|
||||
:amount="account.exchanged.total_final"
|
||||
:prefix="account.exchanged.currency.prefix"
|
||||
:suffix="account.exchanged.currency.suffix"
|
||||
:decimal_places="account.exchanged.currency.decimal_places"
|
||||
color="grey"
|
||||
text-end></c-amount.display>
|
||||
{% endif %}
|
||||
|
||||
Reference in New Issue
Block a user