164 lines
6.8 KiB
Python
164 lines
6.8 KiB
Python
"""یافتن شمارههای یک مشتری از همهٔ روشهای ذخیرهسازی.
|
|
|
|
شمارهها در این پروژه به چند شکل ذخیره میشوند:
|
|
|
|
* رکوردهای مدل ``PhoneNumber`` (روش جدید؛ عنوان + شماره)
|
|
* فیلدهای قدیمی ``Customer.phone1`` / ``phone2`` / ``phone3``
|
|
* بدون صفر ابتدایی (``9133722815`` بهجای ``09133722815``) و بعضاً مقدارهای
|
|
بیمعنی مثل ``'0'`` یا شمارهٔ ثابت.
|
|
|
|
این ماژول تنها مرجع خواندن شمارههاست: هر جای دیگر (پنل، اسکنر، worker) باید از
|
|
همین توابع استفاده کند تا رفتار یکسان باشد.
|
|
|
|
«شمارهٔ اصلی» بهصورت **مقدارِ خودِ شماره** (نرمالشده) در ``Customer.sms_primary_phones``
|
|
ذخیره میشود، نه با کلید رکورد. دلیل: دادهٔ مشتریان دائماً تغییر میکند (افزودن، ویرایش،
|
|
حذف) و کلید رکورد در بازسازیها از بین میرود؛ ولی شماره یک مقدار پایدار است. اگر شمارهای
|
|
از مشتری حذف یا ویرایش شود، انتخاب اصلی خودبهخود بیاثر میشود و سیستم به شمارهٔ اول
|
|
برمیگردد.
|
|
"""
|
|
from integrations.sms.normalize import normalize_mobile
|
|
|
|
|
|
class PhoneEntry:
|
|
"""یک شمارهٔ مشتری، مستقل از اینکه کجا ذخیره شده است."""
|
|
|
|
__slots__ = ("raw", "display", "normalized", "title", "source", "is_primary")
|
|
|
|
def __init__(self, raw, title, source, is_primary=False):
|
|
self.raw = raw
|
|
self.title = title
|
|
self.source = source
|
|
self.normalized = normalize_mobile(raw)
|
|
self.display = to_display(raw)
|
|
self.is_primary = is_primary
|
|
|
|
@property
|
|
def is_mobile(self):
|
|
return bool(self.normalized)
|
|
|
|
def as_dict(self):
|
|
return {
|
|
"raw": self.raw,
|
|
"display": self.display,
|
|
"normalized": self.normalized,
|
|
"title": self.title,
|
|
"source": self.source,
|
|
"is_primary": self.is_primary,
|
|
"is_mobile": self.is_mobile,
|
|
}
|
|
|
|
def __repr__(self): # pragma: no cover - کمککنندهٔ دیباگ
|
|
return f"<PhoneEntry {self.display!r} primary={self.is_primary} source={self.source}>"
|
|
|
|
|
|
def to_display(raw):
|
|
"""شماره را برای نمایش با صفر ابتدایی برمیگرداند (بدون دستبردن در مقدار ذخیرهشده)."""
|
|
text = "" if raw is None else str(raw).strip()
|
|
if not text:
|
|
return ""
|
|
normalized = normalize_mobile(text)
|
|
if normalized:
|
|
return "0" + normalized[3:] # +989xxxxxxxxx → 09xxxxxxxxx
|
|
return text
|
|
|
|
|
|
def _iter_sources(customer):
|
|
"""(title, raw, source) از همهٔ منابع، بدون تکرار — ابتدا رکوردها، سپس فیلدهای قدیمی."""
|
|
from ..models import PhoneNumber
|
|
|
|
seen = set()
|
|
rows = PhoneNumber.objects.filter(customer=customer).order_by("id")
|
|
for row in rows:
|
|
raw = (row.phone_number or "").strip()
|
|
if not raw:
|
|
continue
|
|
key = normalize_mobile(raw) or raw
|
|
if key in seen:
|
|
continue
|
|
seen.add(key)
|
|
yield (row.title or "تلفن", raw, "phone_number")
|
|
|
|
legacy = (
|
|
("تلفن 1", getattr(customer, "phone1", "")),
|
|
("تلفن 2", getattr(customer, "phone2", "")),
|
|
("تلفن 3", getattr(customer, "phone3", "")),
|
|
)
|
|
for title, raw in legacy:
|
|
raw = (raw or "").strip()
|
|
if not raw:
|
|
continue
|
|
key = normalize_mobile(raw) or raw
|
|
if key in seen:
|
|
continue
|
|
seen.add(key)
|
|
yield (title, raw, "legacy")
|
|
|
|
|
|
def stored_primary_normalized(customer):
|
|
"""مجموعهٔ شمارههای نرمالشدهای که مدیر بهعنوان «اصلی» انتخاب کرده است."""
|
|
raw = (getattr(customer, "sms_primary_phones", "") or "").strip()
|
|
if not raw:
|
|
return set()
|
|
return {part.strip() for part in raw.split(",") if part.strip()}
|
|
|
|
|
|
def normalize_primary_values(values):
|
|
"""فهرست شمارههای خام → رشتهٔ نرمالشدهٔ یکتا برای ذخیره در ``sms_primary_phones``.
|
|
|
|
شمارههای نامعتبر (ثابت، ناقص، ``'0'``) نادیده گرفته میشوند؛ پس علامتزدن یک شمارهٔ
|
|
غیرموبایل بهعنوان «اصلی» بیاثر میماند و سیستم به شمارهٔ اولِ معتبر برمیگردد.
|
|
"""
|
|
unique = []
|
|
for raw in values:
|
|
number = normalize_mobile(raw)
|
|
if number and number not in unique:
|
|
unique.append(number)
|
|
return ",".join(unique)
|
|
|
|
|
|
def save_primary_normalized(customer, numbers, *, update_fields=("sms_primary_phones",)):
|
|
"""ذخیرهٔ فهرست شمارههای اصلی (نرمالشده، بدون تکرار، به ترتیب ورودی)."""
|
|
customer.sms_primary_phones = normalize_primary_values(numbers)
|
|
customer.save(update_fields=list(update_fields))
|
|
return stored_primary_normalized(customer)
|
|
|
|
|
|
def customer_phone_entries(customer):
|
|
"""همهٔ شمارههای مشتری از هر منبعی که ذخیره شده باشند (بدون تکرار)."""
|
|
if customer is None:
|
|
return []
|
|
primary = stored_primary_normalized(customer)
|
|
return [
|
|
PhoneEntry(raw, title, source, is_primary=(entry_norm in primary) if entry_norm else False)
|
|
for title, raw, source in _iter_sources(customer)
|
|
for entry_norm in [normalize_mobile(raw)]
|
|
]
|
|
|
|
|
|
def usable_entries(customer):
|
|
"""فقط شمارههایی که موبایل معتبرند و واقعاً قابل ارسال پیامک هستند."""
|
|
return [entry for entry in customer_phone_entries(customer) if entry.is_mobile]
|
|
|
|
|
|
def primary_phones(customer):
|
|
"""شمارههای خام گیرندهٔ پیامک: انتخابشدههای مدیر، وگرنه شمارهٔ اولِ قابل استفاده."""
|
|
entries = usable_entries(customer)
|
|
if not entries:
|
|
return []
|
|
chosen = [entry for entry in entries if entry.is_primary]
|
|
if chosen:
|
|
return [entry.raw for entry in chosen]
|
|
return [entries[0].raw]
|
|
|
|
|
|
def resolved_recipients(customer):
|
|
"""گیرندگان نهاییِ نرمالشده (``+989xxxxxxxxx``) و یکتا."""
|
|
seen = []
|
|
for entry in usable_entries(customer):
|
|
if entry.is_primary and entry.normalized not in seen:
|
|
seen.append(entry.normalized)
|
|
if seen:
|
|
return seen
|
|
entries = usable_entries(customer)
|
|
return [entries[0].normalized] if entries else []
|