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"