Files
tehran_tala/docs/migration-precheck.sql
T
s.esmaeiliandClaude Opus 4.6 19d490e790 feat: accounting integration (D27), SMS relay driver (D26), rebuild user apps
- Add Tahesab/Zarrin accounting sync module with admin panel UI
- Add SMS relay driver as default provider, refactor SMS into driver pattern
- Update customer services, orders, products, and catalog queries
- Rebuild user-app and user-app-v2 dist assets
- Add Prisma migration for accounting tables
- Update docker-compose and env examples
- Update parity docs (DECISIONS, ISSUES, PROGRESS)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-10-06 16:48:36 +03:30

322 lines
18 KiB
SQL
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
-- ============================================================================
-- پیش‌بررسی مهاجرت — فقط شمارش، صفر داده
-- ============================================================================
--
-- روی دیتابیس MySQL قدیمی (اسکیمای `oapi`) اجرا شود.
--
-- ⚠️ این فایل فقط SELECT COUNT / SUM / MIN / MAX دارد.
-- هیچ INSERT / UPDATE / DELETE / DROP / ALTER در آن نیست.
-- هیچ ستون متنی (نام، موبایل، کد ملی، آدرس) در خروجی ظاهر نمی‌شود.
--
-- اجرا:
-- mysql -u USER -p -h HOST -P 3305 oapi < migration-precheck.sql > out.txt
--
-- سپس محتوای out.txt کپی شود. خروجی فقط عدد است.
--
-- مرجع: docs/DATA-MIGRATION-MAP.md §۳ · §۵.۳ · §۵.۵ · §۵.۶
-- ============================================================================
-- ---------------------------------------------------------------------------
-- بخش ۱ — حجم کار: تعداد ردیف زنده و حذف‌شده‌ی نرم
-- مرجع: §۵.۶ — ردیف حذف‌شده نباید منتقل شود
-- ---------------------------------------------------------------------------
SELECT '=== 1. ROW COUNTS ===' AS section;
SELECT 'customers' AS tbl, COUNT(*) AS live, SUM(deleted_at IS NOT NULL) AS soft_deleted FROM customers
UNION ALL SELECT 'users', COUNT(*), SUM(deleted_at IS NOT NULL) FROM users
UNION ALL SELECT 'orders', COUNT(*), SUM(deleted_at IS NOT NULL) FROM orders
UNION ALL SELECT 'order_items', COUNT(*), SUM(deleted_at IS NOT NULL) FROM order_items
UNION ALL SELECT 'cashes', COUNT(*), SUM(deleted_at IS NOT NULL) FROM cashes
UNION ALL SELECT 'product', COUNT(*), SUM(deleted_at IS NOT NULL) FROM product
UNION ALL SELECT 'variant', COUNT(*), SUM(deleted_at IS NOT NULL) FROM products_productvarient
UNION ALL SELECT 'prod_image', COUNT(*), SUM(deleted_at IS NOT NULL) FROM products_productimage
UNION ALL SELECT 'category', COUNT(*), SUM(deleted_at IS NOT NULL) FROM category_collection
UNION ALL SELECT 'image', COUNT(*), SUM(deleted_at IS NOT NULL) FROM image
UNION ALL SELECT 'cities', COUNT(*), SUM(deleted_at IS NOT NULL) FROM cities
UNION ALL SELECT 'branches', COUNT(*), SUM(deleted_at IS NOT NULL) FROM branches
UNION ALL SELECT 'workshops', COUNT(*), SUM(deleted_at IS NOT NULL) FROM workshops
UNION ALL SELECT 'custom_orders', COUNT(*), SUM(deleted_at IS NOT NULL) FROM custom_orders
UNION ALL SELECT 'comments', COUNT(*), SUM(deleted_at IS NOT NULL) FROM comments
UNION ALL SELECT 'messages', COUNT(*), 0 FROM messages
UNION ALL SELECT 'notify', COUNT(*), SUM(deleted_at IS NOT NULL) FROM notify
UNION ALL SELECT 'fav', COUNT(*), 0 FROM fav
UNION ALL SELECT 'permission', COUNT(*), 0 FROM permission
UNION ALL SELECT 'config_statics', COUNT(*), SUM(deleted_at IS NOT NULL) FROM config_statics;
-- ---------------------------------------------------------------------------
-- بخش ۲ — 🔴 یکتایی: آیا @unique در Prisma می‌شکند؟
-- مرجع: §۳ — مبدأ هیچ unique index ندارد
-- هر عدد غیرصفر یعنی مهاجرت در میانه متوقف می‌شود
-- ---------------------------------------------------------------------------
SELECT '=== 2. UNIQUENESS (any nonzero = migration breaks) ===' AS section;
-- چند *گروه* موبایل تکراری، و چند ردیف درگیرند
SELECT 'dup_mobile_groups' AS metric, COUNT(*) AS n FROM (
SELECT mobile FROM customers
WHERE deleted_at IS NULL AND mobile IS NOT NULL AND mobile <> ''
GROUP BY mobile HAVING COUNT(*) > 1
) d
UNION ALL
SELECT 'dup_mobile_rows', COALESCE(SUM(c),0) FROM (
SELECT COUNT(*) AS c FROM customers
WHERE deleted_at IS NULL AND mobile IS NOT NULL AND mobile <> ''
GROUP BY mobile HAVING COUNT(*) > 1
) d2
UNION ALL
SELECT 'dup_ncode_groups', COUNT(*) FROM (
SELECT ncode FROM customers
WHERE deleted_at IS NULL AND ncode IS NOT NULL AND ncode <> ''
GROUP BY ncode HAVING COUNT(*) > 1
) d3
UNION ALL
SELECT 'dup_ncode_rows', COALESCE(SUM(c),0) FROM (
SELECT COUNT(*) AS c FROM customers
WHERE deleted_at IS NULL AND ncode IS NOT NULL AND ncode <> ''
GROUP BY ncode HAVING COUNT(*) > 1
) d4;
-- تهی‌بودن: Prisma هر دو را NOT NULL می‌خواهد
SELECT 'empty_mobile' AS metric, SUM(mobile IS NULL OR mobile = '') AS n FROM customers WHERE deleted_at IS NULL
UNION ALL SELECT 'empty_ncode', SUM(ncode IS NULL OR ncode = '') FROM customers WHERE deleted_at IS NULL;
-- تداخل شماره بین مشتری و کاربر ادمین (D21: «کارشناس برنده است»)
SELECT 'mobile_collision_customer_vs_user' AS metric, COUNT(*) AS n
FROM customers c JOIN users u ON u.mobile = c.mobile
WHERE c.deleted_at IS NULL AND u.deleted_at IS NULL
AND c.mobile IS NOT NULL AND c.mobile <> '';
-- یکتایی در جدول users هم بررسی شود
SELECT 'dup_user_mobile_groups' AS metric, COUNT(*) AS n FROM (
SELECT mobile FROM users
WHERE deleted_at IS NULL AND mobile IS NOT NULL AND mobile <> ''
GROUP BY mobile HAVING COUNT(*) > 1
) d;
-- ---------------------------------------------------------------------------
-- بخش ۳ — 🔴 نام: آیا D0 درست است؟
-- مرجع: §۲ — fname/lname از قبل وجود دارند
-- ---------------------------------------------------------------------------
SELECT '=== 3. NAME PARTS (does `name` agree with fname+lname?) ===' AS section;
SELECT
SUM(fname IS NULL OR fname = '') AS empty_fname,
SUM(lname IS NULL OR lname = '') AS empty_lname,
SUM(name IS NULL OR name = '') AS empty_name,
-- ردیف‌هایی که name با اجزا نمی‌خواند → trigger مقصد name را بازنویسی می‌کند
SUM(TRIM(COALESCE(name,'')) <> TRIM(CONCAT(COALESCE(fname,''),' ',COALESCE(lname,'')))) AS name_mismatch,
-- اجزا تهی ولی name پر → تنها حالتی که تجزیه لازم است
SUM((fname IS NULL OR fname = '') AND (lname IS NULL OR lname = '')
AND name IS NOT NULL AND name <> '') AS parts_empty_name_set
FROM customers WHERE deleted_at IS NULL;
-- ---------------------------------------------------------------------------
-- بخش ۴ — enum ها: آیا مقداری بی‌نگاشت می‌ماند؟
-- مرجع: §۵.۳ (type) · §۵.۵ (verify) · §۷.۳ (sts)
-- ---------------------------------------------------------------------------
SELECT '=== 4. ENUMS ===' AS section;
-- type: 1..5 نگاشت دارند. NULL یا خارج از بازه → بی‌صدا APP_SHOPKEEPER می‌شود
SELECT 'customers.type' AS col, CAST(type AS CHAR) AS val, COUNT(*) AS n
FROM customers WHERE deleted_at IS NULL GROUP BY type ORDER BY type;
-- sts و active: دو سوییچ مجزا
SELECT 'customers.sts' AS col, CAST(sts AS CHAR) AS val, COUNT(*) AS n
FROM customers WHERE deleted_at IS NULL GROUP BY sts
UNION ALL
SELECT 'customers.active', CAST(active AS CHAR), COUNT(*)
FROM customers WHERE deleted_at IS NULL GROUP BY active
UNION ALL
SELECT 'customers.is_archive', CAST(is_archive AS CHAR), COUNT(*)
FROM customers WHERE deleted_at IS NULL GROUP BY is_archive;
-- دو ستون تأیید → یک enum چهارحالته. حالت (0,1) در مقصد نماینده ندارد
SELECT 'verify_step_combo' AS col,
CONCAT(COALESCE(CAST(sts_verify_step_one AS CHAR),'NULL'), ',',
COALESCE(CAST(sts_verify_step_two AS CHAR),'NULL')) AS val,
COUNT(*) AS n
FROM customers WHERE deleted_at IS NULL
GROUP BY sts_verify_step_one, sts_verify_step_two;
-- وضعیت سفارش عمده: 0..6 نگاشت دارند
SELECT 'orders.sts' AS col, CAST(sts AS CHAR) AS val, COUNT(*) AS n
FROM orders WHERE deleted_at IS NULL GROUP BY sts ORDER BY sts;
-- اسناد مالی: type → LedgerSourceType (9,1,2,3,4)، confirmed حیاتی است (D24)
SELECT 'cashes.type' AS col, CAST(type AS CHAR) AS val, COUNT(*) AS n
FROM cashes WHERE deleted_at IS NULL GROUP BY type ORDER BY type;
SELECT 'cashes.confirmed' AS col, CAST(confirmed AS CHAR) AS val, COUNT(*) AS n
FROM cashes WHERE deleted_at IS NULL GROUP BY confirmed;
-- ---------------------------------------------------------------------------
-- بخش ۵ — 🔴 پول و وزن: مبنای تطبیق + تأیید اینکه آستانه لازم است
-- مرجع: §۴ — ۱۱۰ ستون double، «صفر اختلاف» دست‌نیافتنی است
-- ---------------------------------------------------------------------------
SELECT '=== 5. MONEY / WEIGHT TOTALS (migration must match these) ===' AS section;
SELECT
COUNT(*) AS n_customers,
CAST(SUM(remain) AS CHAR) AS sum_gold_debt,
CAST(SUM(rial_remain) AS CHAR) AS sum_rial_debt,
CAST(SUM(gram_level) AS CHAR) AS sum_credit_limit,
SUM(remain <> 0) AS n_nonzero_gold,
SUM(rial_remain <> 0) AS n_nonzero_rial,
SUM(remain < 0) AS n_negative_gold,
SUM(rial_remain < 0) AS n_negative_rial
FROM customers WHERE deleted_at IS NULL;
-- 🔴 شاهدِ مشکل double: چند ردیف مقداری دارند که در Decimal(14,4) جا نمی‌شود؟
-- عدد غیرصفر = گِردکردن رخ می‌دهد = «صفر اختلاف» ناممکن است
SELECT 'gold_needs_rounding' AS metric, SUM(remain <> ROUND(remain, 4)) AS n
FROM customers WHERE deleted_at IS NULL
UNION ALL
SELECT 'credit_needs_rounding', SUM(gram_level <> ROUND(gram_level, 4))
FROM customers WHERE deleted_at IS NULL
UNION ALL
SELECT 'order_weight_needs_rounding', SUM(w <> ROUND(w, 4))
FROM orders WHERE deleted_at IS NULL
UNION ALL
SELECT 'cash_amount_needs_rounding', SUM(remain <> ROUND(remain, 4))
FROM cashes WHERE deleted_at IS NULL;
-- فقط اسناد تأییدشده در موجودی حساب می‌شوند (D24)
SELECT 'confirmed_only' AS scope,
CAST(SUM(remain) AS CHAR) AS sum_gold,
CAST(SUM(rial_remain) AS CHAR) AS sum_rial,
COUNT(*) AS n
FROM cashes WHERE deleted_at IS NULL AND confirmed = 1;
-- ---------------------------------------------------------------------------
-- بخش ۶ — یکپارچگی ارجاعی (مبدأ صفر FK دارد — §۱۱)
-- ارجاع یتیم = ردیفی که مهاجرت نمی‌تواند وصل کند
-- ---------------------------------------------------------------------------
SELECT '=== 6. ORPHAN REFERENCES (no FKs exist in source) ===' AS section;
SELECT 'customer.city_id -> cities' AS ref, COUNT(*) AS orphans
FROM customers c LEFT JOIN cities ci ON ci.id = c.city_id
WHERE c.deleted_at IS NULL AND c.city_id IS NOT NULL AND ci.id IS NULL
UNION ALL
SELECT 'customer.reagent_id -> users', COUNT(*)
FROM customers c LEFT JOIN users u ON u.id = c.reagent_id
WHERE c.deleted_at IS NULL AND c.reagent_id IS NOT NULL AND u.id IS NULL
UNION ALL
SELECT 'customer.image_id -> image', COUNT(*)
FROM customers c LEFT JOIN image i ON i.id = c.image_id
WHERE c.deleted_at IS NULL AND c.image_id IS NOT NULL AND i.id IS NULL
UNION ALL
SELECT 'order.customer_id -> customers', COUNT(*)
FROM orders o LEFT JOIN customers c ON c.id = o.customer_id
WHERE o.deleted_at IS NULL AND o.customer_id IS NOT NULL AND c.id IS NULL
UNION ALL
SELECT 'order_items.order_id -> orders', COUNT(*)
FROM order_items oi LEFT JOIN orders o ON o.id = oi.order_id
WHERE oi.deleted_at IS NULL AND oi.order_id IS NOT NULL AND o.id IS NULL
UNION ALL
SELECT 'order_items.variant_id -> variant', COUNT(*)
FROM order_items oi LEFT JOIN products_productvarient v ON v.id = oi.variant_id
WHERE oi.deleted_at IS NULL AND oi.variant_id IS NOT NULL AND v.id IS NULL
UNION ALL
SELECT 'cashes.customer_id -> customers', COUNT(*)
FROM cashes ca LEFT JOIN customers c ON c.id = ca.customer_id
WHERE ca.deleted_at IS NULL AND ca.customer_id IS NOT NULL AND c.id IS NULL
UNION ALL
SELECT 'variant.product_id_id -> product', COUNT(*)
FROM products_productvarient v LEFT JOIN product p ON p.id = v.product_id_id
WHERE v.deleted_at IS NULL AND v.product_id_id IS NOT NULL AND p.id IS NULL
UNION ALL
SELECT 'fav.customer_id -> customers', COUNT(*)
FROM fav f LEFT JOIN customers c ON c.id = f.customer_id
WHERE c.id IS NULL
UNION ALL
SELECT 'fav.product_id -> product', COUNT(*)
FROM fav f LEFT JOIN product p ON p.id = f.product_id
WHERE p.id IS NULL;
-- 🔴 fav هیچ PK ندارد → ردیف تکراری ممکن است (§۱۰)
SELECT 'fav_duplicate_pairs' AS metric, COUNT(*) AS n FROM (
SELECT customer_id, product_id FROM fav
GROUP BY customer_id, product_id HAVING COUNT(*) > 1
) d;
-- permission هم PK ندارد
SELECT 'permission_duplicate_rows' AS metric, COUNT(*) AS n FROM (
SELECT rid, ut FROM permission GROUP BY rid, ut HAVING COUNT(*) > 1
) d;
-- ---------------------------------------------------------------------------
-- بخش ۷ — ستون‌های مبهم: نوع واقعی داده
-- مرجع: §۸ — ayar متنی یا عددی؟ تاریخ جلالی یا میلادی؟
-- ---------------------------------------------------------------------------
SELECT '=== 7. AMBIGUOUS COLUMNS ===' AS section;
-- ayar در مبدأ varchar(20) است ولی مقصد Int? — مقدار غیرعددی می‌شکند
SELECT 'ayar_total' AS metric, COUNT(*) AS n FROM cashes WHERE deleted_at IS NULL AND ayar IS NOT NULL AND ayar <> ''
UNION ALL
SELECT 'ayar_non_numeric', COUNT(*) FROM cashes
WHERE deleted_at IS NULL AND ayar IS NOT NULL AND ayar <> '' AND ayar NOT REGEXP '^[0-9]+$';
-- recive_date / proceed_at در مبدأ varchar هستند — جلالی یا میلادی؟
-- طول رشته و اینکه با 13 یا 14 شروع می‌شود، نوع تقویم را لو می‌دهد
SELECT 'recive_date_set' AS metric, COUNT(*) AS n FROM cashes WHERE deleted_at IS NULL AND recive_date IS NOT NULL AND recive_date <> ''
UNION ALL
SELECT 'recive_date_starts_13x', COUNT(*) FROM cashes WHERE deleted_at IS NULL AND recive_date LIKE '13%'
UNION ALL
SELECT 'recive_date_starts_14x', COUNT(*) FROM cashes WHERE deleted_at IS NULL AND recive_date LIKE '14%'
UNION ALL
SELECT 'recive_date_starts_20x', COUNT(*) FROM cashes WHERE deleted_at IS NULL AND recive_date LIKE '20%'
UNION ALL
SELECT 'proceed_at_set', COUNT(*) FROM cashes WHERE deleted_at IS NULL AND proceed_at IS NOT NULL AND proceed_at <> '';
-- ستون‌های داده‌ی غیرقابل‌بازسازی (§۵.۲ گروه ج) — چند ردیف واقعاً مقدار دارند؟
SELECT 'has_birth_date' AS metric, COUNT(*) AS n FROM customers WHERE deleted_at IS NULL AND birth_date IS NOT NULL
UNION ALL SELECT 'has_marriage_date', COUNT(*) FROM customers WHERE deleted_at IS NULL AND marriage_date IS NOT NULL
UNION ALL SELECT 'has_token_mobile', COUNT(*) FROM customers WHERE deleted_at IS NULL AND token_mobile IS NOT NULL AND token_mobile <> ''
UNION ALL SELECT 'has_new_city', COUNT(*) FROM customers WHERE deleted_at IS NULL AND new_city IS NOT NULL AND new_city <> '';
-- فرمت رمز عبور: bcrypt با $2y$ شروع می‌شود. اگر نه، hash دیگری است
SELECT 'pw_bcrypt' AS metric, SUM(password LIKE '$2%') AS n FROM customers WHERE deleted_at IS NULL
UNION ALL SELECT 'pw_other', SUM(password IS NOT NULL AND password <> '' AND password NOT LIKE '$2%') FROM customers WHERE deleted_at IS NULL
UNION ALL SELECT 'pw_empty', SUM(password IS NULL OR password = '') FROM customers WHERE deleted_at IS NULL;
-- ---------------------------------------------------------------------------
-- بخش ۸ — جداول پشتیبان: آیا ردیفی دارند که `customers` ندارد؟
-- مرجع: §۱.۱ — قبل از نادیده‌گرفتن باید شمرده شوند
-- ---------------------------------------------------------------------------
SELECT '=== 8. BACKUP TABLES (safe to ignore?) ===' AS section;
SELECT 'customers' AS tbl, COUNT(*) AS n, CAST(MAX(id) AS CHAR) AS max_id FROM customers
UNION ALL SELECT 'customers_copy1', COUNT(*), CAST(MAX(id) AS CHAR) FROM customers_copy1
UNION ALL SELECT 'customers_copy2', COUNT(*), CAST(MAX(id) AS CHAR) FROM customers_copy2
UNION ALL SELECT 'customers_test', COUNT(*), CAST(MAX(id) AS CHAR) FROM customers_test;
-- شناسه‌هایی که در کپی هستند ولی در جدول اصلی نیستند → یعنی ردیف حذف شده
SELECT 'ids_only_in_copy1' AS metric, COUNT(*) AS n
FROM customers_copy1 k LEFT JOIN customers c ON c.id = k.id WHERE c.id IS NULL
UNION ALL
SELECT 'ids_only_in_copy2', COUNT(*)
FROM customers_copy2 k LEFT JOIN customers c ON c.id = k.id WHERE c.id IS NULL;
-- ---------------------------------------------------------------------------
-- بخش ۹ — تک‌فروشی (به تأخیر افتاده، §۷.۲) — فقط حجم، برای برنامه‌ریزی آینده
-- ---------------------------------------------------------------------------
SELECT '=== 9. RETAIL (deferred — size only) ===' AS section;
SELECT 'orders_orders' AS tbl, COUNT(*) AS live, SUM(deleted_at IS NOT NULL) AS soft_deleted FROM orders_orders
UNION ALL SELECT 'orders_orderitem', COUNT(*), SUM(deleted_at IS NOT NULL) FROM orders_orderitem
UNION ALL SELECT 'login', COUNT(*), SUM(deleted_at IS NOT NULL) FROM login;
-- 🔴 sts=7 در خرده «تحویل مشتری» است ولی در عمده «کنسل». چند ردیف درگیرند؟
SELECT 'retail_sts' AS col, CAST(sts AS CHAR) AS val, COUNT(*) AS n
FROM orders_orders WHERE deleted_at IS NULL GROUP BY sts ORDER BY sts;
SELECT '=== END ===' AS section;