Watch
1
0
Fork
You've already forked Seyyed_arc
0
forked from hesabix/arc
Seyyed_arc/docs/PERSON_MIGRATION_ANALYSIS.md
2026-04-14 19:34:55 +03:30

15 KiB
Executable file
Raw Permalink Blame History

تحلیل انتقال اشخاص از hesabixOld به hesabixpy

خلاصه اجرایی

این سند تحلیل نحوه انتقال اشخاص (persons/contacts) از دیتابیس قدیمی (hesabixOld) به دیتابیس جدید (hesabixpy) را ارائه می‌دهد.

وضعیت فعلی

دیتابیس قدیمی (hesabixOld)

جدول person

  • تعداد اشخاص: 46,646
  • تعداد کسب و کارهای دارای شخص: 2,597
  • ساختار:
    • id: شناسه یکتا
    • bid_id: شناسه کسب و کار (ارجاع به business.id)
    • code: کد یکتا در کسب و کار (bigint)
    • nikename: نام مستعار (varchar(255))
    • name: نام (varchar(255))
    • tel: تلفن (varchar(12))
    • mobile: موبایل (varchar(12))
    • mobile2: موبایل دوم (varchar(15))
    • address: آدرس (varchar(255))
    • des: توضیحات (varchar(255))
    • company: نام شرکت (varchar(255))
    • shenasemeli: شناسه ملی (varchar(255))
    • codeeghtesadi: شناسه اقتصادی (varchar(255))
    • sabt: شماره ثبت (varchar(255))
    • keshvar: کشور (varchar(255))
    • ostan: استان (varchar(255))
    • shahr: شهر (varchar(255))
    • postalcode: کد پستی (varchar(255))
    • email: ایمیل (varchar(255))
    • website: وب‌سایت (varchar(255))
    • fax: فکس (varchar(255))
    • birthday: تاریخ تولد (varchar(255))
    • payment_id: شناسه پرداخت (varchar(255))
    • prelabel_id: شناسه پیش‌برچسب (int)
    • tags: تگ‌ها (longtext)
    • فیلدهای خاص: plug_noghre_morsa, plug_noghre_hakak, plug_noghre_tarash, employe, plug_noghre_ghalam

آمار:

  • اشخاص با شرکت: 4,250 (9.1%)
  • اشخاص با شناسه ملی: 1,072 (2.3%)
  • اشخاص قابل انتقال: 44,754 (96.0%)
  • کسب و کارهای قابل انتقال: 2,356

جدول person_type

  • ساختار:
    • id: شناسه یکتا
    • label: برچسب (varchar(255)) - مثل "مشتری", "تامین‌کننده"
    • code: کد (varchar(255)) - مثل "customer", "supplier"

انواع موجود:

  • مشتری (customer)
  • بازاریاب (marketer)
  • کارمند (emplyee)
  • تامین‌کننده (supplier)
  • همکار (colleague)
  • فروشنده (salesman)

جدول person_person_type

  • ساختار:
    • person_id: شناسه شخص (ارجاع به person.id)
    • person_type_id: شناسه نوع شخص (ارجاع به person_type.id)

آمار:

  • کل رکوردها: 21,356
  • اشخاص منحصر به فرد با نوع: 19,751
  • اشخاص با چندین نوع: 916

توزیع انواع:

  • مشتری: 14,314 (67.0%)
  • تامین‌کننده: 2,705 (12.7%)
  • همکار: 2,077 (9.7%)
  • کارمند: 1,097 (5.1%)
  • فروشنده: 906 (4.2%)
  • بازاریاب: 257 (1.2%)

دیتابیس جدید (hesabixpy)

جدول persons

  • تعداد اشخاص فعلی: 122
  • ساختار:
    • id: شناسه یکتا
    • business_id: شناسه کسب و کار (ارجاع به businesses.id)
    • code: کد یکتا در هر کسب و کار (integer)
    • alias_name: نام مستعار (varchar(255)) - الزامی
    • first_name: نام (varchar(100))
    • last_name: نام خانوادگی (varchar(100))
    • person_types: لیست انواع شخص به صورت JSON (text) - الزامی
    • company_name: نام شرکت (varchar(255))
    • payment_id: شناسه پرداخت (varchar(100))
    • national_id: شناسه ملی (varchar(20))
    • registration_number: شماره ثبت (varchar(50))
    • economic_id: شناسه اقتصادی (varchar(50))
    • country: کشور (varchar(100))
    • province: استان (varchar(100))
    • city: شهرستان (varchar(100))
    • address: آدرس (text)
    • postal_code: کد پستی (varchar(20))
    • phone: تلفن (varchar(20))
    • mobile: موبایل (varchar(20))
    • fax: فکس (varchar(20))
    • email: ایمیل (varchar(255))
    • website: وب‌سایت (varchar(255))
    • share_count: تعداد سهام (integer)
    • commission_sale_percent: درصد پورسانت از فروش (decimal(5,2))
    • commission_sales_return_percent: درصد پورسانت از برگشت از فروش (decimal(5,2))
    • commission_sales_amount: مبلغ فروش مبنا برای پورسانت (decimal(12,2))
    • commission_sales_return_amount: مبلغ برگشت از فروش مبنا برای پورسانت (decimal(12,2))
    • commission_exclude_discounts: عدم محاسبه تخفیف در پورسانت (boolean)
    • commission_exclude_additions_deductions: عدم محاسبه اضافات و کسورات فاکتور در پورسانت (boolean)
    • commission_post_in_invoice_document: ثبت پورسانت در سند حسابداری فاکتور (boolean)
    • credit_limit: سقف اعتبار شخص (decimal(14,2))
    • credit_check_enabled: فعال بودن بررسی اعتبار (boolean)
    • created_at: تاریخ ایجاد (datetime)
    • updated_at: تاریخ به‌روزرسانی (datetime)

نکته مهم: person_types به صورت JSON array نگهداری می‌شود، مثل: ["مشتری", "تامین‌کننده"]

استراتژی انتقال

تبدیل فیلدها

فیلد قدیمی فیلد جدید تبدیل
id - نگه‌داری در mapping table (اختیاری)
bid_id business_id نگاشت از business_id_mapping
code code تبدیل bigint به integer (با بررسی محدوده)
nikename alias_name مستقیم (الزامی)
name first_name, last_name تقسیم نام به نام و نام خانوادگی
company company_name مستقیم
shenasemeli national_id مستقیم
codeeghtesadi economic_id مستقیم
sabt registration_number مستقیم
keshvar country مستقیم
ostan province مستقیم
shahr city مستقیم
address address مستقیم
postalcode postal_code مستقیم
tel phone مستقیم
mobile mobile مستقیم
mobile2 - حذف (یا در توضیحات)
fax fax مستقیم
email email مستقیم
website website مستقیم
birthday - حذف (یا در توضیحات)
payment_id payment_id مستقیم
des - حذف (یا در توضیحات)
tags - حذف (یا در جدول جداگانه)
person_person_type person_types تبدیل به JSON array

تبدیل name به first_name و last_name

الگوریتم:

def split_name(name: str | None) -> Tuple[str | None, str | None]:
	"""
	تبدیل name به first_name و last_name
	
	Args:
		name: نام کامل (مثل "علی زیوری" یا "صادق احمدیان")
	
	Returns:
		(first_name, last_name)
	"""
	if not name or not name.strip():
		return (None, None)
	
	name = name.strip()
	parts = name.split()
	
	if len(parts) == 0:
		return (None, None)
	elif len(parts) == 1:
		return (parts[0], None)
	else:
		# اولین کلمه = نام، بقیه = نام خانوادگی
		first_name = parts[0]
		last_name = ' '.join(parts[1:])
		return (first_name, last_name)

نکته: اگر name خالی باشد، از nikename استفاده می‌شود.

تبدیل person_types

چالش:

  • در قدیمی: جدول person_person_type با چندین رکورد برای هر شخص
  • در جدید: فیلد person_types به صورت JSON array

راه‌حل:

  1. برای هر شخص، تمام person_type_id را از person_person_type بخوان
  2. label هر نوع را از person_type بگیر
  3. تبدیل به JSON array: ["مشتری", "تامین‌کننده"]
  4. اگر شخصی نوع نداشت، پیش‌فرض: ["مشتری"]

الگوریتم:

def get_person_types(old_person_id: int, person_type_mapping: Dict[int, str]) -> List[str]:
	"""
	دریافت انواع شخص از دیتابیس قدیمی
	
	Args:
		old_person_id: شناسه شخص قدیمی
		person_type_mapping: mapping {person_type_id: label}
	
	Returns:
		لیست انواع شخص (مثل ["مشتری", "تامین‌کننده"])
	"""
	query = text("""
		SELECT person_type_id
		FROM hesabixOld.person_person_type
		WHERE person_id = :person_id
	""")
	results = old_db.execute(query, {"person_id": old_person_id}).fetchall()
	
	types = []
	for row in results:
		type_label = person_type_mapping.get(row.person_type_id)
		if type_label:
			types.append(type_label)
	
	# اگر نوعی نداشت، پیش‌فرض مشتری
	if not types:
		types = ["مشتری"]
	
	return types

def convert_person_types_to_json(types: List[str]) -> str:
	"""تبدیل لیست انواع به JSON"""
	return json.dumps(types, ensure_ascii=False)

Mapping انواع:

  • مشتری (customer) → "مشتری"
  • بازاریاب (marketer) → "بازاریاب"
  • کارمند (emplyee) → "کارمند"
  • تامین‌کننده (supplier) → "تامین‌کننده"
  • همکار (colleague) → "همکار"
  • فروشنده (salesman) → "فروشنده"

تبدیل code

چالش:

  • در قدیمی: code به صورت bigint
  • در جدید: code به صورت integer (محدود به 2147483647)

راه‌حل:

def convert_code(code: int | None) -> int | None:
	"""تبدیل code از bigint به integer"""
	if code is None:
		return None
	
	# محدود کردن به محدوده integer
	if code > 2147483647:
		return None  # یا استفاده از code جدید
	
	return int(code)

تبدیل alias_name

الگوریتم:

def get_alias_name(nikename: str | None, name: str | None) -> str:
	"""
	دریافت alias_name از nikename یا name
	
	Args:
		nikename: نام مستعار
		name: نام کامل
	
	Returns:
		alias_name (الزامی)
	"""
	if nikename and nikename.strip():
		return nikename.strip()
	elif name and name.strip():
		return name.strip()
	else:
		# اگر هیچکدام نبود، از "شخص بدون نام" استفاده می‌کنیم
		return "شخص بدون نام"

الگوریتم انتقال

مرحله 1: آماده‌سازی

  1. ایجاد business_id_mapping: از انتقال کسب و کارها
  2. ایجاد person_type_mapping: mapping بین person_type.id و person_type.label

مرحله 2: فیلتر اشخاص برای انتقال

شرایط انتقال:

  1. شخص باید bid_id داشته باشد
  2. bid_id باید در business_id_mapping وجود داشته باشد
  3. شخص نباید در دیتابیس جدید وجود داشته باشد (بر اساس business_id و code)

SQL برای انتخاب:

SELECT p.*
FROM hesabixOld.person p
INNER JOIN business_id_mapping m ON p.bid_id = m.old_business_id
WHERE NOT EXISTS (
	SELECT 1 FROM hesabixpy.persons new
	WHERE new.business_id = m.new_business_id
	  AND new.code = p.code
)
ORDER BY p.bid_id, p.id;

مرحله 3: پردازش هر شخص

مراحل:

  1. نگاشت bid_id → business_id
  2. تبدیل code (bigint به integer)
  3. تبدیل nikename/name → alias_name
  4. تقسیم name → first_name و last_name
  5. تبدیل person_person_type → person_types (JSON)
  6. تبدیل سایر فیلدها
  7. درج در دیتابیس جدید

مرحله 4: مدیریت موارد خاص

سناریو 1: code خارج از محدوده integer

  • عمل: code = NULL یا استفاده از code جدید
  • لاگ: ثبت در لاگ

سناریو 2: alias_name خالی

  • عمل: استفاده از "شخص بدون نام"
  • لاگ: ثبت در لاگ

سناریو 3: person_types خالی

  • عمل: پیش‌فرض ["مشتری"]
  • لاگ: ثبت در لاگ

سناریو 4: کد تکراری

  • عمل: skip کردن با reason "duplicate_code"
  • لاگ: ثبت در لاگ

آمار و ارقام

توزیع داده‌ها

  • کل اشخاص: 46,646
  • اشخاص قابل انتقال: 44,754 (96.0%)
  • کسب و کارهای قابل انتقال: 2,356
  • اشخاص با شرکت: 4,250 (9.1%)
  • اشخاص با شناسه ملی: 1,072 (2.3%)
  • اشخاص با نوع: 19,751 (42.3%)
  • اشخاص با چندین نوع: 916 (2.0%)

توزیع انواع شخص

  • مشتری: 14,314 (67.0%)
  • تامین‌کننده: 2,705 (12.7%)
  • همکار: 2,077 (9.7%)
  • کارمند: 1,097 (5.1%)
  • فروشنده: 906 (4.2%)
  • بازاریاب: 257 (1.2%)

قابل انتقال

  • کسب و کارهای منتقل شده: 3,812
  • کسب و کارهای دارای شخص که منتقل شده‌اند: 2,356
  • اشخاص قابل انتقال: 44,754
  • اشخاص با code خارج از محدوده integer: 0 (همه code ها در محدوده هستند)

نکات مهم

  1. حجم متوسط داده: 44,754 شخص - نیاز به پردازش batch به batch
  2. person_types: باید از جدول person_person_type به JSON array تبدیل شود
  3. alias_name: الزامی است - باید از nikename یا name استفاده شود
  4. code: باید از bigint به integer تبدیل شود (با بررسی محدوده)
  5. name splitting: باید به first_name و last_name تقسیم شود

ریسک‌ها و راه‌حل‌ها

ریسک 1: code خارج از محدوده integer

راه‌حل: بررسی محدوده و استفاده از NULL یا code جدید وضعیت: همه code ها در محدوده integer هستند (max: 21,252)

ریسک 2: alias_name خالی

راه‌حل: استفاده از پیش‌فرض "شخص بدون نام"

ریسک 3: person_types خالی

راه‌حل: استفاده از پیش‌فرض ["مشتری"]

ریسک 4: کدهای تکراری

راه‌حل: بررسی unique constraint قبل از درج

ریسک 5: اشخاص با چندین نوع

راه‌حل: تبدیل همه انواع به JSON array

نتیجه‌گیری

انتقال اشخاص نیاز به:

  • ✅ نگاشت business_id
  • ✅ تبدیل code (bigint به integer)
  • ✅ تبدیل nikename/name به alias_name
  • ✅ تقسیم name به first_name و last_name
  • ✅ تبدیل person_person_type به person_types (JSON)
  • ✅ پردازش batch به batch

تخمین زمان: با batch size 500، حدود 90 batch نیاز است که می‌تواند 10-20 دقیقه طول بکشد.

آمار دقیق قابل انتقال:

  • اشخاص: 44,754
  • کسب و کارهای دارای شخص: 2,356
  • اشخاص با نوع: 19,751
  • اشخاص با چندین نوع: 916