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:
@@ -426,7 +426,40 @@ class ExcelExportService:
|
||||
money_columns={4, 5, 6},
|
||||
)
|
||||
|
||||
# --- برگه ۶: انبار ---
|
||||
# --- برگه ۶: دفتر معین ---
|
||||
# همان چیزی که «معین» یعنی: بهازای هر حسابِ معینِ دارای گردش، تکِ
|
||||
# تکِ اسناد بهترتیبِ تاریخ با ماندهی در جریان — دقیقا فرمتی که
|
||||
# حسابدار وقتی میگوید «دفتر معین رو بفرست» انتظارش را دارد.
|
||||
ws = wb.create_sheet("دفتر معین")
|
||||
subsidiary_rows: list[list] = []
|
||||
for account in Account.objects.filter(user=user, level=Account.Level.MOIN).order_by("code"):
|
||||
statement = SubsidiaryLedgerService.statement(user, account, start, end)
|
||||
if not statement["rows"]:
|
||||
continue
|
||||
subsidiary_rows.append([f"{account.code} — {account.name}", "", "", "", "", "", ""])
|
||||
subsidiary_rows.append(["ماندهی ابتدای دوره", "", "", "", "", "", statement["opening_balance"]])
|
||||
for row in statement["rows"]:
|
||||
subsidiary_rows.append(
|
||||
[
|
||||
row["date"].date().isoformat() if hasattr(row["date"], "date") else row["date"],
|
||||
row["number"],
|
||||
row["type"],
|
||||
row["description"],
|
||||
row["debit"],
|
||||
row["credit"],
|
||||
row["balance"],
|
||||
]
|
||||
)
|
||||
subsidiary_rows.append(["ماندهی پایانِ دوره", "", "", "", "", "", statement["closing_balance"]])
|
||||
subsidiary_rows.append([])
|
||||
ExcelExportService._write(
|
||||
ws,
|
||||
["تاریخ", "شماره سند", "نوع", "شرح", "بدهکار", "بستانکار", "مانده"],
|
||||
subsidiary_rows,
|
||||
money_columns={4, 5, 6},
|
||||
)
|
||||
|
||||
# --- برگه ۷: انبار ---
|
||||
ws = wb.create_sheet("انبار")
|
||||
ExcelExportService._write(
|
||||
ws,
|
||||
@@ -489,9 +522,14 @@ class LedgerBookService:
|
||||
end_date: date | None = None,
|
||||
carry_balance: bool = True,
|
||||
) -> "LedgerBook":
|
||||
"""فایل تازه باز میکند: فایلِ جاریِ قبلی بسته و فقط-خواندنی میشود،
|
||||
و اگر خواسته شود ماندهی پایانیِ آن بهعنوان ماندهی ابتدای فایلِ
|
||||
جدید منتقل میگردد (کارِ استانداردِ «انتقال مانده» سرِ سال مالی)."""
|
||||
"""فایل تازه باز میکند و آن را فایلِ جاری میکند؛ اگر خواسته شود
|
||||
ماندهی پایانیِ فایلِ قبلی بهعنوان ماندهی ابتدای فایلِ جدید منتقل
|
||||
میشود (کارِ استانداردِ «انتقال مانده» سرِ سال مالی).
|
||||
|
||||
برخلافِ قبل، فایلِ قبلی خودکار بایگانی (بسته) نمیشود — فقط از حالتِ
|
||||
«جاری» خارج میشود و همچنان در فهرست و قابلویرایش باقی میماند؛
|
||||
بایگانیکردنش یک اقدامِ جداگانه و دستی است (به LedgerBookService.
|
||||
archive_book مراجعه کن)."""
|
||||
from apps.bizapps.models import LedgerBook
|
||||
|
||||
previous = LedgerBook.objects.select_for_update().filter(user=user, is_active=True).first()
|
||||
@@ -500,9 +538,7 @@ class LedgerBookService:
|
||||
if carry_balance:
|
||||
opening = LedgerBookService.closing_balance(previous)
|
||||
previous.is_active = False
|
||||
previous.is_closed = True
|
||||
previous.closed_at = timezone.now()
|
||||
previous.save(update_fields=["is_active", "is_closed", "closed_at", "updated_at"])
|
||||
previous.save(update_fields=["is_active", "updated_at"])
|
||||
|
||||
return LedgerBook.objects.create(
|
||||
id=str(uuid.uuid4()),
|
||||
@@ -514,6 +550,18 @@ class LedgerBookService:
|
||||
is_active=True,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def archive_book(book: "LedgerBook") -> "LedgerBook":
|
||||
"""بایگانیکردنِ دستیِ یک فایل — از این پس فقط-خواندنی میشود.
|
||||
فایلِ جاری هم قابلبایگانی است (کاربر آگاهانه انتخاب کرده)، فقط بعد
|
||||
از آن یک فایلِ جاریِ تازه لازم است (اولین فراخوانیِ بعدیِ
|
||||
ensure_active_book خودش میسازد)."""
|
||||
book.is_active = False
|
||||
book.is_closed = True
|
||||
book.closed_at = timezone.now()
|
||||
book.save(update_fields=["is_active", "is_closed", "closed_at", "updated_at"])
|
||||
return book
|
||||
|
||||
@staticmethod
|
||||
def closing_balance(book: "LedgerBook") -> float:
|
||||
"""ماندهی پایانیِ یک فایل = ماندهی ابتدا + درآمدها − هزینهها."""
|
||||
|
||||
Reference in New Issue
Block a user