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)