mirror of
https://github.com/eitchtee/WYGIWYH.git
synced 2026-11-13 12:23:44 +01:00
Currencies and accounts get a "Show exchanged amounts" switch, on by default. Turning it off hides the amounts converted to the exchange currency without removing the exchange currency, so consolidated totals on the Net Worth page keep working. The switch belongs to whichever one provides the exchange currency: - Transactions (including both legs of combined transfers) use the account's switch when the account has its own exchange currency, and the currency's switch otherwise, matching how the exchanged amount is picked. - Account cards and Net Worth account rows follow the account. - Currency cards, the monthly summary and daily allowance, Net Worth currency rows and the month-by-month and year-by-year insights follow the currency. Currency totals still convert hidden currencies, because the consolidated totals are built from those values; the setting only leaves them out of the displayed data. The API keeps returning exchanged_amount for transactions, and the account serializer exposes the new field (currencies already serialize all fields). Closes #486
150 lines
5.4 KiB
Python
150 lines
5.4 KiB
Python
from copy import copy
|
|
from decimal import Decimal
|
|
|
|
from django.core.exceptions import ValidationError as DjangoValidationError
|
|
from django.db.models import Q
|
|
from rest_framework import serializers
|
|
from rest_framework.permissions import IsAuthenticated
|
|
|
|
from apps.accounts.models import AccountGroup, Account, InvestmentClass
|
|
from apps.api.serializers.currencies import CurrencySerializer
|
|
from apps.currencies.models import Currency
|
|
|
|
|
|
class AccountGroupSerializer(serializers.ModelSerializer):
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
class Meta:
|
|
model = AccountGroup
|
|
fields = "__all__"
|
|
|
|
|
|
class InvestmentClassSerializer(serializers.ModelSerializer):
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
class Meta:
|
|
model = InvestmentClass
|
|
fields = ["id", "name", "target"]
|
|
|
|
def validate(self, attrs):
|
|
instance = copy(self.instance) if self.instance else InvestmentClass()
|
|
for name, value in attrs.items():
|
|
setattr(instance, name, value)
|
|
try:
|
|
instance.clean() # unique name and targets up to 100%
|
|
except DjangoValidationError as e:
|
|
raise serializers.ValidationError(e.message_dict)
|
|
return attrs
|
|
|
|
|
|
class AccountSerializer(serializers.ModelSerializer):
|
|
group = AccountGroupSerializer(read_only=True)
|
|
group_id = serializers.PrimaryKeyRelatedField(
|
|
queryset=AccountGroup.objects.all(),
|
|
source="group",
|
|
write_only=True,
|
|
allow_null=True,
|
|
)
|
|
|
|
currency = CurrencySerializer(read_only=True)
|
|
currency_id = serializers.PrimaryKeyRelatedField(
|
|
queryset=Currency.objects.all(), source="currency", write_only=True
|
|
)
|
|
exchange_currency = CurrencySerializer(read_only=True)
|
|
exchange_currency_id = serializers.PrimaryKeyRelatedField(
|
|
queryset=Currency.objects.all(),
|
|
source="exchange_currency",
|
|
write_only=True,
|
|
allow_null=True,
|
|
)
|
|
|
|
investment_class = InvestmentClassSerializer(read_only=True)
|
|
investment_class_id = serializers.PrimaryKeyRelatedField(
|
|
queryset=InvestmentClass.objects.all(),
|
|
source="investment_class",
|
|
write_only=True,
|
|
allow_null=True,
|
|
required=False,
|
|
)
|
|
|
|
permission_classes = [IsAuthenticated]
|
|
|
|
class Meta:
|
|
model = Account
|
|
fields = [
|
|
"id",
|
|
"name",
|
|
"group",
|
|
"group_id",
|
|
"currency",
|
|
"currency_id",
|
|
"exchange_currency",
|
|
"exchange_currency_id",
|
|
"show_exchanged_amounts",
|
|
"is_asset",
|
|
"is_investment",
|
|
"investment_class",
|
|
"investment_class_id",
|
|
"income_entities",
|
|
"income_categories",
|
|
"income_tags",
|
|
]
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
super().__init__(*args, **kwargs)
|
|
request = self.context.get("request")
|
|
if request and request.user.is_authenticated:
|
|
# Reload the queryset to get an updated version with the requesting user
|
|
self.fields["group_id"].queryset = AccountGroup.objects.all()
|
|
self.fields["investment_class_id"].queryset = InvestmentClass.objects.all()
|
|
|
|
|
|
class AccountBalanceSerializer(serializers.Serializer):
|
|
"""Serializer for account balance response."""
|
|
|
|
current_balance = serializers.DecimalField(max_digits=20, decimal_places=10)
|
|
projected_balance = serializers.DecimalField(max_digits=20, decimal_places=10)
|
|
currency = CurrencySerializer()
|
|
|
|
|
|
|
|
class InvestmentPerformanceSerializer(serializers.Serializer):
|
|
"""Serializer for an investment account's performance."""
|
|
|
|
currency = CurrencySerializer()
|
|
priced = serializers.BooleanField()
|
|
quantity = serializers.DecimalField(max_digits=42, decimal_places=30)
|
|
price = serializers.DecimalField(max_digits=42, decimal_places=30, allow_null=True)
|
|
price_date = serializers.DateTimeField(allow_null=True)
|
|
average_cost = serializers.DecimalField(
|
|
max_digits=42, decimal_places=30, allow_null=True
|
|
)
|
|
cost_basis = serializers.DecimalField(max_digits=42, decimal_places=30)
|
|
market_value = serializers.DecimalField(
|
|
max_digits=42, decimal_places=30, allow_null=True
|
|
)
|
|
unrealized = serializers.DecimalField(
|
|
max_digits=42, decimal_places=30, allow_null=True
|
|
)
|
|
realized = serializers.DecimalField(max_digits=42, decimal_places=30)
|
|
income = serializers.DecimalField(max_digits=42, decimal_places=30)
|
|
contributed = serializers.DecimalField(max_digits=42, decimal_places=30)
|
|
withdrawn = serializers.DecimalField(max_digits=42, decimal_places=30)
|
|
total_return = serializers.DecimalField(max_digits=42, decimal_places=30)
|
|
return_percentage = serializers.DecimalField(
|
|
max_digits=42, decimal_places=30, allow_null=True
|
|
)
|
|
estimated = serializers.BooleanField()
|
|
money_weighted_return = serializers.SerializerMethodField(
|
|
help_text="Return weighted by when money went in and out, in percent: "
|
|
"annual (XIRR) when the first contribution is a year old or more, "
|
|
"otherwise over the period held."
|
|
)
|
|
money_weighted_annualized = serializers.SerializerMethodField()
|
|
|
|
def get_money_weighted_return(self, perf) -> Decimal | None:
|
|
return perf.money_weighted and perf.money_weighted["percentage"]
|
|
|
|
def get_money_weighted_annualized(self, perf) -> bool | None:
|
|
return perf.money_weighted and perf.money_weighted["annualized"]
|