diff --git a/app/apps/api/fields/__init__.py b/app/apps/api/fields/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/app/apps/api/fields/transactions.py b/app/apps/api/fields/transactions.py new file mode 100644 index 0000000..0225648 --- /dev/null +++ b/app/apps/api/fields/transactions.py @@ -0,0 +1,69 @@ +from drf_spectacular.types import OpenApiTypes +from drf_spectacular.utils import extend_schema_field +from rest_framework import serializers + +from apps.transactions.models import TransactionCategory, TransactionTag + + +@extend_schema_field( + { + "oneOf": [{"type": "string"}, {"type": "integer"}], + "description": "TransactionCategory ID or name. If the name doesn't exist, a new one will be created", + } +) +class TransactionCategoryField(serializers.Field): + def to_representation(self, value): + return {"id": value.id, "name": value.name} + + def to_internal_value(self, data): + if isinstance(data, int): + try: + return TransactionCategory.objects.get(pk=data) + except TransactionCategory.DoesNotExist: + raise serializers.ValidationError( + "Category with this ID does not exist." + ) + elif isinstance(data, str): + category, created = TransactionCategory.objects.get_or_create(name=data) + return category + raise serializers.ValidationError( + "Invalid category data. Provide an ID or name." + ) + + @staticmethod + def get_schema(): + return { + "type": "array", + "items": {"type": "string", "description": "TransactionTag ID or name"}, + } + + +@extend_schema_field( + { + "type": "array", + "items": {"oneOf": [{"type": "string"}, {"type": "integer"}]}, + "description": "TransactionTag ID or name. If the name doesn't exist, a new one will be created", + } +) +class TransactionTagField(serializers.Field): + def to_representation(self, value): + return [{"id": tag.id, "name": tag.name} for tag in value.all()] + + def to_internal_value(self, data): + tags = [] + for item in data: + if isinstance(item, int): + try: + tag = TransactionTag.objects.get(pk=item) + except TransactionTag.DoesNotExist: + raise serializers.ValidationError( + f"Tag with ID {item} does not exist." + ) + elif isinstance(item, str): + tag, created = TransactionTag.objects.get_or_create(name=item) + else: + raise serializers.ValidationError( + "Invalid tag data. Provide an ID or name." + ) + tags.append(tag) + return tags diff --git a/app/apps/api/serializers/transactions.py b/app/apps/api/serializers/transactions.py index 24c7504..137e849 100644 --- a/app/apps/api/serializers/transactions.py +++ b/app/apps/api/serializers/transactions.py @@ -1,7 +1,10 @@ +from django.utils.translation import gettext_lazy as _ + from rest_framework import serializers from rest_framework.permissions import IsAuthenticated from apps.accounts.models import Account +from apps.api.fields.transactions import TransactionTagField, TransactionCategoryField from apps.api.serializers.accounts import AccountSerializer from apps.transactions.models import ( Transaction, @@ -37,8 +40,9 @@ class InstallmentPlanSerializer(serializers.ModelSerializer): class TransactionSerializer(serializers.ModelSerializer): - category_name = serializers.CharField(source="category.name", read_only=True) - tags = TransactionTagSerializer(many=True, read_only=True) + category = TransactionCategoryField(required=False) + tags = TransactionTagField(required=False) + exchanged_amount = serializers.SerializerMethodField() # For read operations (GET) @@ -49,11 +53,45 @@ class TransactionSerializer(serializers.ModelSerializer): queryset=Account.objects.all(), source="account", write_only=True ) + reference_date = serializers.DateField(required=False) + permission_classes = [IsAuthenticated] class Meta: model = Transaction fields = "__all__" + read_only_fields = [ + "id", + "installment_plan", + "recurring_transaction", + "installment_id", + ] + + def validate(self, data): + if "date" in data and "reference_date" not in data: + data["reference_date"] = data["date"].replace(day=1) + elif "reference_date" in data: + data["reference_date"] = data["reference_date"].replace(day=1) + else: + raise serializers.ValidationError( + _("Either 'date' or 'reference_date' must be provided.") + ) + return data + + def create(self, validated_data): + tags = validated_data.pop("tags", []) + transaction = Transaction.objects.create(**validated_data) + transaction.tags.set(tags) + return transaction + + def update(self, instance, validated_data): + tags = validated_data.pop("tags", None) + for attr, value in validated_data.items(): + setattr(instance, attr, value) + instance.save() + if tags is not None: + instance.tags.set(tags) + return instance @staticmethod def get_exchanged_amount(obj): diff --git a/app/apps/transactions/forms.py b/app/apps/transactions/forms.py index 354e1b0..137215c 100644 --- a/app/apps/transactions/forms.py +++ b/app/apps/transactions/forms.py @@ -62,19 +62,6 @@ class TransactionForm(forms.ModelForm): "notes": forms.Textarea(attrs={"rows": 3}), "account": TomSelect(), } - # labels = { - # "tags": mark_safe('' + _("Tags")), - # "category": mark_safe( - # '' + _("Category") - # ), - # "notes": mark_safe( - # '' + _("Notes") - # ), - # "amount": mark_safe('' + _("Amount")), - # "description": mark_safe( - # '' + _("Name") - # ), - # } def __init__(self, *args, **kwargs): super().__init__(*args, **kwargs) diff --git a/app/apps/transactions/models.py b/app/apps/transactions/models.py index c8eac96..8dd0782 100644 --- a/app/apps/transactions/models.py +++ b/app/apps/transactions/models.py @@ -103,7 +103,12 @@ class Transaction(models.Model): self.amount = truncate_decimal( value=self.amount, decimal_places=self.account.currency.decimal_places ) - self.reference_date = self.reference_date.replace(day=1) + + if self.reference_date: + self.reference_date = self.reference_date.replace(day=1) + elif not self.reference_date and self.date: + self.reference_date = self.date.replace(day=1) + self.full_clean() super().save(*args, **kwargs)