# رَنبوك تنفيذ إصلاح أسعار الليرة على الإنتاج

**الفرع:** `fix/syp-priced-orders-2026-09-07` · **الكوميت:** `78243b00`
**الـ migration:** `2026_09_07_000001_fix_syp_priced_orders`
**المدة المتوقعة:** أقل من ثانية للتنفيذ · ~30 دقيقة للرَنبوك كاملاً
**وضع الصيانة:** غير مطلوب

---

## 🔴 تحذيران قبل أي شيء

### 1. لا تشغّل `php artisan migrate` مجرّدة على الإنتاج — أبداً

في هذا المشروع **8 migrations معلّقة**، بعضها DDL من مورّد الـ CMS على جداول حيّة:

```
2019_12_14_000001_create_personal_access_tokens_table
2021_06_07_000000_create_payku_transactions_table
2021_06_07_000001_create_payku_payments_table
2021_12_15_000000_add_new_columns_to_tables      ← DDL على جداول قائمة
2022_06_29_075906_create_product_queries_table
2026_09_04_000001_seed_seller_pwa_translations
2026_09_04_000002_create_seller_push_tokens_table
2026_09_04_000003_seed_seller_push_translations
```

`php artisan migrate` بتشغّلهن **كلهن دفعة واحدة**. لذلك كل أمر في هذا الرَنبوك يستعمل `--path` ليشتغل ملف واحد فقط لا غير.

### 2. الكوميت `78243b00` يحتوي تعديلات غير متعلقة

الكوميت ما فيه الإصلاح وحده — فيه كمان:

| الملف | التعديل |
|---|---|
| `app/Exports/OrdersExport.php` | فلترة التصدير بـ `id`، `whereIn` لحالة التسليم، والفلترة بعمود `date` بدل `created_at` |
| `app/Http/Controllers/OrderController.php` | تصحيح `use App\Models\OrdersExport` ← `App\Exports\OrdersExport`، والفلترة بـ `date` |

**قرار مطلوب منك:** إذا كنت جاهزاً لنشر هذين الملفين مع الإصلاح، كمّل عادي. إذا لأ، افصل الـ migration في كوميت لحاله قبل النشر:

```bash
git checkout -b deploy/syp-fix-only dev
git checkout 78243b00 -- database/migrations/2026_09_07_000001_fix_syp_priced_orders.php \
                          database/scripts/ .gitignore
git commit -m "fix(pricing): correct legacy SYP-priced orders"
```

---

## المرحلة 0 — تحقق للقراءة فقط (لا كتابة إطلاقاً)

```bash
mysql -u USER -p DBNAME < database/scripts/phase0_verify_syp_priced_orders.sql \
  > phase0_output_$(date +%Y%m%d_%H%M).txt
```

### بوابات القرار — توقّف إذا فشلت أي واحدة

| # | الفحص | المتوقع | إذا فشل |
|---|---|---|---|
| 1 | آخر طلب في القسم 1 | تاريخه **قبل** 2026-01-23 | ⛔ توقف — نافذة التلوث مختلفة عن المحلي |
| 2 | `implied_rate` في القسمين 2 و 3 | كلها بين **11,400 و 11,600** | ⛔ توقف — سعر الصرف مختلف |
| 3 | القسم 6، أول رقم | **صفر** | ⛔ توقف — التلوث امتد لما بعد نقطة القطع |
| 4 | القسم 6، الجدول الثاني | مبالغ 1000–5000 كلها منتجات غالية حقيقية (آيفون/أجهزة) | ⛔ راجعها بالعين قبل المتابعة |
| 5 | القسم 8 | `commission_histories` = **0** | ⚠️ لو أكبر من صفر، عمولات البائعين ستحتاج إعادة حساب |
| 6 | القسم 8، آخر أمرين | `refund_requests` و `club_points` غير موجودين | ⚠️ لو موجودين، افحص `order_id` فيهما يدوياً |

**احتفظ بمخرجات القسم 7 (خط الأساس الشهري)** — رح تقارن فيها بعد التنفيذ.

---

## المرحلة 1 — النسخ الاحتياطي

```bash
mysqldump -u USER -p --single-transaction --quick \
  DBNAME orders order_details combined_orders \
  > ~/backup_syp_fix_$(date +%Y%m%d_%H%M).sql

ls -lh ~/backup_syp_fix_*.sql          # تأكد أن الحجم منطقي وليس صفراً
```

`--single-transaction` يعني **لا قفل على الجداول** — الموقع يشتغل عادي أثناء النسخ.

> ⚠️ لا تعتمد على النسخة الاحتياطية وحدها. جدول `price_fix_backup_2026_01` اللي بينشئه الـ migration هو آلية التراجع الأساسية؛ الـ dump شبكة أمان ثانية.

---

## المرحلة 2 — بروفة على نسخة من بيانات الإنتاج (موصى بها بشدة)

هذه أقوى خطوة أمان في الرَنبوك كله: نفّذ على نسخة أولاً.

```bash
mysql -u root -p -e "CREATE DATABASE kap_rehearsal CHARACTER SET utf8mb4;"
mysqldump -u USER -p --single-transaction DBNAME | mysql -u root -p kap_rehearsal

# وجّه .env مؤقتاً على kap_rehearsal، ثم:
php artisan migrate --path=database/migrations/2026_09_07_000001_fix_syp_priced_orders.php --force
```

تحقق من النتيجة، ثم جرّب التراجع، ثم أعد `.env` لقاعدة الإنتاج واحذف `kap_rehearsal`.

**لا تستعمل `--pretend`** — الـ migration يقرأ الصفوف ليقرر ماذا يصحّح، و`--pretend` ما بينفّذ القراءات فبتطلع نتيجة مضلّلة.

---

## المرحلة 3 — التنفيذ

**التوقيت:** ساعة هدوء (بعد منتصف الليل). العملية أقل من ثانية على ~35 صف.

```bash
# 1) نزّل الكود على السيرفر
git fetch origin
git checkout fix/syp-priced-orders-2026-09-07
git pull origin fix/syp-priced-orders-2026-09-07

# 2) تأكد أن الملف وصل
ls -l database/migrations/2026_09_07_000001_fix_syp_priced_orders.php

# 3) تأكد أنه معلّق ولم يُشغَّل من قبل
php artisan migrate:status | grep syp_priced_orders

# 4) نفّذ — لاحظ --path، إلزامية
php artisan migrate \
  --path=database/migrations/2026_09_07_000001_fix_syp_priced_orders.php \
  --force

# 5) امسح الكاش — إلزامي، بدونه تبقى الأرقام القديمة ظاهرة
php artisan cache:clear
```

### ماذا يفعل الـ migration بالضبط

1. ينشئ `price_fix_backup_2026_01` (جدول جديد، لا يمسّ شيئاً قائماً)
2. يرفض العمل إذا كان الجدول يحوي صفوفاً غير مسترجَعة — حماية من التشغيل مرتين
3. يحدّد الصفوف **بالقيمة لا بالمعرّف** — فيشتغل صح حتى لو اختلفت معرّفات الإنتاج
4. يفتح transaction: يؤرشف كل قيمة قديمة ثم يقسمها على 11,500
5. يتحقق قبل الـ commit؛ وإذا فشل التحقق يرمي استثناء فيتراجع كل شيء تلقائياً

**الفشل آمن:** أي خطأ داخل الـ transaction يعني أنه لم تُكتب ولا قيمة واحدة. أعد التشغيل بعد معالجة السبب.

---

## المرحلة 4 — التحقق بعد التنفيذ

```bash
mysql -u USER -p DBNAME <<'SQL'
-- أ) قارن مع خط الأساس: كل شهر من 2026-02 يجب أن يبقى مطابقاً حرفياً
SELECT DATE_FORMAT(created_at,'%Y-%m') m, COUNT(*) c, ROUND(SUM(grand_total),2) s
FROM orders GROUP BY m ORDER BY m;

-- ب) يجب أن يكون صفراً
SELECT COUNT(*) still_syp FROM orders
WHERE created_at < '2026-02-01' AND grand_total >= 1000;

-- ج) عدد الصفوف المؤرشفة، وكلها غير مسترجَعة
SELECT table_name, column_name, COUNT(*) FROM price_fix_backup_2026_01
WHERE restored_at IS NULL GROUP BY table_name, column_name;

-- د) الطلبات المجمّعة يجب أن تطابق مجموع طلباتها
SELECT co.id, co.grand_total, ROUND(SUM(o.grand_total),2) orders_sum
FROM combined_orders co JOIN orders o ON o.combined_order_id = co.id
WHERE co.created_at < '2026-02-01'
GROUP BY co.id HAVING ABS(co.grand_total - orders_sum) > 0.05;
SQL
```

### المتوقع (من البروفة المحلية)

| الفحص | المتوقع |
|---|---|
| أ — كانون الثاني 2026 | من 1,137,964.08 $ إلى **192.03 $** |
| أ — كانون الأول 2025 | 18.35 $ (بلا تغيير) |
| أ — شباط فما بعد | **مطابقة حرفياً لخط الأساس** ← الأهم |
| أ — عدد الطلبات في كل شهر | بلا تغيير |
| ب | **0** |
| ج | ~13 combined_orders، ~20 order_details.price، ~12 shipping_cost، 8 orders.grand_total، 1 coupon_discount |
| د | **لا صفوف** (أو صف واحد بفرق 0.01 تقريب) |

### فحص بصري

- افتح صفحة طلب من المتأثرين (كود `20260112-15445369`) — الأرقام منطقية؟
- افتح صفحة طلب من شباط 2026 — بلا تغيير؟
- افتح تقارير الأدمن (المبيعات، المنتجات، البائعين) — تفتح بلا أخطاء؟
- افتح صفحة طلب من تشرين الثاني 2025 (كود `20251120-14121394`) — سطر «الطلب المجمّع كاملاً» صار 2.44 $ بدل 28,060

---

## المرحلة 5 — التراجع

إذا ظهر أي خلل:

```bash
php artisan migrate:rollback \
  --path=database/migrations/2026_09_07_000001_fix_syp_priced_orders.php \
  --force

php artisan cache:clear
```

يقرأ `price_fix_backup_2026_01` ويعيد كل قيمة إلى ما كانت عليه **حرفياً**، ثم يختمها بـ `restored_at`. تحققتُ من هذا محلياً: `diff` على الجداول الثلاثة قبل وبعد دورة إصلاح ← تراجع طلعت **فارغة تماماً**.

الجدول يبقى بعد التراجع عمداً، فيبقى سجل ما جرى.

**التراجع اليدوي** (لو تعطّل artisan) — **جملة منفصلة لكل عمود**:

> ⚠️ لا تدمج عدة أعمدة في `UPDATE` واحد بـ `IF()`. الصف الواحد قد يكون له أكثر من عمود مؤرشف
> (الطلب 113 مثلاً له `grand_total` و`coupon_discount`)، و MySQL تطبّق `UPDATE ... JOIN` مرة
> واحدة فقط على الصف الهدف فيضيع العمود الثاني وتبقى البيانات نصف مسترجَعة.

```sql
UPDATE orders o JOIN price_fix_backup_2026_01 b
  ON b.table_name='orders' AND b.column_name='grand_total'
 AND b.row_id=o.id AND b.restored_at IS NULL
SET o.grand_total = b.old_value;

UPDATE orders o JOIN price_fix_backup_2026_01 b
  ON b.table_name='orders' AND b.column_name='coupon_discount'
 AND b.row_id=o.id AND b.restored_at IS NULL
SET o.coupon_discount = b.old_value;

UPDATE order_details d JOIN price_fix_backup_2026_01 b
  ON b.table_name='order_details' AND b.column_name='price'
 AND b.row_id=d.id AND b.restored_at IS NULL
SET d.price = b.old_value;

UPDATE order_details d JOIN price_fix_backup_2026_01 b
  ON b.table_name='order_details' AND b.column_name='tax'
 AND b.row_id=d.id AND b.restored_at IS NULL
SET d.tax = b.old_value;

UPDATE order_details d JOIN price_fix_backup_2026_01 b
  ON b.table_name='order_details' AND b.column_name='shipping_cost'
 AND b.row_id=d.id AND b.restored_at IS NULL
SET d.shipping_cost = b.old_value;

UPDATE combined_orders c JOIN price_fix_backup_2026_01 b
  ON b.table_name='combined_orders' AND b.column_name='grand_total'
 AND b.row_id=c.id AND b.restored_at IS NULL
SET c.grand_total = b.old_value;

UPDATE price_fix_backup_2026_01 SET restored_at = NOW() WHERE restored_at IS NULL;
```

---

## المرحلة 6 — بعد التنفيذ

- احتفظ بـ `price_fix_backup_2026_01` والـ dump **شهراً على الأقل**
- ادمج الفرع في `dev` ثم `main` حسب سير العمل المعتاد
- الـ ~99 صف `combined_orders` اليتيمة (سلال متروكة، تموز 2025 → آذار 2026) **لم تُمسّ عمداً** — مخفية عن كل تقرير، وعهدها أقدم من نافذة سعر 11,500 المتحقَّق منها

---

## ما لا يفعله هذا الرَنبوك

- ❌ لا `migrate:fresh` ولا `db:wipe` ولا seeding
- ❌ لا `php artisan migrate` بدون `--path`
- ❌ لا تعديل على `currencies.exchange_rate` (سعر 135 الحالي صحيح)
- ❌ لا تعديل على أي طلب بتاريخ 2026-01-23 أو بعده
- ❌ لا تعديل على `products` / `product_stocks` — أسعارها سليمة
- ❌ لا تعديل على منطق التسعير في `Helpers.php` — الكود صحيح، المشكلة بيانات قديمة فقط
