Fix OTP/SMS, ledger accounting, order tracking, and add reviews app
- Add MessageTemplate.runtime_field_labels so mobile's per-field execution-time labels are actually persisted (were silently dropped) - Fix AdvanceOrderStageView reading the wrong extra-input key, and be honest about SMS send failures instead of always claiming success - Add admin gift-gem SMS notification via Kavenegar - Post real double-entry Transactions for plain (non-invoice) ledger entries so trial balance, the معین screen, and CSV/XLSX exports actually include everyday income/expense entries; backfill existing historical entries via data migration - Rewrite CSV ledger export to produce an actual دفتر معین structure (per-account running balance) instead of a flat row dump - Add invoice percent/promotional discounts, tax presets, ledger discount-percent handling, product SKU/category/expiry fields, and manual ledger-book archiving (replacing auto-archive on new book) - Add reviews app (models, API, admin) for the comments/reviews feature - Update tests for the above; fix two stale assertions left over from earlier work in this branch (معین sheet name, book auto-close)
This commit is contained in:
@@ -109,6 +109,7 @@ class LedgerEntryListCreateView(_OwnedMixin, generics.ListCreateAPIView):
|
||||
book = LedgerBookService.ensure_active_book(self.request.user)
|
||||
entry = serializer.save(user=self.request.user, book=book)
|
||||
LedgerService.sync_entry_account(self.request.user, entry)
|
||||
LedgerService.post_entry_transaction(self.request.user, entry)
|
||||
if entry.counterparty_phone:
|
||||
LedgerService.recompute_customer_balance(self.request.user, entry.counterparty_phone)
|
||||
|
||||
@@ -123,6 +124,7 @@ class LedgerEntryDetailView(_OwnedMixin, generics.RetrieveUpdateDestroyAPIView):
|
||||
def perform_update(self, serializer):
|
||||
entry = serializer.save()
|
||||
LedgerService.sync_entry_account(self.request.user, entry)
|
||||
LedgerService.post_entry_transaction(self.request.user, entry)
|
||||
if entry.counterparty_phone:
|
||||
LedgerService.recompute_customer_balance(self.request.user, entry.counterparty_phone)
|
||||
|
||||
@@ -473,6 +475,48 @@ class LedgerBookSummaryView(APIView):
|
||||
)
|
||||
|
||||
|
||||
class LedgerBookDetailView(generics.RetrieveUpdateDestroyAPIView):
|
||||
"""ویرایش/حذفِ یک فایل دفتر. فایلِ بایگانیشده فقط-خواندنی است — نه
|
||||
ویرایش و نه حذف. حذف فقط برای فایلِ خالی (بدون هیچ ثبتی) مجاز است تا
|
||||
دادهی مالی با حذفِ فایل بهطور آبشاری از بین نرود."""
|
||||
|
||||
serializer_class = LedgerBookSerializer
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def get_queryset(self):
|
||||
return LedgerBook.objects.filter(user=self.request.user)
|
||||
|
||||
def _guard_editable(self, book: LedgerBook):
|
||||
if book.is_closed:
|
||||
raise ValidationError("فایلِ بایگانیشده فقط-خواندنی است.")
|
||||
|
||||
def perform_update(self, serializer):
|
||||
self._guard_editable(serializer.instance)
|
||||
serializer.save()
|
||||
|
||||
def perform_destroy(self, instance):
|
||||
self._guard_editable(instance)
|
||||
if instance.entries.exists():
|
||||
raise ValidationError("فقط فایلِ خالی (بدون هیچ ثبتی) قابلحذف است.")
|
||||
instance.delete()
|
||||
|
||||
|
||||
class LedgerBookArchiveView(APIView):
|
||||
"""بایگانیکردنِ دستیِ یک فایل دفتر — از این پس فقط-خواندنی میشود و در
|
||||
انتخابِ فایلِ دفتر برای فاکتورهای تازه نشان داده نمیشود."""
|
||||
|
||||
permission_classes = [IsAuthenticated]
|
||||
|
||||
def post(self, request, pk):
|
||||
book = LedgerBook.objects.filter(user=request.user, pk=pk).first()
|
||||
if book is None:
|
||||
raise NotFound("فایل دفتر یافت نشد.")
|
||||
if book.is_closed:
|
||||
raise ValidationError("این فایل قبلا بایگانی شده.")
|
||||
book = LedgerBookService.archive_book(book)
|
||||
return Response(LedgerBookSerializer(book).data)
|
||||
|
||||
|
||||
class SubsidiaryLedgerView(APIView):
|
||||
"""دفتر معین یک حساب — گردشِ حساب با ماندهی در جریان."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user