140 lines
6.0 KiB
Python
140 lines
6.0 KiB
Python
"""پاکسازی یکبارهٔ شمارههای قدیمی مشتریان (فیلدهای ``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
|