adding
This commit is contained in:
+216
-19
@@ -1,37 +1,120 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from apps.bizapps.models import Appointment, Customer, LedgerEntry, Product, StockMovement
|
||||
from apps.bizapps.models import (
|
||||
Account,
|
||||
Customer,
|
||||
Invoice,
|
||||
InvoiceItem,
|
||||
LedgerBook,
|
||||
LedgerEntry,
|
||||
LedgerInstallment,
|
||||
Product,
|
||||
StockMovement,
|
||||
Transaction,
|
||||
)
|
||||
|
||||
|
||||
class AccountSerializer(serializers.ModelSerializer):
|
||||
full_path = serializers.CharField(read_only=True)
|
||||
level_display = serializers.CharField(source="get_level_display", read_only=True)
|
||||
account_type_display = serializers.CharField(source="get_account_type_display", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Account
|
||||
fields = [
|
||||
"id", "code", "name", "level", "level_display", "account_type",
|
||||
"account_type_display", "parent", "full_path", "is_system", "is_active",
|
||||
]
|
||||
read_only_fields = ["is_system"]
|
||||
|
||||
|
||||
class CustomerSerializer(serializers.ModelSerializer):
|
||||
account_code = serializers.CharField(source="account.code", read_only=True, default="")
|
||||
|
||||
class Meta:
|
||||
model = Customer
|
||||
fields = ["id", "name", "phone", "email", "address", "notes", "updated_at"]
|
||||
read_only_fields = ["updated_at"]
|
||||
fields = [
|
||||
"id", "name", "phone", "social_platform", "social_handle",
|
||||
"email", "address", "notes",
|
||||
"total_purchases_count", "total_purchase_amount", "last_purchase_date",
|
||||
"outstanding_balance", "account_code", "updated_at",
|
||||
]
|
||||
# اینها را سرویسها محاسبه میکنند، نه کلاینت — وگرنه ماندهی بدهی
|
||||
# از سمت اپ قابلدستکاری میشد.
|
||||
read_only_fields = [
|
||||
"total_purchases_count", "total_purchase_amount", "last_purchase_date",
|
||||
"outstanding_balance", "account_code", "updated_at",
|
||||
]
|
||||
|
||||
|
||||
class LedgerInstallmentSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = LedgerInstallment
|
||||
fields = ["id", "amount", "paid_date", "reference", "created_at"]
|
||||
# برخلافِ بقیهی مدلها (که UUID را خودِ اپ فلاتر میسازد و میفرستد)،
|
||||
# id قسط را ویو تولید میکند — پس نباید از کلاینت الزامی باشد.
|
||||
read_only_fields = ["id", "created_at"]
|
||||
|
||||
def validate_amount(self, value):
|
||||
if value <= 0:
|
||||
raise serializers.ValidationError("مبلغ قسط باید بزرگتر از صفر باشد.")
|
||||
return value
|
||||
|
||||
def validate(self, attrs):
|
||||
entry: LedgerEntry = self.context["ledger_entry"]
|
||||
if not entry.is_debt:
|
||||
raise serializers.ValidationError("فقط برای ردیفهای بدهیدار میتوان قسط ثبت کرد.")
|
||||
amount = attrs.get("amount", 0)
|
||||
# epsilon کوچک تا خطای گردکردنِ اعشارِ شناور باعثِ ردِ یک قسطِ
|
||||
# تسویهی دقیق نشود.
|
||||
if amount > entry.remaining_amount + 1:
|
||||
raise serializers.ValidationError("مبلغ قسط از ماندهی بدهی بیشتر است.")
|
||||
return attrs
|
||||
|
||||
|
||||
class LedgerEntrySerializer(serializers.ModelSerializer):
|
||||
installments = LedgerInstallmentSerializer(many=True, read_only=True)
|
||||
is_debt = serializers.BooleanField(read_only=True)
|
||||
professional_type = serializers.CharField(read_only=True)
|
||||
paid_amount = serializers.FloatField(read_only=True)
|
||||
remaining_amount = serializers.FloatField(read_only=True)
|
||||
category_display = serializers.CharField(source="get_category_display", read_only=True)
|
||||
account_name = serializers.CharField(source="account.name", read_only=True, default="")
|
||||
account_code = serializers.CharField(source="account.code", read_only=True, default="")
|
||||
|
||||
class Meta:
|
||||
model = LedgerEntry
|
||||
fields = ["id", "entry_type", "title", "amount", "category", "entry_date", "note", "updated_at"]
|
||||
read_only_fields = ["updated_at"]
|
||||
fields = [
|
||||
"id", "entry_type", "title", "amount", "category", "category_display",
|
||||
"entry_date", "note", "total_amount", "counterparty_name", "counterparty_phone",
|
||||
"linked_invoice", "account_name", "account_code",
|
||||
"is_debt", "professional_type", "paid_amount", "remaining_amount",
|
||||
"installments", "updated_at",
|
||||
]
|
||||
read_only_fields = ["linked_invoice", "account_name", "account_code", "updated_at"]
|
||||
|
||||
def validate(self, attrs):
|
||||
category = attrs.get("category", getattr(self.instance, "category", ""))
|
||||
if category in LedgerEntry.DEBT_CATEGORIES:
|
||||
total = attrs.get("total_amount", getattr(self.instance, "total_amount", 0))
|
||||
if not total or total <= 0:
|
||||
raise serializers.ValidationError(
|
||||
{"total_amount": "برای فروش قسطی/خرید نسیه، مبلغ کل تعهد الزامی است."}
|
||||
)
|
||||
return attrs
|
||||
|
||||
|
||||
class ProductSerializer(serializers.ModelSerializer):
|
||||
is_low_stock = serializers.BooleanField(read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"sku",
|
||||
"category",
|
||||
"quantity",
|
||||
"unit_price",
|
||||
"low_stock_threshold",
|
||||
"notes",
|
||||
"updated_at",
|
||||
"id", "name", "sku", "category", "unit", "quantity",
|
||||
"unit_price", "purchase_price", "total_purchase_value",
|
||||
"low_stock_threshold", "is_low_stock", "notes", "updated_at",
|
||||
]
|
||||
read_only_fields = ["updated_at"]
|
||||
read_only_fields = ["total_purchase_value", "is_low_stock", "updated_at"]
|
||||
|
||||
|
||||
class StockMovementSerializer(serializers.ModelSerializer):
|
||||
@@ -51,8 +134,122 @@ class AdjustStockSerializer(serializers.Serializer):
|
||||
return value
|
||||
|
||||
|
||||
class AppointmentSerializer(serializers.ModelSerializer):
|
||||
class InvoiceItemSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Appointment
|
||||
fields = ["id", "customer_name", "customer_phone", "title", "start_at", "status", "notes", "updated_at"]
|
||||
read_only_fields = ["updated_at"]
|
||||
model = InvoiceItem
|
||||
fields = ["id", "product", "description", "quantity", "unit_price", "discount_percent", "total_price"]
|
||||
read_only_fields = ["total_price"]
|
||||
|
||||
|
||||
class InvoiceSerializer(serializers.ModelSerializer):
|
||||
items = InvoiceItemSerializer(many=True, read_only=True)
|
||||
payment_type_display = serializers.CharField(source="get_payment_type_display", read_only=True)
|
||||
payment_status_display = serializers.CharField(source="get_payment_status_display", read_only=True)
|
||||
order_status_display = serializers.CharField(source="get_order_status_display", read_only=True)
|
||||
|
||||
class Meta:
|
||||
model = Invoice
|
||||
fields = [
|
||||
"id", "invoice_number", "invoice_date", "customer",
|
||||
"buyer_name", "buyer_phone", "buyer_address",
|
||||
"seller_name", "seller_phone", "seller_address",
|
||||
"total_amount", "discount_amount", "tax_lines", "final_amount",
|
||||
"payment_type", "payment_type_display",
|
||||
"payment_status", "payment_status_display",
|
||||
"order_status", "order_status_display",
|
||||
"shipping_method", "tracking_code", "notes", "paid_at",
|
||||
"items", "created_at", "updated_at",
|
||||
]
|
||||
read_only_fields = [
|
||||
"invoice_number", "total_amount", "final_amount", "paid_at", "created_at", "updated_at",
|
||||
]
|
||||
|
||||
|
||||
class CreateInvoiceItemSerializer(serializers.Serializer):
|
||||
product = serializers.PrimaryKeyRelatedField(queryset=Product.objects.all(), required=False, allow_null=True)
|
||||
description = serializers.CharField(max_length=255, required=False, allow_blank=True, default="")
|
||||
quantity = serializers.FloatField(min_value=0.0001)
|
||||
unit_price = serializers.FloatField(required=False, default=0)
|
||||
discount_percent = serializers.FloatField(required=False, default=0, min_value=0, max_value=100)
|
||||
|
||||
def validate(self, attrs):
|
||||
if attrs.get("product") is None and not attrs.get("description"):
|
||||
raise serializers.ValidationError("برای ردیف بدون کالا، شرح الزامی است.")
|
||||
return attrs
|
||||
|
||||
|
||||
class CreateInvoiceSerializer(serializers.Serializer):
|
||||
items = CreateInvoiceItemSerializer(many=True)
|
||||
customer = serializers.PrimaryKeyRelatedField(queryset=Customer.objects.all(), required=False, allow_null=True)
|
||||
invoice_date = serializers.DateTimeField(required=False)
|
||||
buyer_name = serializers.CharField(max_length=150, required=False, allow_blank=True, default="")
|
||||
buyer_phone = serializers.CharField(max_length=20, required=False, allow_blank=True, default="")
|
||||
buyer_address = serializers.CharField(max_length=255, required=False, allow_blank=True, default="")
|
||||
seller_name = serializers.CharField(max_length=150, required=False, allow_blank=True, default="")
|
||||
seller_phone = serializers.CharField(max_length=20, required=False, allow_blank=True, default="")
|
||||
seller_address = serializers.CharField(max_length=255, required=False, allow_blank=True, default="")
|
||||
discount_amount = serializers.FloatField(required=False, default=0, min_value=0)
|
||||
tax_lines = serializers.ListField(child=serializers.DictField(), required=False, default=list)
|
||||
payment_type = serializers.ChoiceField(choices=Invoice.PaymentType.choices, required=False)
|
||||
shipping_method = serializers.ChoiceField(choices=Invoice.ShippingMethod.choices, required=False)
|
||||
tracking_code = serializers.CharField(max_length=100, required=False, allow_blank=True, default="")
|
||||
notes = serializers.CharField(required=False, allow_blank=True, default="")
|
||||
send_sms = serializers.BooleanField(required=False, default=False)
|
||||
|
||||
def validate_items(self, value):
|
||||
if not value:
|
||||
raise serializers.ValidationError("فاکتور باید حداقل یک قلم کالا داشته باشد.")
|
||||
return value
|
||||
|
||||
|
||||
class UpdateInvoiceStatusSerializer(serializers.Serializer):
|
||||
order_status = serializers.ChoiceField(choices=Invoice.OrderStatus.choices, required=False)
|
||||
payment_status = serializers.ChoiceField(choices=Invoice.PaymentStatus.choices, required=False)
|
||||
tracking_code = serializers.CharField(max_length=100, required=False, allow_blank=True)
|
||||
|
||||
def validate(self, attrs):
|
||||
if not attrs:
|
||||
raise serializers.ValidationError("حداقل یکی از فیلدهای وضعیت باید ارسال شود.")
|
||||
return attrs
|
||||
|
||||
|
||||
class TransactionSerializer(serializers.ModelSerializer):
|
||||
transaction_type_display = serializers.CharField(source="get_transaction_type_display", read_only=True)
|
||||
debit_account_name = serializers.CharField(source="debit_account_ref.name", read_only=True, default="")
|
||||
credit_account_name = serializers.CharField(source="credit_account_ref.name", read_only=True, default="")
|
||||
|
||||
class Meta:
|
||||
model = Transaction
|
||||
fields = [
|
||||
"id", "transaction_number", "transaction_date", "reference_invoice",
|
||||
"transaction_type", "transaction_type_display",
|
||||
"debit_account", "credit_account", "debit_account_name", "credit_account_name",
|
||||
"amount", "description", "created_at",
|
||||
]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class LedgerBookSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = LedgerBook
|
||||
fields = [
|
||||
"id", "name", "start_date", "end_date", "opening_balance",
|
||||
"is_active", "is_closed", "closed_at", "created_at",
|
||||
]
|
||||
read_only_fields = ["id", "is_active", "is_closed", "closed_at", "created_at"]
|
||||
|
||||
|
||||
class OpenLedgerBookSerializer(serializers.Serializer):
|
||||
name = serializers.CharField(max_length=120)
|
||||
start_date = serializers.DateField()
|
||||
end_date = serializers.DateField(required=False, allow_null=True)
|
||||
# پیشفرض true: انتقالِ مانده سرِ سال مالی کارِ استاندارد است.
|
||||
carry_balance = serializers.BooleanField(required=False, default=True)
|
||||
|
||||
def validate(self, attrs):
|
||||
end = attrs.get("end_date")
|
||||
if end and end <= attrs["start_date"]:
|
||||
raise serializers.ValidationError(
|
||||
{"end_date": "تاریخ پایان باید بعد از تاریخ شروع باشد."}
|
||||
)
|
||||
return attrs
|
||||
|
||||
Reference in New Issue
Block a user