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:
+43
-10
@@ -177,19 +177,24 @@ class LedgerServiceTests(TestCase):
|
||||
self.customer.refresh_from_db()
|
||||
self.assertEqual(self.customer.outstanding_balance, 0)
|
||||
|
||||
def _create_and_post(self, **kwargs):
|
||||
"""معادلِ دستیِ کاری که ویوی API انجام میدهد: بعد از ساختِ ردیف،
|
||||
حساب را هماهنگ و سندِ دوطرفهاش را میزند — بدونِ این سند، ردیف در
|
||||
هیچ گزارشِ حرفهای (تراز/دفتر معین/CSV) دیده نمیشود."""
|
||||
entry = LedgerEntry.objects.create(id=str(uuid.uuid4()), user=self.user, **kwargs)
|
||||
LedgerService.sync_entry_account(self.user, entry)
|
||||
LedgerService.post_entry_transaction(self.user, entry)
|
||||
return entry
|
||||
|
||||
def test_export_csv_filters_by_date_range(self):
|
||||
LedgerEntry.objects.create(
|
||||
id=str(uuid.uuid4()),
|
||||
user=self.user,
|
||||
self._create_and_post(
|
||||
entry_type=LedgerEntry.EntryType.INCOME,
|
||||
title="داخل بازه",
|
||||
amount=100,
|
||||
category=LedgerEntry.Category.SALE_CASH,
|
||||
entry_date=date(2026, 1, 15),
|
||||
)
|
||||
LedgerEntry.objects.create(
|
||||
id=str(uuid.uuid4()),
|
||||
user=self.user,
|
||||
self._create_and_post(
|
||||
entry_type=LedgerEntry.EntryType.EXPENSE,
|
||||
title="خارج از بازه",
|
||||
amount=50,
|
||||
@@ -200,6 +205,31 @@ class LedgerServiceTests(TestCase):
|
||||
self.assertIn("داخل بازه", csv_text)
|
||||
self.assertNotIn("خارج از بازه", csv_text)
|
||||
|
||||
def test_post_entry_transaction_covers_plain_income_and_expense(self):
|
||||
"""قبلا فقط فاکتور/قسط سند میخورد؛ یک ثبتِ سادهِ «فروش نقدی» یا
|
||||
«اجاره» (که کارِ روزمرهِ اکثرِ کاربرهاست) اصلا در ترازِ حسابها
|
||||
دیده نمیشد. این تست دقیقا همان رگرسیون را قفل میکند."""
|
||||
self._create_and_post(
|
||||
entry_type=LedgerEntry.EntryType.INCOME,
|
||||
title="فروش نقدی امروز",
|
||||
amount=200,
|
||||
category=LedgerEntry.Category.SALE_CASH,
|
||||
entry_date=date.today(),
|
||||
)
|
||||
self._create_and_post(
|
||||
entry_type=LedgerEntry.EntryType.EXPENSE,
|
||||
title="اجاره مغازه",
|
||||
amount=80,
|
||||
category=LedgerEntry.Category.RENT,
|
||||
entry_date=date.today(),
|
||||
)
|
||||
rows = ChartOfAccountsService.trial_balance(self.user)
|
||||
by_code = {r["code"]: r for r in rows}
|
||||
self.assertIn(ACCOUNT_CASH, by_code)
|
||||
# فروشِ نقدی صندوق را بدهکار میکند، اجاره بستانکار — ماندهی خالص
|
||||
# صندوق باید ۲۰۰ منهای ۸۰ باشد.
|
||||
self.assertEqual(by_code[ACCOUNT_CASH]["balance"], 120)
|
||||
|
||||
|
||||
class InvoiceLedgerIntegrationTests(TestCase):
|
||||
"""اتصالِ فاکتور <-> دفتر: طبق درخواست، فقط فروشِ قسطی باید خودکار یک
|
||||
@@ -553,7 +583,7 @@ class ExcelExportTests(TestCase):
|
||||
wb = self._workbook()
|
||||
self.assertEqual(
|
||||
wb.sheetnames,
|
||||
["خلاصه", "دفتر روزنامه", "تراز حسابها", "بدهکاران", "فاکتورها", "انبار"],
|
||||
["خلاصه", "دفتر روزنامه", "تراز حسابها", "بدهکاران", "فاکتورها", "دفتر معین", "انبار"],
|
||||
)
|
||||
|
||||
def test_sheets_are_right_to_left(self):
|
||||
@@ -825,15 +855,18 @@ class LedgerBookTests(TestCase):
|
||||
orphan.refresh_from_db()
|
||||
self.assertEqual(orphan.book_id, book.id)
|
||||
|
||||
def test_opening_a_new_book_closes_the_previous_one(self):
|
||||
def test_opening_a_new_book_deactivates_but_does_not_archive_the_previous_one(self):
|
||||
"""بایگانیکردن یک اقدامِ دستیِ جداگانه است (LedgerBookService.
|
||||
archive_book) — باز کردنِ فایلِ تازه فقط فایلِ قبلی را از حالتِ
|
||||
«جاری» خارج میکند، نه اینکه خودکار بایگانیاش کند."""
|
||||
old = LedgerBookService.ensure_active_book(self.user)
|
||||
new = LedgerBookService.open_new_book(
|
||||
self.user, name="سال ۱۴۰۶", start_date=date(2027, 3, 21)
|
||||
)
|
||||
old.refresh_from_db()
|
||||
self.assertFalse(old.is_active)
|
||||
self.assertTrue(old.is_closed)
|
||||
self.assertIsNotNone(old.closed_at)
|
||||
self.assertFalse(old.is_closed)
|
||||
self.assertIsNone(old.closed_at)
|
||||
self.assertTrue(new.is_active)
|
||||
# همیشه دقیقا یک فایل جاری
|
||||
self.assertEqual(LedgerBook.objects.filter(user=self.user, is_active=True).count(), 1)
|
||||
|
||||
Reference in New Issue
Block a user