# WP-A — ورقة الجرد على مستوى المتغيّر

## Goal
`InventoryCountController::productsForWarehouse` بيحمّل ورقة الجرد لمخزن. حاليًا حبيبته **المنتج الأساسي فقط**: الـleftJoin على `inventory_stock_balances` فيه `->whereNull('sb.product_variant_id')`، فمنتج له ألوان/مقاسات بيطلع **صف واحد برصيد صفر** والمتغيّرات مش ظاهرة أصلًا — وإنهاء الجرد بيكتب تسوية غلط. المطلوب: الحبيبة تبقى **(منتج × متغيّر)**.

## Exact files
- `/home/moonui2/moon-erp-be/Modules/Inventory/app/Http/Controllers/InventoryCountController.php` — الدالة `productsForWarehouse` (تبدأ ~سطر 115). الـleftJoin ~سطر 128-133، الـselect ~134-146، الكومنت التوثيقي ~95-113.

## المطلوب بالضبط
- المنتج اللي **له** متغيّرات (`product_variants` فيها صفوف لـ`product_id` ده) → **صف لكل متغيّر**، كل صف يحمل `product_variant_id` + **اسم المتغيّر** + رصيده الحقيقي من `inventory_stock_balances` للمخزن ده.
- المنتج اللي **مالوش** متغيّرات → **صف واحد** بـ`product_variant_id = null` — **byte-for-byte زي دلوقتي**.
- **كل المتغيّرات تظهر حتى اللي رصيدها صفر** (قرار مثبّت من العميل) — نفس فلسفة الورقة الحالية اللي بتعرض المنتجات اللي مالهاش رصيد.
- الـjoin على الرصيد لازم يطابق المتغيّر صح **بما فيه حالة NULL** (لاحظ: Laravel بيحوّل `where(col, null)` لـ`whereNull` تلقائيًا، بس هنا الـjoin على أعمدة فاستخدم شرط صريح NULL-safe — مثلاً `whereColumn` + معالجة الـnull، أو `on` مع `whereNull` في الفرع المناسب).
- حدّث الكومنت التوثيقي (~101-103) اللي بيقول «الحبيبة هي المنتج الأساسي… ولأن هذه النسخة لا تستخدم متغيّرات… لو اتضاف مخزون متغيّرات، المحمّل ده محتاج حبيبة لكل متغيّر» — بقى منفَّذ.
- حافظ على: فلاتر `search`/`category_id`، الترتيب، الـpagination والـcap (per_page 2000/5000)، وتحقّق ملكية المخزن للشركة (abort 404).

## Interfaces (يستهلكها WP-D)
كل صف في الرد لازم يحتوي — بالإضافة للموجود (`product_id`, `product_name`, `product_code`, `product_sku`, `unit_id`, `product_category_id`, `before_quantity`, `average_cost`) — على:
- `product_variant_id` : int|null
- `product_variant_name` : string|null  (اسم المتغيّر المعروض؛ استخدم `getLocalizedName()` لو موجود على الموديل)

## Acceptance
- [ ] منتج بمتغيّرين برصيدين مختلفين في المخزن → الورقة ترجّع **صفّين**، كل واحد بـ`before_quantity` الصحيح بتاعه.
- [ ] متغيّر رصيده صفر **بيظهر** بصف `before_quantity = 0`.
- [ ] منتج بلا متغيّرات → صف واحد `product_variant_id = null` وبنفس القيم بالظبط زي قبل التغيير.
- [ ] الورقة متوافقة مع مفتاح البحث في `store()`/`finalize()` (اللي بيدوّروا بـproduct + variant) — **ماتعدّلش المسارات دي**.

## Tests
اكتب/وسّع اختبار Pest في `Modules/Inventory/tests/Feature/` (مثلاً `CountSheetVariantGrainTest.php`):
- منتج بمتغيّرين + أرصدة مختلفة → صفّين بالقيم الصح.
- متغيّر بلا رصيد → موجود بصفر.
- منتج بلا متغيّرات → صف واحد variant=null (regression).
⚠️ **جدول `product_variants` فاضي على dev** — اعمل بيانات المتغيّرات جوّه الاختبار نفسه (factories/insert)، ماتعتمدش على dev.
شغّل: `/opt/cpanel/ea-php82/root/usr/bin/php -d memory_limit=1G vendor/bin/pest Modules/Inventory/tests`

## Flags
NOT [FIN] · **migration: لا** (ممنوع أي migration).

## Out of scope
مسارات `store()`/`finalize()`/`FinalizeCount` · أي شاشة تانية · الواجهة (WP-D) · أي تقرير آخر.
