Files
mobiniqow 831e656fa3 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)
2026-09-15 23:18:05 +03:30

100 lines
3.7 KiB
Python

# apps/account/send_sms.py
"""
ارسال پیامک OTP با کاوه‌نگار
"""
import logging
from threading import Thread
import requests
from django.conf import settings
logger = logging.getLogger(__name__)
REQUEST_TIMEOUT_SECONDS = 10
def _call_lookup(phone: str, template: str, token: str, *, log_label: str = "OTP") -> bool:
"""
ارسال پیامکِ پترنی (lookup) با API کاوه‌نگار — همان endpoint برای OTP و
هر پترنِ تک‌token دیگری (مثلا هدیه‌ی جم) به کار می‌رود.
"""
api_key = settings.KAVENEGAR_API_KEY
logger.info(f"📤 Sending {log_label} to {phone} with template: {template}")
if not api_key or not template:
logger.error(f"❌ KAVENEGAR_API_KEY یا نام قالبِ «{log_label}» در .env تنظیم نشده است")
logger.error(f" KAVENEGAR_API_KEY: {'✅' if api_key else '❌'}")
logger.error(f" نام قالب: {'✅' if template else '❌'}")
return False
# ساخت آدرس کامل
url = f"https://api.kavenegar.com/v1/{api_key}/verify/lookup.json"
# پیلود مورد نیاز کاوه‌نگار
payload = {
"receptor": phone,
"token": token,
"template": template,
}
try:
logger.info(f"📤 POST {url}")
response = requests.post(url, data=payload, timeout=REQUEST_TIMEOUT_SECONDS)
response_data = response.json()
# بررسی پاسخ
if response.status_code == 200:
return_status = response_data.get("return", {}).get("status")
if return_status == 200:
logger.info(f"✅ {log_label} sent to {phone} via Kavenegar")
return True
else:
error_msg = response_data.get("return", {}).get("message", "Unknown error")
logger.error(f"❌ Kavenegar error: {error_msg}")
return False
else:
logger.error(f"❌ Kavenegar HTTP error: {response.status_code} - {response.text}")
return False
except requests.Timeout:
logger.error(f"❌ Kavenegar timeout for {phone}")
return False
except requests.RequestException as exc:
logger.error(f"❌ خطا در ارسال پیامک به {phone}: {exc}")
return False
def _call_token_url(phone: str, code: str) -> bool:
return _call_lookup(phone, settings.SMS_PATTERN_NAME, code, log_label="OTP")
def send_otp_message(phone: str, code: str) -> None:
"""ارسال غیربلاکینگ کد تایید"""
def _send():
try:
_call_token_url(phone, code)
except Exception as exc:
logger.error(f"❌ خطای غیرمنتظره در ارسال OTP: {exc}")
Thread(target=_send, daemon=True).start()
def send_gift_gem_sms(phone: str, amount: int) -> None:
"""اطلاع‌رسانیِ غیربلاکینگِ «هدیه‌ی جم از پنل ادمین» — دقیقا هم‌الگوی
send_otp_message، فقط با قالبِ جداگانه‌ی SMS_GIFT_PATTERN_NAME و
token=تعدادِ جمِ هدیه‌داده‌شده. اگر آن قالب در .env تنظیم نشده باشد،
فقط لاگ می‌کند و هدیه‌دادنِ جم را (که قبلا در دیتابیس ثبت شده) بلاک
نمی‌کند."""
def _send():
try:
_call_lookup(
phone, settings.SMS_GIFT_PATTERN_NAME, str(amount), log_label="هدیه‌ی جم"
)
except Exception as exc:
logger.error(f"❌ خطای غیرمنتظره در ارسال پیامکِ هدیه‌ی جم: {exc}")
Thread(target=_send, daemon=True).start()