adding
This commit is contained in:
@@ -0,0 +1,58 @@
|
||||
from rest_framework import serializers
|
||||
|
||||
from apps.bizapps.models import Appointment, Customer, LedgerEntry, Product, StockMovement
|
||||
|
||||
|
||||
class CustomerSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Customer
|
||||
fields = ["id", "name", "phone", "email", "address", "notes", "updated_at"]
|
||||
read_only_fields = ["updated_at"]
|
||||
|
||||
|
||||
class LedgerEntrySerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = LedgerEntry
|
||||
fields = ["id", "entry_type", "title", "amount", "category", "entry_date", "note", "updated_at"]
|
||||
read_only_fields = ["updated_at"]
|
||||
|
||||
|
||||
class ProductSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Product
|
||||
fields = [
|
||||
"id",
|
||||
"name",
|
||||
"sku",
|
||||
"category",
|
||||
"quantity",
|
||||
"unit_price",
|
||||
"low_stock_threshold",
|
||||
"notes",
|
||||
"updated_at",
|
||||
]
|
||||
read_only_fields = ["updated_at"]
|
||||
|
||||
|
||||
class StockMovementSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = StockMovement
|
||||
fields = ["id", "delta", "quantity_after", "note", "created_at"]
|
||||
read_only_fields = fields
|
||||
|
||||
|
||||
class AdjustStockSerializer(serializers.Serializer):
|
||||
delta = serializers.FloatField()
|
||||
note = serializers.CharField(max_length=255, required=False, allow_blank=True, default="")
|
||||
|
||||
def validate_delta(self, value):
|
||||
if value == 0:
|
||||
raise serializers.ValidationError("مقدار تغییر نمیتواند صفر باشد.")
|
||||
return value
|
||||
|
||||
|
||||
class AppointmentSerializer(serializers.ModelSerializer):
|
||||
class Meta:
|
||||
model = Appointment
|
||||
fields = ["id", "customer_name", "customer_phone", "title", "start_at", "status", "notes", "updated_at"]
|
||||
read_only_fields = ["updated_at"]
|
||||
@@ -0,0 +1,130 @@
|
||||
from django.db import transaction
|
||||
from rest_framework import generics, status
|
||||
from rest_framework.exceptions import NotFound
|
||||
from rest_framework.permissions import IsAuthenticated
|
||||
from rest_framework.response import Response
|
||||
from rest_framework.views import APIView
|
||||
|
||||
from apps.billing.permissions import HasActiveFeature
|
||||
from apps.bizapps.models import Appointment, Customer, LedgerEntry, Product, StockMovement
|
||||
|
||||
from .serializers import (
|
||||
AdjustStockSerializer,
|
||||
AppointmentSerializer,
|
||||
CustomerSerializer,
|
||||
LedgerEntrySerializer,
|
||||
ProductSerializer,
|
||||
StockMovementSerializer,
|
||||
)
|
||||
|
||||
# همهی این ریزاپها با جم فعال میشوند (یا دورهی آزمایشی) — `feature_key`
|
||||
# روی هر ویو مشخص میکند HasActiveFeature باید چه چیزی را چک کند.
|
||||
# permission_classes اینجا Base است تا زیرکلاسها فراموش نکنند.
|
||||
|
||||
|
||||
class _UserScopedListCreateView(generics.ListCreateAPIView):
|
||||
permission_classes = [IsAuthenticated, HasActiveFeature]
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(user=self.request.user)
|
||||
|
||||
def perform_create(self, serializer):
|
||||
serializer.save(user=self.request.user)
|
||||
|
||||
|
||||
class _UserScopedDetailView(generics.RetrieveUpdateDestroyAPIView):
|
||||
permission_classes = [IsAuthenticated, HasActiveFeature]
|
||||
|
||||
def get_queryset(self):
|
||||
return self.model.objects.filter(user=self.request.user)
|
||||
|
||||
|
||||
class CustomerListCreateView(_UserScopedListCreateView):
|
||||
model = Customer
|
||||
serializer_class = CustomerSerializer
|
||||
feature_key = "customers"
|
||||
|
||||
|
||||
class CustomerDetailView(_UserScopedDetailView):
|
||||
model = Customer
|
||||
serializer_class = CustomerSerializer
|
||||
feature_key = "customers"
|
||||
|
||||
|
||||
class LedgerEntryListCreateView(_UserScopedListCreateView):
|
||||
model = LedgerEntry
|
||||
serializer_class = LedgerEntrySerializer
|
||||
feature_key = "ledger"
|
||||
|
||||
|
||||
class LedgerEntryDetailView(_UserScopedDetailView):
|
||||
model = LedgerEntry
|
||||
serializer_class = LedgerEntrySerializer
|
||||
feature_key = "ledger"
|
||||
|
||||
|
||||
class ProductListCreateView(_UserScopedListCreateView):
|
||||
model = Product
|
||||
serializer_class = ProductSerializer
|
||||
feature_key = "inventory"
|
||||
|
||||
|
||||
class ProductDetailView(_UserScopedDetailView):
|
||||
model = Product
|
||||
serializer_class = ProductSerializer
|
||||
feature_key = "inventory"
|
||||
|
||||
|
||||
class ProductAdjustStockView(APIView):
|
||||
"""تعدیل اتمیک موجودی (+/-) + یک ردیف تاریخچه — بهجای اینکه کلاینت
|
||||
مستقیم quantity جدید را PATCH کند (که هیچ تاریخچهای نمیسازد و اگر دو
|
||||
درخواست همزمان بیایند میتواند یکی را گم کند)."""
|
||||
|
||||
permission_classes = [IsAuthenticated, HasActiveFeature]
|
||||
feature_key = "inventory"
|
||||
|
||||
@transaction.atomic
|
||||
def post(self, request, pk):
|
||||
try:
|
||||
product = Product.objects.select_for_update().get(pk=pk, user=request.user)
|
||||
except Product.DoesNotExist:
|
||||
raise NotFound("کالا یافت نشد.")
|
||||
|
||||
serializer = AdjustStockSerializer(data=request.data)
|
||||
serializer.is_valid(raise_exception=True)
|
||||
delta = serializer.validated_data["delta"]
|
||||
|
||||
product.quantity += delta
|
||||
product.save(update_fields=["quantity", "updated_at"])
|
||||
StockMovement.objects.create(
|
||||
product=product,
|
||||
delta=delta,
|
||||
quantity_after=product.quantity,
|
||||
note=serializer.validated_data.get("note", ""),
|
||||
)
|
||||
return Response(ProductSerializer(product).data, status=status.HTTP_200_OK)
|
||||
|
||||
|
||||
class ProductMovementsView(generics.ListAPIView):
|
||||
"""تاریخچهی تغییرات موجودی یک کالا — جدیدترین اول."""
|
||||
|
||||
serializer_class = StockMovementSerializer
|
||||
permission_classes = [IsAuthenticated, HasActiveFeature]
|
||||
feature_key = "inventory"
|
||||
|
||||
def get_queryset(self):
|
||||
return StockMovement.objects.filter(
|
||||
product_id=self.kwargs["pk"], product__user=self.request.user
|
||||
)
|
||||
|
||||
|
||||
class AppointmentListCreateView(_UserScopedListCreateView):
|
||||
model = Appointment
|
||||
serializer_class = AppointmentSerializer
|
||||
feature_key = "booking"
|
||||
|
||||
|
||||
class AppointmentDetailView(_UserScopedDetailView):
|
||||
model = Appointment
|
||||
serializer_class = AppointmentSerializer
|
||||
feature_key = "booking"
|
||||
Reference in New Issue
Block a user