19 KiB
Executable file
سناریوی انتقال کسب و کارها از hesabixOld به hesabixpy
خلاصه اجرایی
این سند سناریوی کامل انتقال کسب و کارها (businesses) از دیتابیس قدیمی (hesabixOld) به دیتابیس جدید (hesabixpy) را ارائه میدهد. تمرکز اصلی بر روی نحوه انتقال کسب و کارها و نگاشت صحیح owner_id به کاربران منتقل شده است.
وضعیت فعلی
دیتابیس قدیمی (hesabixOld)
- جدول:
business - تعداد کسب و کارها: 4,473 کسب و کار
- فیلدهای کلیدی:
id: شناسه یکتاowner_id: شناسه مالک (ارجاع به جدول user)name: نام کسب و کارlegal_name: نام قانونیfield: زمینه فعالیت (varchar - مقادیر مختلف)type: نوع کسب و کار (varchar - مقادیر: فروشگاه، مغازه، شخصی، شرکت، موسسه، باشگاه، اتحادیه)shenasemeli: شناسه ملیcodeeghtesadi: کد اقتصادیshomaresabt: شماره ثبتcountry: کشورostan: استانshahrestan: شهرستانpostalcode: کد پستیtel: تلفنmobile: موبایلaddress: آدرسemail: ایمیلwesite: وبسایت (با typo)- و فیلدهای تنظیمات دیگر...
دیتابیس جدید (hesabixpy)
- جدول:
businesses - تعداد کسب و کارهای فعلی: 63 کسب و کار
- فیلدهای کلیدی:
id: شناسه یکتاowner_id: شناسه مالک (ارجاع به جدول users - ForeignKey)name: نام کسب و کارbusiness_type: نوع کسب و کار (ENUM: شرکت، مغازه، فروشگاه، اتحادیه، باشگاه، موسسه، شخصی)business_field: زمینه فعالیت (ENUM: تولیدی، بازرگانی، خدماتی، سایر)national_id: شناسه ملیregistration_number: شماره ثبتeconomic_id: کد اقتصادیcountry: کشورprovince: استانcity: شهرستانpostal_code: کد پستیphone: تلفنmobile: موبایلaddress: آدرسdefault_currency_id: ارز پیشفرضlogo_file_id: شناسه فایل لوگوstamp_file_id: شناسه فایل مهرdefault_credit_limit: سقف اعتبار پیشفرضcheck_credit_enabled_by_default: بررسی اعتبار به صورت پیشفرضcreated_at: تاریخ ایجادupdated_at: تاریخ بهروزرسانیdeleted_at: تاریخ حذف (soft delete)
مشکلات شناسایی شده
- نگاشت owner_id: باید از شناسه کاربر قدیمی به شناسه کاربر جدید نگاشت شود
- تبدیل type و field: باید از varchar به ENUM تبدیل شوند
- تفاوت در نام فیلدها:
ostan→province,shahrestan→city,tel→phone - فیلدهای جدید:
default_currency_id,logo_file_id,stamp_file_idو... در قدیمی وجود ندارند - 37 کسب و کار از دیتابیس قدیمی در دیتابیس جدید وجود دارند (احتمالاً کسب و کارهای تست)
آمار و ارقام
- کل کسب و کارها در قدیمی: 4,473
- کسب و کارها با owner_id: 4,473 (همه کسب و کارها owner دارند)
- کسب و کارها با owner معتبر: 4,415 (با کاربر فعال و email/mobile)
- کسب و کارهای قابل انتقال: ~4,062 (با owner که در دیتابیس جدید وجود دارد)
- کسب و کارهای موجود در جدید: 63
توزیع نوع کسب و کارها (type)
- مغازه: 1,410
- شرکت: 1,121
- شخصی: 1,003
- فروشگاه: 718
- موسسه: 182
- باشگاه: 26
- اتحادیه: 13
سناریوی انتقال
مرحله 1: آمادهسازی و بررسی
1.1 بررسی دادههای قدیمی
-- بررسی کسب و کارها با owner_id معتبر
SELECT COUNT(*) FROM hesabixOld.business WHERE owner_id IS NOT NULL;
-- بررسی کسب و کارها با owner_id که در دیتابیس جدید وجود دارد
SELECT COUNT(*)
FROM hesabixOld.business old
INNER JOIN hesabixpy.users new ON old.owner_id = (
SELECT old_user.id
FROM hesabixOld.user old_user
INNER JOIN hesabixpy.users new_user ON old_user.email = new_user.email
WHERE old_user.id = old.owner_id
LIMIT 1
);
-- بررسی توزیع type
SELECT type, COUNT(*) as cnt
FROM hesabixOld.business
WHERE type IS NOT NULL
GROUP BY type
ORDER BY cnt DESC;
-- بررسی توزیع field
SELECT field, COUNT(*) as cnt
FROM hesabixOld.business
WHERE field IS NOT NULL
GROUP BY field
ORDER BY cnt DESC
LIMIT 20;
1.2 ایجاد جدول mapping برای owner_id
-- ایجاد جدول موقت برای نگهداری mapping کاربران
CREATE TEMPORARY TABLE user_id_mapping AS
SELECT
old.id as old_user_id,
new.id as new_user_id
FROM hesabixOld.user old
INNER JOIN hesabixpy.users new ON (
old.email = new.email OR
(old.mobile IS NOT NULL AND new.mobile IS NOT NULL AND old.mobile = new.mobile)
)
WHERE old.active = 1;
مرحله 2: تبدیل و نگاشت دادهها
2.1 تبدیل فیلدها
| فیلد قدیمی | فیلد جدید | تبدیل |
|---|---|---|
id |
- | نگهداری در mapping table (اختیاری) |
owner_id |
owner_id |
نگاشت از user_id_mapping |
name |
name |
مستقیم |
legal_name |
- | حذف (یا در name نگه داریم) |
type |
business_type |
تبدیل به ENUM |
field |
business_field |
تبدیل به ENUM |
shenasemeli |
national_id |
مستقیم |
codeeghtesadi |
economic_id |
مستقیم |
shomaresabt |
registration_number |
مستقیم |
country |
country |
مستقیم |
ostan |
province |
مستقیم |
shahrestan |
city |
مستقیم |
postalcode |
postal_code |
مستقیم |
tel |
phone |
مستقیم |
mobile |
mobile |
مستقیم |
address |
address |
مستقیم |
email |
- | حذف (در جدول users است) |
wesite |
- | حذف (در صورت نیاز میتوان در جدول جداگانه نگه داشت) |
2.2 تبدیل type به business_type (ENUM)
مقادیر قدیمی → مقادیر جدید:
"فروشگاه"→BusinessType.STORE("فروشگاه")"مغازه"→BusinessType.SHOP("مغازه")"شخصی"→BusinessType.INDIVIDUAL("شخصی")"شرکت"→BusinessType.COMPANY("شرکت")"موسسه"→BusinessType.INSTITUTE("موسسه")"باشگاه"→BusinessType.CLUB("باشگاه")"اتحادیه"→BusinessType.UNION("اتحادیه")NULLیا مقادیر نامعتبر →BusinessType.SHOP(پیشفرض)
الگوریتم:
def convert_business_type(old_type: str | None) -> str:
mapping = {
"فروشگاه": "فروشگاه",
"مغازه": "مغازه",
"شخصی": "شخصی",
"شرکت": "شرکت",
"موسسه": "موسسه",
"باشگاه": "باشگاه",
"اتحادیه": "اتحادیه"
}
if old_type and old_type in mapping:
return mapping[old_type]
return "مغازه" # پیشفرض
2.3 تبدیل field به business_field (ENUM)
مقادیر قدیمی → مقادیر جدید:
"تولید","تولیدی","تولید کننده"→BusinessField.MANUFACTURING("تولیدی")"بازرگانی","فروش","فروشگاه"→BusinessField.TRADING("بازرگانی")"خدماتی"→BusinessField.SERVICE("خدماتی")- سایر مقادیر →
BusinessField.OTHER("سایر")
الگوریتم:
def convert_business_field(old_field: str | None) -> str:
if not old_field:
return "سایر"
old_field_lower = old_field.lower().strip()
# تولیدی
if any(keyword in old_field_lower for keyword in ["تولید", "ساخت"]):
return "تولیدی"
# بازرگانی
if any(keyword in old_field_lower for keyword in ["بازرگانی", "فروش", "خرید", "تجارت"]):
return "بازرگانی"
# خدماتی
if any(keyword in old_field_lower for keyword in ["خدمات", "خدماتی", "مشاوره", "آموزش"]):
return "خدماتی"
# سایر
return "سایر"
2.4 نگاشت owner_id
الگوریتم:
- جستجو در
user_id_mappingبرای یافتنnew_user_idبر اساسold_owner_id - اگر پیدا شد: استفاده از آن
- اگر پیدا نشد: skip کردن کسب و کار (مالک در دیتابیس جدید وجود ندارد)
SQL برای ایجاد mapping:
-- ایجاد جدول mapping کاربران
CREATE TEMPORARY TABLE user_id_mapping AS
SELECT
old.id as old_user_id,
new.id as new_user_id
FROM hesabixOld.user old
INNER JOIN hesabixpy.users new ON (
(old.email IS NOT NULL AND new.email IS NOT NULL AND old.email = new.email) OR
(old.mobile IS NOT NULL AND new.mobile IS NOT NULL AND old.mobile = new.mobile)
)
WHERE old.active = 1;
مرحله 3: الگوریتم انتقال
3.1 فیلتر کسب و کارها برای انتقال
شرایط انتقال:
- کسب و کار باید
owner_idداشته باشد owner_idباید درuser_id_mappingوجود داشته باشد (یعنی کاربر منتقل شده باشد)- کسب و کار نباید در دیتابیس جدید وجود داشته باشد (بر اساس owner_id و name)
SQL برای انتخاب کسب و کارها:
SELECT b.*
FROM hesabixOld.business b
INNER JOIN user_id_mapping m ON b.owner_id = m.old_user_id
WHERE b.owner_id IS NOT NULL
AND NOT EXISTS (
SELECT 1 FROM hesabixpy.businesses new
WHERE new.owner_id = m.new_user_id
AND new.name = b.name
)
ORDER BY b.id;
3.2 پردازش هر کسب و کار
مراحل پردازش:
-
بررسی owner_id:
- جستجو در
user_id_mapping - اگر پیدا نشد: skip با reason "owner_not_migrated"
- جستجو در
-
تبدیل دادهها:
- تبدیل
typeبهbusiness_type(ENUM) - تبدیل
fieldبهbusiness_field(ENUM) - تبدیل نام فیلدها (
ostan→province,shahrestan→city,tel→phone) - normalize کردن
mobileوphone - تبدیل
date_submitبهcreated_at(اگر وجود دارد)
- تبدیل
-
ایجاد کسب و کار جدید:
- درج در جدول
businesses - تنظیم
owner_idبهnew_user_id - تنظیم
created_atوupdated_at
- درج در جدول
-
مدیریت خطاها:
- در صورت خطا: ثبت در لاگ با جزئیات
- ادامه با کسب و کار بعدی
3.3 مدیریت کسب و کارهای تکراری
سناریو 1: کسب و کار با همان owner و name در دیتابیس جدید وجود دارد
- بررسی: آیا کسب و کار با همان
owner_idوnameدر دیتابیس جدید وجود دارد؟ - عمل: skip کردن با reason "already_exists"
- ثبت در لاگ
سناریو 2: owner_id در دیتابیس جدید وجود ندارد
- بررسی: آیا
owner_idدرuser_id_mappingوجود دارد؟ - عمل: skip کردن با reason "owner_not_migrated"
- ثبت در لاگ
مرحله 4: فیلدهای اختیاری و پیشفرض
4.1 فیلدهای جدید در دیتابیس جدید
فیلدهایی که در قدیمی وجود ندارند:
default_currency_id:NULL(میتوان بعداً تنظیم کرد)logo_file_id:NULL(فایلها باید جداگانه منتقل شوند)stamp_file_id:NULL(فایلها باید جداگانه منتقل شوند)default_credit_limit:NULL(میتوان بعداً تنظیم کرد)check_credit_enabled_by_default:false(پیشفرض)
4.2 تبدیل تاریخها
الگوریتم:
- اگر
date_submitوجود دارد: تبدیل بهdatetimeو استفاده درcreated_at - در غیر این صورت: استفاده از
datetime.utcnow() updated_at: همیشهdatetime.utcnow()
مرحله 5: اسکریپت انتقال
5.1 ساختار اسکریپت
فایل: scripts/migrate_businesses_from_old_db.py
ویژگیها:
- اتصال به هر دو دیتابیس
- ایجاد
user_id_mappingاز کاربران منتقل شده - خواندن کسب و کارها از دیتابیس قدیمی
- تبدیل و نگاشت دادهها
- درج در دیتابیس جدید
- لاگگیری کامل
- قابلیت dry-run (تست بدون تغییر)
- پردازش batch به batch
5.2 پارامترهای اسکریپت
python scripts/migrate_businesses_from_old_db.py [OPTIONS]
پارامترها:
--dry-run: اجرای تست بدون تغییر در دیتابیس--batch-size: تعداد کسب و کارها در هر batch (پیشفرض: 100)--start-id: شروع از شناسه خاص--limit: محدود کردن تعداد کسب و کارها--old-db: نام دیتابیس قدیمی (پیشفرض: hesabixOld)--new-db: نام دیتابیس جدید (پیشفرض: hesabixpy)--db-user: نام کاربری دیتابیس (پیشفرض: root)--db-password: رمز عبور دیتابیس (پیشفرض: 136431)--db-host: آدرس دیتابیس (پیشفرض: localhost)--db-port: پورت دیتابیس (پیشفرض: 3306)
5.3 خروجی و گزارش
گزارش شامل:
- تعداد کل کسب و کارهای پردازش شده
- تعداد کسب و کارهای منتقل شده
- تعداد کسب و کارهای skip شده (با دلایل):
owner_not_migrated: مالک در دیتابیس جدید وجود نداردalready_exists: کسب و کار در دیتابیس جدید وجود داردinvalid_data: دادههای نامعتبر
- تعداد خطاها
- لیست خطاها با جزئیات
مرحله 6: تست و اعتبارسنجی
6.1 تستهای واحد
-
تست تبدیل business_type:
- ورودی:
"فروشگاه"→ خروجی:"فروشگاه" - ورودی:
"مغازه"→ خروجی:"مغازه" - ورودی:
NULL→ خروجی:"مغازه"(پیشفرض)
- ورودی:
-
تست تبدیل business_field:
- ورودی:
"تولیدی"→ خروجی:"تولیدی" - ورودی:
"بازرگانی"→ خروجی:"بازرگانی" - ورودی:
"خدماتی"→ خروجی:"خدماتی" - ورودی:
"کامپیوتری"→ خروجی:"سایر"
- ورودی:
-
تست نگاشت owner_id:
- تست با owner_id موجود در mapping
- تست با owner_id غیرموجود در mapping
6.2 تست یکپارچگی
-
تست انتقال نمونه:
- انتخاب 10 کسب و کار نمونه از دیتابیس قدیمی
- انتقال آنها
- بررسی صحت دادهها
- بررسی foreign key constraint (owner_id)
-
تست تکراری بودن:
- تست skip کردن کسب و کارهای تکراری
6.3 تست عملکرد
- تست سرعت:
- اندازهگیری زمان انتقال 1000 کسب و کار
- بهینهسازی در صورت نیاز
مرحله 7: اجرای نهایی
7.1 آمادهسازی
-
پشتیبانگیری:
- پشتیبان از دیتابیس
hesabixpy - پشتیبان از دیتابیس
hesabixOld
- پشتیبان از دیتابیس
-
تست در محیط staging:
- اجرای کامل در محیط تست
- بررسی نتایج
7.2 اجرا
-
اجرای dry-run:
python scripts/migrate_businesses_from_old_db.py --dry-run --verbose -
بررسی نتایج dry-run:
- بررسی تعداد کسب و کارها
- بررسی خطاها
- بررسی owner_id mapping
-
اجرای واقعی:
python scripts/migrate_businesses_from_old_db.py --batch-size 100 --verbose -
نظارت:
- نظارت بر لاگها
- بررسی خطاها
- بررسی عملکرد
7.3 پس از اجرا
-
اعتبارسنجی:
- مقایسه تعداد کسب و کارها
- بررسی foreign key constraint
- بررسی یکپارچگی دادهها
- تست ایجاد کسب و کار جدید
-
پاکسازی:
- حذف جدول موقت
user_id_mapping(خودکار)
- حذف جدول موقت
خلاصه مراحل
- ⏳ بررسی و آمادهسازی دادهها
- ⏳ ایجاد user_id_mapping
- ⏳ نوشتن اسکریپت انتقال
- ⏳ تست در محیط staging (با --dry-run)
- ⏳ پشتیبانگیری
- ⏳ اجرای انتقال
- ⏳ اعتبارسنجی
نکات مهم
- نگاشت owner_id: فقط کسب و کارهایی منتقل میشوند که مالک آنها در دیتابیس جدید وجود دارد
- تبدیل ENUM: type و field باید به درستی به ENUM تبدیل شوند
- کاربران تکراری: کسب و کارهایی که در دیتابیس جدید وجود دارند را skip میکنیم
- فیلدهای جدید: فیلدهای جدید در دیتابیس جدید با مقادیر پیشفرض یا NULL تنظیم میشوند
- لاگگیری: تمام مراحل را لاگ میکنیم برای audit و debug
ریسکها و راهحلها
ریسک 1: از دست رفتن دادهها
راهحل: پشتیبانگیری کامل قبل از انتقال
ریسک 2: خطا در انتقال
راهحل: استفاده از transaction و rollback در صورت خطا
ریسک 3: کسب و کارهای تکراری
راهحل: بررسی دقیق قبل از انتقال و skip کردن تکراریها
ریسک 4: owner_id نامعتبر
راهحل: بررسی وجود owner_id در user_id_mapping قبل از انتقال
ریسک 5: تبدیل نامعتبر ENUM
راهحل: استفاده از مقادیر پیشفرض برای مقادیر نامعتبر
نتیجهگیری
این سناریو راهنمای کامل انتقال کسب و کارها از دیتابیس قدیمی به جدید است. تمرکز اصلی بر روی:
- نگاشت صحیح owner_id به کاربران منتقل شده
- تبدیل صحیح type و field به ENUM
- مدیریت کسب و کارهای تکراری
- حفظ یکپارچگی دادهها
پس از اجرای موفق، کسب و کارها با مالکهای صحیح در دیتابیس جدید ایجاد میشوند و میتوانند استفاده شوند.