fix mandehesab, bazdid, sms

This commit is contained in:
javad committed 2026-10-16 17:54:07 +03:30
1 parent 7e5c83bea2
commit 1fd27d0445
22 files changed
+16462 -5918

No files matched your search

+138 -24
View File
@@ -18,6 +18,7 @@ from admin_panel.sms.recipients import resolve_recipients
from admin_panel.sms.rules import is_sms_enabled
from admin_panel.sms.scanner import run_all
from admin_panel.sms.timing import apply_timing, current_timing_value, timing_for
from admin_panel.sms.triggers import notify_welcome
from integrations.sms.base import SMSProvider, SMSResult
from integrations.sms.normalize import normalize_mobile, to_local
@@ -193,13 +194,13 @@ class ScannerTests(TestCase):
customer=self.customer, phone_number='09120000001',
)
def test_cheque_funding_fires_after_due_and_is_idempotent(self):
"""یادآوری تأمین موجودی، چند روز پس از سررسید و فقط یک‌بار."""
def test_cheque_funding_fires_before_due_and_is_idempotent(self):
"""یادآوری تأمین موجودی، پیش از سررسید و فقط یک‌بار."""
today = timezone.localdate()
# سررسید ۳ روز پیش، offset پیش‌فرض = ۱ ⇒ واجد شرط.
# سررسید فردا، offset پیش‌فرض = ۱ ⇒ همین امروز واجد شرط است.
Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() - timedelta(days=3),
sarresidDT=timezone.now() + timedelta(days=1),
)
first = run_all(today)
second = run_all(today)
@@ -209,8 +210,8 @@ class ScannerTests(TestCase):
SMSQueue.objects.filter(sms_type=SMSType.CHEQUE_REGISTRATION).count(), 1,
)
def test_cheque_funding_waits_until_after_due(self):
"""پیش از سررسید هیچ یادآوری‌ای ساخته نمی‌شود."""
def test_cheque_funding_waits_when_due_is_far_ahead(self):
"""سررسیدِ دورتر از پنجرهٔ یادآوری هیچ پیامکی نمی‌سازد."""
today = timezone.localdate()
Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check',
@@ -223,29 +224,138 @@ class ScannerTests(TestCase):
today = timezone.localdate()
Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='true', type='check',
sarresidDT=timezone.now() - timedelta(days=3),
sarresidDT=timezone.now() + timedelta(days=1),
)
self.assertEqual(run_all(today)['cheque_registration'], 0)
def test_cheque_funding_offset_is_configurable(self):
"""تعداد روز پس از سررسید از قالب (پنل) قابل تنظیم است."""
"""تعداد روز پیش از سررسید از قالب (پنل) قابل تنظیم است."""
template = SMSTemplate.objects.get(sms_type=SMSType.CHEQUE_REGISTRATION)
apply_timing(template, 10)
template.save()
today = timezone.localdate()
# سررسید ۳ روز پیش ولی offset=۱۰ ⇒ هنوز واجد شرط نیست.
# سررسید فردا ولی offset=۱۰ ⇒ داخل پنجره ⇒ پیامک می‌رود.
Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() - timedelta(days=3),
)
self.assertEqual(run_all(today)['cheque_registration'], 0)
# سررسید ۱۲ روز پیش ⇒ واجد شرط.
Sarresid.objects.all().delete()
Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() - timedelta(days=12),
sarresidDT=timezone.now() + timedelta(days=1),
)
self.assertEqual(run_all(today)['cheque_registration'], 1)
# با offset کوچک‌تر (۱)، سررسید ۱۲ روز آینده بیرون پنجره است ⇒ ۰.
Sarresid.objects.all().delete()
SMSDedup.objects.all().delete()
SMSQueue.objects.all().delete()
apply_timing(template, 1)
template.save()
Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() + timedelta(days=12),
)
self.assertEqual(run_all(today)['cheque_registration'], 0)
# همان چک با offset=۱۲ داخل پنجره می‌شود ⇒ ۱.
apply_timing(template, 12)
template.save()
self.assertEqual(run_all(today)['cheque_registration'], 1)
def test_cheque_funding_days_is_days_remaining(self):
"""«{days}» تعداد روز باقی‌مانده تا سررسید است (مثبت = پیش از سررسید)."""
today = timezone.localdate()
Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() + timedelta(days=1),
)
# قالبِ دیتابیس ممکن است {days} نداشته باشد؛ متن را برای این تست می‌گذاریم
# تا معنای روزِ باقی‌مانده سنجیده شود (متنِ خودِ رکورد DB دست‌نخورده می‌ماند).
SMSTemplate.objects.filter(
sms_type=SMSType.CHEQUE_REGISTRATION,
).update(text='{days}')
self.assertEqual(run_all(today)['cheque_registration'], 1)
item = SMSQueue.objects.get(sms_type=SMSType.CHEQUE_REGISTRATION)
# سررسید فردا ⇒ روز باقی‌مانده = ۱
self.assertEqual(item.message, "1")
def test_cheque_funding_exposes_sayyad_id(self):
"""«{sayyad_id}» شناسهٔ صیاد همان چک را نشان می‌دهد."""
today = timezone.localdate()
Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() + timedelta(days=1),
check_row_number='4321', check_sayyad_id='1234567890123456',
)
# متن با هر دو متغیر چک، برای سنجش مقدار واقعی (DB دست‌نخورده می‌ماند).
SMSTemplate.objects.filter(
sms_type=SMSType.CHEQUE_REGISTRATION,
).update(text='{check_row}|{sayyad_id}')
self.assertEqual(run_all(today)['cheque_registration'], 1)
item = SMSQueue.objects.get(sms_type=SMSType.CHEQUE_REGISTRATION)
self.assertEqual(item.message, "4321|1234567890123456")
def test_cheque_funding_sayyad_id_empty_when_not_set(self):
"""اگر شناسهٔ صیاد ثبت نشده باشد، متغیر خالی رندر می‌شود (نه خطا)."""
today = timezone.localdate()
Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() + timedelta(days=1),
check_row_number='9999',
)
SMSTemplate.objects.filter(
sms_type=SMSType.CHEQUE_REGISTRATION,
).update(text='[{sayyad_id}]')
self.assertEqual(run_all(today)['cheque_registration'], 1)
item = SMSQueue.objects.get(sms_type=SMSType.CHEQUE_REGISTRATION)
self.assertEqual(item.message, "[]")
def test_balance_is_available_in_every_template(self):
"""«{balance}» در همهٔ قالب‌ها (نه فقط چک) قابل استفاده است."""
from admin_panel.models import Article, SMSType as T
Article.objects.create(customer=self.customer, debit=500000, credit=0)
Article.objects.create(customer=self.customer, debit=0, credit=125000)
# ۵۰۰٬۰۰۰ − ۱۲۵٬۰۰۰ = ۳۷۵٬۰۰۰
for sms_type in T.values:
SMSTemplate.objects.filter(sms_type=sms_type).update(
text='{balance}|{balance_sep}',
)
# رویدادی: خوش‌آمدگویی
notify_welcome(self.customer)
item = SMSQueue.objects.get(sms_type=T.WELCOME)
self.assertEqual(item.message, "375000|375,000")
def test_balance_reflects_real_articles(self):
"""مانده حساب = مجموع بدهکار − مجموع بستانکار اسناد مشتری."""
from admin_panel.models import Article
from admin_panel.sms.balance import customer_balance
self.assertEqual(customer_balance(self.customer), 0)
Article.objects.create(customer=self.customer, debit=900000, credit=0)
Article.objects.create(customer=self.customer, debit=0, credit=400000)
self.assertEqual(customer_balance(self.customer), 500000)
# مانده منفی هم باید درست برگردد (بستانکار بیشتر از بدهکار).
Article.objects.create(customer=self.customer, debit=0, credit=750000)
self.assertEqual(customer_balance(self.customer), -250000)
def test_balance_zero_when_customer_is_none(self):
"""بدون مشتری، مانده صفر است و خطایی رخ نمی‌دهد."""
from admin_panel.sms.balance import customer_balance
self.assertEqual(customer_balance(None), 0)
def test_balance_does_not_touch_due_amount(self):
"""{balance} (مانده کل حساب) با {amount} (مبلغ چک) قاطی نمی‌شود."""
from admin_panel.models import Article
today = timezone.localdate()
Article.objects.create(customer=self.customer, debit=8000000, credit=0)
Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() + timedelta(days=1),
)
SMSTemplate.objects.filter(
sms_type=SMSType.CHEQUE_REGISTRATION,
).update(text='amount={amount_sep} balance={balance_sep}')
self.assertEqual(run_all(today)['cheque_registration'], 1)
item = SMSQueue.objects.get(sms_type=SMSType.CHEQUE_REGISTRATION)
# مبلغ چک = ۵٬۰۰۰ (سررسیدشده) ولی مانده حساب = ۸٬۰۰۰٬۰۰۰
self.assertEqual(item.message, "amount=5,000 balance=8,000,000")
def test_nesiye_due_fires_on_due_date_and_ignores_checks(self):
"""یادآوری سررسید فقط برای نسیه است، نه چک."""
@@ -356,7 +466,7 @@ class ScannerTests(TestCase):
today = timezone.localdate()
Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() - timedelta(days=3),
sarresidDT=timezone.now() + timedelta(days=1),
)
SMSTemplate.objects.filter(
sms_type=SMSType.CHEQUE_REGISTRATION,
@@ -374,16 +484,20 @@ class ColdStartTests(TestCase):
)
self.today = timezone.localdate()
def _make_due_cheque(self):
# سررسید در گذشته تا یادآوری «تأمین موجودی» واجد شرط شود.
def _make_cheque(self, days_from_today):
"""چکِ پرداخت‌نشده با سررسیدِ ``days_from_today`` نسبت به امروز."""
return Sarresid.objects.create(
customer=self.customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() - timedelta(days=3),
sarresidDT=timezone.now() + timedelta(days=days_from_today),
)
def _make_due_cheque(self):
# سررسید فردا تا یادآوری «تأمین موجودی» (offset=۱) واجد شرط شود.
return self._make_cheque(1)
def test_first_run_sends_nothing_for_existing_records(self):
# مورد پیش از فعال‌سازی وجود داشته است (خط شروع = همین حالا).
self._make_due_cheque()
# مورد قدیمی: لحظهٔ واجد‌شرط‌شدنش (سررسید − offset) پیش از «خط شروع» است.
self._make_cheque(-3)
activate_sms(since=timezone.now())
self.assertEqual(run_all(self.today)['cheque_registration'], 0)
self.assertEqual(SMSQueue.objects.count(), 0)
@@ -1696,7 +1810,7 @@ class DeletedStaysDeletedTests(PanelTestCase):
mark_primary(customer, '09120000000')
Sarresid.objects.create(
customer=customer, price=5000, payStatus='false', type='check',
sarresidDT=timezone.now() - timedelta(days=3),
sarresidDT=timezone.now() + timedelta(days=1),
)
activate_sms()
template = SMSTemplate.objects.get(sms_type=SMSType.CHEQUE_REGISTRATION)