sms
This commit is contained in:
1 parent
250cdbab7f
commit
5c6069c3ef
87 files changed
+29209
-3697
No files matched your search
@@ -0,0 +1,139 @@
|
||||
"""پاکسازی یکبارهٔ شمارههای قدیمی مشتریان (فیلدهای ``phone1``/``phone2``/``phone3``).
|
||||
|
||||
سالها شمارهها در سه فیلد متنی ذخیره شدهاند و به هر شکلی وارد شدهاند:
|
||||
بدون صفر ابتدایی (``9133722815``)، با ارقام فارسی، با فاصله/خط تیره، یا کلاهی
|
||||
متن آزاد (نام، تاریخ نصب، «معرف …»). این ماژول قاعدهٔ تبدیل را یکجا تعریف میکند
|
||||
تا هم مهاجرت (اجرای خودکار، یکبار) و هم دستور ``fix_customer_phones`` (اجرای
|
||||
دستی روی دیتابیس production) از یک پیادهسازی استفاده کنند.
|
||||
|
||||
قاعده (طبق تصمیم کاربر):
|
||||
|
||||
* شمارهٔ موبایل — هرجای متن که باشد — استخراج و به شکل استاندارد ``09xxxxxxxxx``
|
||||
تبدیل میشود.
|
||||
* شمارهٔ ثابت ۸رقمی (مثل ``57387608``) **دستنخورده** میماند.
|
||||
* بقیه (متن آزاد، تاریخ، تکههای بیمعنی) از فیلد شماره خالی میشود و متن اصلیاش
|
||||
در فیلد ``Customer.notes`` میرود تا چیزی از سیستم گم نشود.
|
||||
|
||||
تابع ``clean_legacy_cell`` خودبسنده و idempotent است: بعد از یکبار اجرا، خروجی
|
||||
همان مقدار فعلی است و دیگر توضیحی اضافه نمیشود؛ پس اجرای دوباره بیخطر است.
|
||||
"""
|
||||
import re
|
||||
|
||||
from persiantools import digits
|
||||
|
||||
from integrations.sms.normalize import normalize_mobile, to_local
|
||||
|
||||
_NOTE_HEADER = "— پاکسازی خودکار فیلدهای تلفن —"
|
||||
|
||||
|
||||
def _numeric_tokens(raw):
|
||||
"""تکههای رقمی متن، پس از تبدیل ارقام فارسی/عربی به انگلیسی."""
|
||||
text = digits.fa_to_en(digits.ar_to_fa(str(raw)))
|
||||
return re.findall(r"\d+", text)
|
||||
|
||||
|
||||
def clean_legacy_cell(raw):
|
||||
"""یک فیلد تلفن را پاکسازی میکند.
|
||||
|
||||
خروجی ``(value, note)`` است:
|
||||
|
||||
* ``value`` مقدار جدید فیلد، یا ``None`` یعنی «دست نزن».
|
||||
* ``note`` متن اصلی برای افزودن به ``Customer.notes``، یا ``None``.
|
||||
|
||||
قاعده: هرچیزِ فیلد که در مقدار جدید نمیماند، **بیکموکاست** در
|
||||
``note`` برمیگردد تا هیچ دادهای گم نشود.
|
||||
"""
|
||||
original = (raw or "").strip()
|
||||
if not original:
|
||||
return None, None
|
||||
|
||||
# ۱) کل فیلد یک موبایل است (شاید با فاصله/خط تیره/پیششماره).
|
||||
whole = normalize_mobile(original)
|
||||
if whole:
|
||||
local = to_local(whole)
|
||||
if local == original:
|
||||
return None, None
|
||||
return local, original
|
||||
|
||||
tokens = _numeric_tokens(original)
|
||||
|
||||
# ۲) موبایلی داخل متن پنهان است (مثل «57322182/9130261671» یا «موبایل: 0912…»).
|
||||
for token in tokens:
|
||||
local = to_local(token)
|
||||
if local:
|
||||
return local, original
|
||||
|
||||
# ۳) شمارهٔ ثابت ۸رقمیِ تنها ⇒ دستنخورده.
|
||||
if len(tokens) == 1 and len(tokens[0]) == 8:
|
||||
return None, None
|
||||
|
||||
# ۴) بقیه (متن آزاد، تاریخ، تکهٔ بیمعنی) ⇒ خالی؛ متن اصلی در یادداشت میماند.
|
||||
return "", original
|
||||
|
||||
|
||||
def _append_note(notes, lines):
|
||||
"""خطوط توضیح را به انتهای یادداشت مشتری اضافه میکند (با سرصفحهٔ یکبار)."""
|
||||
current = (notes or "").strip()
|
||||
block = "\n".join(lines)
|
||||
if _NOTE_HEADER in current:
|
||||
return f"{current}\n{block}"
|
||||
if current:
|
||||
return f"{current}\n\n{_NOTE_HEADER}\n{block}"
|
||||
return f"{_NOTE_HEADER}\n{block}"
|
||||
|
||||
|
||||
def clean_customer(customer, *, dry_run=False):
|
||||
"""فیلدهای تلفن یک مشتری را اصلاح و متنهای حذفشده را در ``notes`` میریزد.
|
||||
|
||||
خروجی: تعداد فیلدهای تغییرکرده. اگر تغییری نباشد، چیزی ذخیره نمیشود.
|
||||
"""
|
||||
changed = 0
|
||||
note_lines = []
|
||||
updates = {}
|
||||
for field in ("phone1", "phone2", "phone3"):
|
||||
value, note = clean_legacy_cell(getattr(customer, field, ""))
|
||||
if value is not None and value != (getattr(customer, field) or ""):
|
||||
updates[field] = value
|
||||
changed += 1
|
||||
if note:
|
||||
note_lines.append(f"«{field}»: {note}")
|
||||
|
||||
if not changed and not note_lines:
|
||||
return 0
|
||||
if dry_run:
|
||||
return changed or (1 if note_lines else 0)
|
||||
|
||||
if note_lines:
|
||||
customer.notes = _append_note(customer.notes, note_lines)
|
||||
updates["notes"] = customer.notes
|
||||
if updates:
|
||||
for field, value in updates.items():
|
||||
setattr(customer, field, value)
|
||||
customer.save(update_fields=list(updates))
|
||||
return changed
|
||||
|
||||
|
||||
def clean_all(*, dry_run=False, batch_size=500, apps=None):
|
||||
"""همهٔ مشتریان را پاکسازی میکند. خروجی: دیکشنری آمار.
|
||||
|
||||
``apps`` فقط برای مهاجرتها است تا مدل را از رجیستری تاریخی بگیرد (نه import
|
||||
زماناجرا)؛ در غیر این صورت مدل زندهٔ ``Customer`` استفاده میشود.
|
||||
"""
|
||||
if apps is not None:
|
||||
Customer = apps.get_model("admin_panel", "Customer")
|
||||
else:
|
||||
from ..models import Customer
|
||||
|
||||
stats = {"customers": 0, "fields": 0, "noted": 0}
|
||||
qs = Customer.objects.all().only(
|
||||
"id", "phone1", "phone2", "phone3", "notes"
|
||||
).order_by("id")
|
||||
for customer in qs.iterator(chunk_size=batch_size):
|
||||
before_notes = (customer.notes or "").strip()
|
||||
changed = clean_customer(customer, dry_run=dry_run)
|
||||
if changed:
|
||||
stats["customers"] += 1
|
||||
stats["fields"] += changed
|
||||
if (not dry_run) and (customer.notes or "").strip() != before_notes:
|
||||
stats["noted"] += 1
|
||||
return stats
|
||||
Reference in new issue
Block a user