# LEDGER — وسم الشغلانة على إذن الصرف (ISS-2026-9296)

**الفيتشر:** أمين المخزن يربط إذن الصرف بـ**وسم شغلانة** (يختار موجود أو يضيف جديد من نفس الشاشة)، ويطلع **تقرير بالوسوم**: كل وسم اتصرف عليه إيه وكام مرة.
**التاريخ:** 2026-07-27 · **الفرع:** hazemdev2 · **الريبو:** BE + FE · **الأم:** ISS-2026-9260

**السياق الحاكم:** العميل هيشغّل **المخازن من غير موديول الإنتاج**. الهدف **مراقبة مش تكلفة** — «صرف 5 مرات في اليوم على نفس الشغلانة يعرف يراقب هو بيعمل إيه». الوسم **بديل مؤقت** لأمر الإنتاج لحد ما الإنتاج يشتغل.

**اختبار قبول المالك:** (1) إذن صرف بنوع «وسم شغلانة» يتحفظ ويتعتمد، المخزون ينقص، و**صفر حركة على حساب تحت التشغيل**. (2) أمين المخزن بصلاحية الإنشاء يضيف وسمًا من شاشة الصرف من غير ما يسيبها؛ واللي مامعهوش يختار من الموجود بس. (3) اسم متقارب ⇒ تحذير. (4) 5 أذون على نفس الوسم ⇒ التقرير يوريهم 5 مرات بالأصناف والكميات. (5) أنواع المرجع الحالية وسلوك الشاشة **مايتأثروش**. (6) مفيش أي أثر لزرار «الأمر المؤقت» في الواجهة ولا مسار في الباك إند.

## القرارات المجمّدة (العميل أخذ بالتوصيات الخمسة — خطوة 6512)
1. الوسم **على مستوى الإذن كله** (مش السطر).
2. إضافة وسم جديد تحت **صلاحية منفصلة**؛ الباقي يختار من الموجود.
3. التقرير: **كميات وأصناف أساسي**، والقيمة **عمود اختياري** يتعرض/يتخفي.
4. الوسوم لما الإنتاج يشتغل: **أرشيف بس** — مفيش ربط لاحق بأوامر حقيقية.
5. زرار «أمر إنتاج مؤقت» بتاع 9260: **يتشال خالص** (واجهة + باك إند).

## الحقيقة الحاكمة اللي بترخّص التصميم (متحقَّق منها)
- إذن الصرف عنده **`reference_type` + `reference_id`** أصلًا؛ الأنواع في `Modules/Inventory/app/Enums/IssueReferenceType.php` (sale · consumption · damage · adjustment · production_order · other) والتسميات في `Modules/Inventory/lang/{ar,en}/inventory.php` تحت `issue_reference_types` (ar :104-111).
- **قيد «تحت التشغيل» بيتفعّل بشرط واحد حرفيًا:** `ApplyMaterialIssueOnApproval.php:54` → `if ($issue->reference_type !== IssueReferenceType::ProductionOrder) return;` ⇒ **أي نوع مرجع تاني مابيمرّش من هناك أصلًا** ⇒ الوسم **مايقدرش** يلمس الحسابات. ← ده أهم ضمان في الفيتشر، ولازم **اختبار صريح** يثبّته.
- `referenceIdRules()` في `Store|UpdateInventoryIssueRequest` بتشرّط `required + exists` **لنوع أمر الإنتاج بس** (:107-109)، والباقي `['nullable','integer']` ⇒ الوسم محتاج فرعه الخاص بنفس النمط.
- `InventoryIssueResource::referenceNumber()` (:62-68) بيحلّ رقم المستند **لنوع أمر الإنتاج بس** ⇒ يتوسّع ليحلّ اسم الوسم.
- نمط الصلاحيات: `inventory.issues.view|create|update|delete` في `Modules/Core/database/seeders/RolePermissionSeeder.php`.
- **منتقي الأوامر وحوار «إضافة جديد» في شاشة أذون الصرف (اتعملوا في 9260) هما نفس النمط المطلوب للوسم** ⇒ إعادة استخدام مش كتابة من الصفر.

## جدول الـWPs
| WP | النطاق | الريبو | يعتمد | مراجعة | Migration | الحالة | Commits |
|----|--------|--------|-------|--------|-----------|--------|---------|
| WP1 | جدول الوسوم + الموديل + CRUD + الصلاحيات (`inventory.issue-tags.*`) + الإقفال/الأرشفة + بحث الأسماء المتقاربة | BE | — | opus | **نعم — اتشغّلت على dev** | ✅ **تم** — 23 اختبار | Inventory **564/1** · غير ملتزم |
| WP2 | نوع مرجع `job_tag` + ربطه بإذن الصرف (تحقّق + ريسورس + اسم الوسم) + **اختبار صفر أثر محاسبي** | BE | WP1 | opus | لا | ✅ **تم** — 25 اختبار | Inventory **589/1** · Production **625/10** |
| WP3 | تقرير «المنصرف حسب الوسم» (تجميع + تفصيل + فلتر فترة + القيمة اختيارية) | BE | WP2 | opus | لا | ✅ **تم** — 26 اختبار | Inventory **615/1** · غير ملتزم |
| WP4 | **إزالة** «الأمر المؤقت» (واجهة + باك إند + اختباراته + العلامة اللي بقت ميتة) | BE+FE | — | opus | لا | ✅ **تم** | Production **616/10** (625 − 9 محذوفة) · بناء أخضر |
| WP5 | الواجهة: نوع «وسم شغلانة» في شاشة أذون الصرف + منتقي + حوار إضافة + تحذير التقارب | FE | عقد WP1/WP2 | opus | لا | ✅ **تم** — بناء أخضر | `I18N_VERSION=20260725c` · غير ملتزم |
| WP6 | الواجهة: شاشة إدارة الوسوم + **تبويب** التقرير في مركز تقارير المخزون | FE | عقد WP1/WP3 | opus | لا | ✅ **تم** — بناء أخضر | `I18N_VERSION=20260725e` · منشور `main-CMHJRIN2.js` |

## DAG / التسلسل
WP1 → WP2 → WP3 بالتسلسل (نفس الريبو ومتسلسلة منطقيًا). WP4 نصّه BE ⇒ يتنفّذ في نافذة BE منفصلة. WP5 و WP6 على FE ⇒ بالتسلسل بينهم، وبيبدأوا بعد ما عقود WP1/WP2/WP3 تثبت.
**قاعدة: كاتب واحد لكل ريبو في أي لحظة.**

## خط الأساس (Baseline)
- **Production:** 625 نجاح / **10 فشل سابقة** (6 `ProductionVarianceTest` · 3 `CostAiAnomalyVarianceTest` · 1 `ConsignmentFoundationTest`).
- **Inventory:** 541 نجاح / **1 فشل سابق** (`OpeningBalanceApiTest > opening balance creates inventory movement with type opening`).
- FE `ng build` أخضر · الحزمة المنشورة قبل البدء: `main-I2VM4Q7K.js`.
- ⚠️ تشغيل الاختبارات **لازم** بـCLI php صراحةً: `/opt/cpanel/ea-php82/root/usr/bin/php -d memory_limit=1G vendor/bin/pest …` — الافتراضي php-cgi وبيقتل الرَنَر بصمت (exit 255 بلا مخرجات).

## الانحرافات والمؤجّلات
- **قرار مرصود (WP4):** عند إزالة «الأمر المؤقت»، **`order_type` في الريسورس وفلتر الـindex يفضلوا** — دول عامّان ومفيدان لـrnd/pilot/standard بغضّ النظر عن الوسم. اللي يتشال هو الخاص بـ`stock_issue` بس (الثابت + `isFromStockIssue()` + خيار الفلتر + البادج).
- **الوسم على مستوى السطر** — خارج النطاق، لاحقًا لو احتاجه.


---

### WP1 — التنفيذ (2026-07-27) — ✅ **تم**

**الجدول:** `inventory_issue_tags` — `company_id` (FK cascade, NOT NULL) · `name` · `name_ar` · `description` · `status` (افتراضي `active`) · `created_by` (nullOnDelete) · timestamps + softDeletes. فهارس: `(company_id, status)` + الفريد.

**⚠️ اقتراحي في البريف كان غلط والمنفّذ صحّحه.** أنا اقترحت فريدًا على `(company_id, name, deleted_at)` كمخرج من تعارض الحذف الناعم. **ده مايضمنش حاجة أصلًا:** الـUNIQUE بيعامل NULL كقيمة متمايزة في MySQL وsqlite، و`deleted_at = NULL` على كل الصفوف الحيّة ⇒ الفهرس كان هيسمح بتكرار غير محدود للأسماء الحيّة. المنفّذ استخدم **`unique(company_id, name)`** الحقيقي (نمط `partner_categories`/`warehouses` في نفس الموديول) وحلّ الحذف الناعم **في التطبيق**: إنشاء اسم بتاع وسم محذوف ناعمًا **بيحيي الصف** بدل ما يصطدم — من غير كده حذف وسم كان بيحرق اسمه للأبد. وقاعدة `unique` في طلب الإنشاء مقيّدة بالصفوف الحيّة عشان مسار الإحياء يفضل قابل للوصول، وفي طلب التعديل **متعمّد إنها مش كده** لأن إعادة تسمية على اسم محذوف مش هتحيي حاجة وكانت هتطلع 500 بدل 422.

**الصلاحيات:** `inventory.issue-tags.view|create|update|delete` بعد كتلة `inventory.issues.*`. **وأضاف `backfillIssueTagCapability()`** في `runAdditive()` (على نمط `backfillManualReceiptCreateCapability`) — قرار صح: الصلاحيات الجديدة بتوصل للأدوار القياسية بس، وأمين المخزن الحقيقي قاعد في دور **مخصّص**. الـbackfill بيدّي `.view/.create/.update` لكل دور معاه `inventory.issues.create` أصلًا، وبيعمل `.delete` من غير ما يمنحه. واتأكّد على dev إنه لقط دورين مخصّصين حقيقيين (`warehouse_storekeeper` و`production_materials`) كانوا هياخدوا **صفر** من غيره.

**بحث التقارب** (`GET issue-tags/similar`، معلَن **فوق** الـapiResource عشان `similar` ماتتلقطش كـ`{issue_tag}`): التطبيع كله في PHP (بيشتغل على sqlite في الاختبار وMySQL في الإنتاج) — توحيد حالة الأحرف → شيل التشكيل والتطويل → أرقام عربية/فارسية لـASCII → ترقيم لمسافات → **شيل «ال» من أول كل كلمة** → طيّ `أإآٱ→ا`، `ة→ه`، `ى→ي`، `ؤ→و`، `ئ→ي`.
⚠️ **ترتيب مقصود:** شيل «ال» **قبل** طيّ الهمزات عشان «إلغاء» ماتتقريش كأنها «الـغاء».
التسجيل: مطابقة تامة 1.0 · احتواء (≥3 حروف) 0.85 · وإلا `1 − levenshtein/maxlen` **على نقاط UTF-8** (دالة PHP الأصلية بايتية و**بتكذب على العربي**). العتبة 0.75، أقرب 5، بيقارن `name` و`name_ar`. متحقَّق حيًّا: «صيانة» و«صيانه» و«الصيانة» و«الصِّيانةِ» و«صيــانة» كلهم ⇒ 1.00 · «صيانة الماكينة» ⇒ 0.85 · «تنظيف المكتب» ⇒ 0.00.
**بيحذّر بس مايمنعش** — والمنع الوحيد هو المطابقة التامة عبر الفهرس، وفيه اختبار بيثبت إن الاسم المتقارب لسه بيتعمل.

**مفاجأة تستاهل التسجيل:** دالة ستاتيك اسمها `similarity()` بيلقطها Eloquent كـattribute mutator وبتفجّر ⇒ اتسمّت `similarityScore()`.

**قرارات:** الفلتر الافتراضي للقائمة `status=active` (fail-safe — منتقي نسي الفلتر عمره ما يعرض شغلانة مقفولة) · الترتيب بالاسم افتراضيًا · `similar` باسم فاضي بترجّع فاضي مش 422.

**23 اختبار** · **Inventory 564 / 1** (الأساس 541/1 = 542 ⇒ 542 + 23 = 565 ✓) — **صفر فشل جديد**. و`pint --test` على الـ9 ملفات الملموسة بس = pass (**مفيش أي تلويث للملفات التانية**). الـmigration اتطبّقت على `moonui2_dev_be` واتأكّد الفهرس في `SHOW INDEX`.

**متروك لي:** بند CHANGELOG — WP1 لوحده مالوش سطح مرئي، فالبند بيتكتب مع WP2/WP3 لما القدرة تبقى قابلة للاستخدام.
**مؤجَّل:** مفاتيح الوسوم مااتضافتش لـ`PermissionCatalog.php` — الكتالوج ده بيغطّي Core/Accounting/Sales/Purchases بس ومفيش أي `inventory.*` فيه غير `settings`، فإضافة الوسوم لوحدها كانت هتبقى غير متسقة.


---

### WP2 — التنفيذ (2026-07-27) — ✅ **تم**

**النوع:** `IssueReferenceType::JobTag = 'job_tag'` **بعد `ProductionOrder` وقبل `Other`** (المنتقي بيرندر بترتيب الـcases و«أخرى» لازم تفضل الأخيرة). التسميات: «وسم شغلانة» / "Job tag".

**التحقّق:** فرع `job_tag` **اتحطّ قبل** الكود الموجود فمفيش سطر قديم اتغيّر — `required|integer` + `exists` على `inventory_issue_tags` مقيّد بـ**الشركة** و**`status = active`** و`deleted_at IS NULL`. ⚠️ ملاحظة دقيقة: `whereNull('deleted_at')` هنا **حيّة فعلًا** (الموديل بيستعمل SoftDeletes والكونترولر بيحذف ناعمًا) — بعكس نظيرتها في فرع أمر الإنتاج اللي خاملة.
**دليل عدم الانحدار (تلات طبقات):** (أ) الفرع القديم والـfallthrough **مطابقان بايت-ببايت** واتأكّد بإعادة طباعة الدالتين قبل وبعد؛ (ب) **10 اختبارات معلَّمة** (`sale|consumption|damage|adjustment|other` × store/update) بتؤكّد إن `reference_id` = null لسه بيتحفظ؛ (ج) الـsuite كلها على خط الأساس.

**⭐ فخّ الوسم المقفول — البريف كان محقًّا و`has()` وحدها ماكانتش كفاية.** مميّز 9260 هو `$touchesLink = has('reference_type') || has('reference_id')`. ده سليم لأوامر الإنتاج (عمرها ما بتتقاعد) و**غلط للوسوم**: الإقفال شغل روتيني، وشاشة أذون الصرف بتبعت **المستند كله** في الـPUT فـ`reference_id` موجود حتى في تعديل ملاحظات. الحل: `jobTagReferenceIdRules()` بتطبّق القاعدة الصارمة **بس لما الرابط الناتج يختلف عن المحفوظ**. ⇒ تعديل ملاحظات على إذن بوسم مقفول **يعدّي** · إعادة إرسال نفس الـid **تعدّي** · التأشير على وسم مقفول/غريب/محذوف **422** · تحويل النوع من `consumption` لـ`job_tag` بلا id **422**.

**الريسورس بلا N+1:** أضاف علاقة `InventoryIssue::jobTag()` وحمّلها مسبقًا في `index()` ⇒ استعلام واحد للصفحة بدل واحد للصف. و`reference_id` polymorphic-by-convention فالعلاقة ماتقدرش تحمل نطاق الشركة ⇒ الريسورس بيتحقّق من **النوع + تطابق الشركة في الذاكرة** قبل العرض، والاستعلام المباشر للمستند الواحد مقيّد بالشركة في SQL.

**⭐ اختبار صفر أثر محاسبي — مبني كويس:** بينشئ ويعتمد إذنًا موسومًا وبيؤكّد بالترتيب: (1) المخزون 100 → **90** ← **ده اللي بيخلّي الباقي غير فارغ** (المستند فعلًا اترحّل)؛ (2) `MfgMaterialIssue::count() === 0`؛ (3) إعداد `production.wip_account_id` لسه **null** — و`ProductionAccountResolver::resolveWip()` **بينشئ الحساب كسولًا ويكتب الـid في الإعداد ده**، فبقاؤه null **دليل موجب** إن الترحيل عمره ما اشتغل؛ (4) مفيش حساب باسم Work In Progress / تحت التشغيل للشركة؛ (5) صفر `journal_entry_lines` على أي حساب زي ده.

**⭐ وعمل فحصًا طفريًّا للاختبارين الحاملين بدل ما يثق في تشغيل أخضر:** شيل `'jobTag'` من التحميل المسبق ⇒ اختبار الـN+1 وقع (5 استعلامات مقابل 1)؛ وخلّى `$keepsExistingLink = false` (سلوك `has()` الساذج) ⇒ اختبارَي الوسم المقفول وقعا بـ422. ورجّع الطفرتين.

**النتائج:** Inventory **589 / 1** (564 + 25 ✓) · Production **625 / 10** — صفر فشل جديد في الاتنين. `pint` على الـ9 ملفات الملموسة بس.

**قرارات وملاحظات:**
- **ملفان زيادة عن قائمة البريف:** `InventoryIssue.php` (العلاقة) و`InventoryIssueController.php` (التحميل المسبق) — البريف طلب «بلا N+1» وذكر الريسورس بس، لكن الاستعلام لكل صف في مجموعة مصفّحة **هو** N+1 فالإصلاح لازم يقعد حيث تتبنى المجموعة. مقبول.
- العلاقة المحمّلة مسبقًا **بتلزق وسمًا بصفوف مش `job_tag`** لو `reference_id` صادف id وسم — **عمره ما بيترندر** (محروس بالنوع والشركة) وموثّق في الـdocblock، لكنه مرصود لأي مراجع.
- تعليق Scribe لـ`reference_type` في `index()` **كان قديمًا أصلًا** (ناقص `production_order`) ودلوقتي ناقص `job_tag` كمان — اتساب عمدًا لتقليل الدِف.
- **تصحيح لفهمي:** `referenceIdRules()` في طلب التعديل **هي نفسها شغل غير ملتزم** من 9260 (مش كود متدمج من أسابيع زي ما البريف لمّح) ⇒ «اقراها كما هي» كانت أهم مما كتبت.
- **بند CHANGELOG لـ9296 لسه مكتوبش** — WP1 وWP2 الاتنين مالهمش سطح مرئي؛ البند بيتكتب لما الواجهة تنزل. **مسؤوليتي.**


---

### WP5 — التنفيذ (2026-07-27) — ✅ **تم**

**ملفان جديدان** (`issue-tag.model.ts` + `issue-tag.service.ts`) و**6 معدّلة**. البناء أخضر، خروج 0، و**صفر تحذير جديد** — أثبته باستخراج كل مسار `src/…` مذكور في أي تحذير (57 ملف) وإثبات إن **ولا واحد** من الـ8 ملفات بتاعته فيهم.

**المشترك مقابل المكرّر — قرار مشروح:** المشترك = كنترول `reference_id` الواحد · `onReferenceTypeChange` · `resetDialogState` · بناء حمولة `onSave` · وعرض `reference_number` في القائمة والتفاصيل **والطباعة** (اتأكّد إنه شغّال بلا أي كود — معيار القبول الرابع اتحقّق مجانًا). المكرّر = إشارات حالة المنتقي وخط البحث والتبويب والرسائل والـSCSS، **لأن المنتقيين متفقين في الشكل بس**: خدمة مختلفة، صلاحية مختلفة، قالب خيار مختلف، و**الوسم عنده بحث تقارب المنتقي التاني مايعرفوش**. وتجريد مشترك كان هيتطلّب تعديل منتقي الأوامر — وده ممنوع في الـWP ده.

**تحذير التقارب:** debounce 400ms، أقل من حرفين بيسكت، وفشل الطلب **بيسكت مايمنعش**. النتايج بتظهر في لوحة كهرمانية، و**قبول الاقتراح بضغطة واحدة** على الشريحة ⇒ بيحقن الوسم في خيارات المنتقي (الاقتراح مش لازم يكون في الصفحة المحمّلة)، ويقفل الحوار. **الحفظ عمره ما بيتعطّل.**
⚠️ **قرار جيد:** مسار `similar` **مش مفلتر بالحالة** ⇒ الاقتراح ممكن يكون وسمًا **مقفولًا**. دول بيظهروا شريحة رمادية غير قابلة للضغط بشارة «مقفول» — أمين المخزن يشوف إن الاسم متاخد، لكن مايقدرش يربط إذنًا جديدًا بيه (الباك إند بيرفضه بـ422 عمدًا).

**بلا تعطيل — بالدليل:** الـ`p-select` مالوش `[disabled]` ولا أي حالة تعطيل؛ بلا صلاحية العرض المنتقي **بيفضل مفعّلًا وفاضيًا** والرسالة بتقول السبب، فـ`reference_id` بيفضل في `form.value` وعلى السلك.

**الوسم المقفول عند التعديل:** الاستعادة **غير مشروطة**، وبيزرع خيارًا اصطناعيًا من `reference_number` **فورًا على الإشارة** عشان الجلب المتزامن مايقدرش يسقّطه ⇒ الوسم المقفول بيفضل مقروءًا ومختارًا، والـPUT بيبعت نفس الـid فالباك إند بياخد فرعه المتساهل ⇒ **قابل للحفظ**.

**⭐ حارس اكتشفه من نفسه:** `onReferenceTypeChange` بيمسح القيمة لما ننتقل **من** `job_tag` — لأن الوسم هو **تاني نوع حامل لـid**. من غير الحارس ده، التبديل من «وسم» لـ«أمر إنتاج» كان هيبعت **id وسم كأنه id أمر** والـvalidator راضي والمنتقي فاضي. (والفرع المقابل بيمسح عند الدخول عشان id أمر مايتوارثش.)

**عدم الانحدار — بالبنية مش بالادّعاء:** كل إضافة عبارة عن `if` منفصل **بعد** الموجود، والأنواع الخمسة التاريخية لسه مابتبعتش مفتاح `reference_id` أصلًا وفي نفس موضعها؛ والسطر الجديد في `onReferenceTypeChange` بيشتغل **بس لما النوع السابق `job_tag`** — وده مستحيل قبل الـWP ده ⇒ كل انتقال تاريخي بيمشي مساره الأصلي.

**قرار:** قائمة الوسوم بتتحمّل **كسولًا** (عند اختيار النوع أو الاستعادة) مش عند كل فتح حوار — الوسوم مالهاش مفتاح شركة، فالتحميل المتلهّف كان هيكلّف كل تنصيب طلبًا لكل حوار لفيتشر ممكن مايستخدموش أبدًا.

**19 مفتاح ترجمة** في الملفين · `I18N_VERSION` = `'20260725c'`.

**متحقَّق مني على قاعدة التطوير:** الصلاحيات الأربعة موجودة، وموزّعة على `owner` (الكل) و`admin`/`manager` (عرض) و**الأدوار المخصّصة `warehouse_storekeeper` و`production_materials` و`testrole`** (create/update/view) ⇒ الـbackfill بتاع WP1 اشتغل فعلًا والمنتقي مش هيقول «مفيش صلاحية».


---

### WP3 — التنفيذ (2026-07-27) — ✅ **تم** (التقرير = جوهر التذكرة)

**مساران:** `GET reports/issue-tags` (ملخّص) و`GET reports/issue-tags/{tag}` (تفصيل). الملخّص بيرجّع لكل وسم: الاسم المحلّي · الحالة · **`issues_count`** · عدد الأصناف · إجمالي الكمية · أول وآخر صرف · والقيمة **عند الصلاحية**؛ زائد `totals` و`can_view_value` و**`counted_statuses`**. التفصيل بيرجّع الأذون بسطورها.
⚠️ **إجماليات التفصيل دايمًا على الفترة كلها مش على الصفحة** — وفيه اختبار بيثبّت ده.

**⭐ قرار الحالات المحسوبة — `approved` + `partially_issued` بس.** دول بالظبط اللي `ApproveIssue` بيكتبهم **بعد** ما ينقّص المخزون، وبالظبط اللي `isCancellable()` بيسمّيهم «اتعالجوا خلاص». المستبعد: `draft` و`pending_approval` (الاتنين `canPost()` — يعني **لسه ما رحّلوش**، مفيش صف حركة أصلًا) و`cancelled` (`CancelIssue` بيرجّع المخزون على الرف). **والقرار منشور في الرد نفسه** (`counted_statuses`) فالقاعدة عمرها ما تبقى خفيّة.
والكمية بتقرا `COALESCE(issued_quantity, quantity)` — **نفس** الـCOALESCE اللي `recalculateTotals()` بيستعمله ⇒ السطر المصروف جزئيًا بيبلّغ **اللي اتحرّك فعلًا** مش اللي اتطلب.

**⭐ الصلاحية — أخت جديدة `inventory.reports.issue_tag_value` مش إعادة استخدام `production_issue_cost`.** السبب: (أ) الصلاحية القديمة **مسمّاة باسم الإنتاج** ومزروعة حواليه، والعميل بيشغّل **المخازن من غير إنتاج** ⇒ تبويب تقرير مخازن خلف صلاحية اسمها «إنتاج» **عقد كاذب** في محرّر الأدوار؛ (ب) الإفصاحان منفصلان فعلًا. والتكلفة (صلاحية زيادة) متعالَجة بـ**`backfillIssueTagValueCapability()`**: كل دور معاه `production_issue_cost` بياخد الجديدة ⇒ **محدش كان بيشوف فلوس التقارير بيخسر**، ومحدش بياخد زيادة. متحقَّق على dev: الاتنين مع نفس الأربع أدوار بالظبط.

**قرارات تانية:** الوسم اللي **مالوش أذون محسوبة في الفترة غايب** مش صف بصفر (التقرير بيجاوب «إيه اللي خرج من المخزن مجمّعًا بالشغلانة» — شغلانة بلا سحب مالهاش إجابة؛ وصفوف الصفر كانت هتخلّي `meta.total` بيعدّ وسومًا مش شغلانات نشطة). والترتيب الافتراضي **الأكثر صرفًا أولًا** — ده حرفيًا السؤال اللي المستخدم بيفتح الشاشة عشانه. و**`status` الافتراضي `all`** — **عكس** افتراضي المنتقي عمدًا: المنتقي ماينفعش يعرض شغلانة مقفولة، والتقرير ماينفعش يخفي تاريخًا.

**حاجتان في بريفي كانوا غلط/متعالجين:**
- طلبت استبعاد **السطور** المحذوفة ناعمًا — `inventory_issue_items` **مالهوش عمود `deleted_at`** أصلًا (السطور بتتسلسل مع الأب). القابل للاستبعاد هو `inventory_issues.deleted_at` بس، وهو مستبعَد صراحةً (الـquery builder بيتخطّى نطاق الموديل).
- `InventoryIssue::jobTag()` **مش مستخدمة في التقرير** عمدًا — علاقة `belongsTo` على `reference_id` **polymorphic-by-convention** ⇒ ماينفعش يُوثق بيها في التجميع. الاستعلامات بتفلتر على `reference_type = 'job_tag'` وبتحلّ الوسوم منفصلة. **وفيه اختبار بيثبت إن إذن نوعه `sale` وreference_id بتاعه صادف id وسم مابيتحسبش.**

**26 اختبار** أبرزها: **«5 أذون معتمدة على وسم واحد في يوم ⇒ `issues_count = 5`»** (اختبار قبول المالك الحرفي: أصناف 2، كمية 11، قيمة 105) · الملغي مستبعَد · المسودة مستبعدة · `pending_approval` مستبعد · **الملخّص والتفصيل بيحذفوا كل حقول القيمة بلا الصلاحية** (`not->toHaveKey` ⇒ **غايبة مش صفر**) · المصروف جزئيًا بيعدّ اللي اتحرّك · وسم شركة تانية 404 · الوسم المقفول لسه ليه تقرير · التصفيح والإجماليات على الفترة كلها.

**النتيجة:** Inventory **615 / 1** (589 + 26 ✓) — صفر فشل جديد. وكمان 4 suites صلاحيات في Core: **37 نجاح** بلا انحدار. `pint --test` على 4 ملفات ملموسة بس = pass، و`git diff --stat` بيقول **518 إضافة / 0 حذف** ⇒ **ولا دالة موجودة اتعدّلت**.
**وضاف بند CHANGELOG** بلغتين يغطّي قدرة الوسوم (WP1+WP2+WP3) — اللي كنت مؤجّله لحد ما القدرة تبقى قابلة للاستخدام.


---

### WP4 — الإزالة (2026-07-27) — ✅ **تم**

**الباك إند المشال:** مسار `POST orders/quick` · `quickStore()` ومدخلته في الـmiddleware والـimport · ملف `QuickStoreProductionOrderRequest.php` · `ORDER_TYPE_FROM_STOCK_ISSUE` و`isFromStockIssue()` · وصياغة `stock_issue` من التعليقات.
**اللي فضل زي ما هو (متأكَّد):** حقل `order_type` في الريسورس · فلتر `order_type` في الـindex · منتقي `issuable=1` · و`permission:production.orders.create` على `store`.

**الواجهة المشالة:** `createQuick()` ونوعه · `'stock_issue'` من اتحاد `ProductionOrderType` · الثابت · زرار «أمر مؤقت» وحواره وكل حالته ومعالجاته · بادج «من إذن صرف» وخياره في الفلتر · كتلتا SCSS · و**6 مفاتيح ترجمة متأكَّد إنها ميتة** لكل لغة. `I18N_VERSION` → `'20260725d'`.

**بحث نصّي شامل على الريبوين: صفر نتيجة** لـ`orders/quick` · `quickStore` · `isFromStockIssue` · `ORDER_TYPE_FROM_STOCK_ISSUE` · و`stock_issue` كقيمة نوع أمر.

**⭐ قرار أحسن من بريفي:** أنا قلت «5 اختبارات تفضل». هو لقى **4 كمان** بيغطّوا فلتر `issuable=1` والبحث بالمنتج — ودول من الجزء **اللي بيفضل** — فحافظ عليهم كمان ⇒ **9 اختبارات** في ملف مُعاد تسميته `ProductionOrderPickerAndTypeTest.php` (58 تأكيد، كلهم ناجحين لوحدهم). لو مشي على العدد اللي كتبته كان ضيّع تغطية حيّة.

**النتيجة:** Production **616 / 10** — الأساس 625/10 وحذف **9 بالظبط** ⇒ **صفر فشل جديد**، واتأكّد إن العشرة هما نفسهم بإعادة تشغيل الثلاث ملفات منفصلة (23 نجاح / 10 فشل). البناء أخضر بـ38.951 ثانية بلا تحذير يخصّ أي ملف لمسه.

**«الصرف على أمر إنتاج حقيقي» متأكَّد سليم عبر السلسلة كلها:** `buildReferenceTypeOptions()` لسه بيدفع `production_order` خلف `allowIssueAgainstOrder()` · `loadIssuableOrders()` → `listIssuable()` → `GET orders?issuable=1` · بذرة وضع التعديل ومسار الحفظ لسه بيتفرّعوا على `reference_type === 'production_order'` · والباك إند محتفظ بفلتر `issuable` و`ApplyMaterialIssueOnApproval` لسه بيبوّب على `IssueReferenceType::ProductionOrder`. **ولا ملف من دول خسر سطرًا.**

**الـCHANGELOG (تأكّدت بنفسي):** `[Unreleased]` **صفر إشارة** للأمر المؤقت. الأربع إشارات الباقية كلها في **إصدارات منشورة** (5.1.49 · 5.1.39 · 5.0.0) — سجل تاريخي ممنوع تعديله، وصح إنها اتسابت.

**⚠️ درس تشغيلي اتسجّل:** وكيلان كتبوا حلقة انتظار `until ! pgrep -f "pest Modules/…"; do sleep; done` — و`pgrep -f` **بيطابق سطر أوامر الحلقة نفسها** ⇒ الشرط عمره ما يتحقّق ⇒ **لفّوا 47 دقيقة على الفاضي** بعد ما السويت خلصت. اتقتلوا يدويًا، والتحذير اتحطّ في بريف WP6. **القاعدة: أوامر في المقدّمة، مفيش حلقات pgrep.**


---

### WP6 — التنفيذ (2026-07-27) — ✅ **تم** (آخر حزمة)

**شاشة إدارة الوسوم** `/core/issue-tags` + مدخل في القائمة الجانبية تحت «حركات المخزون» بعد أذون الصرف مباشرة، ومسجَّل في `nav-items.config.ts` وفي قائمة مسارات المخزون عشان الموديول النشط يتحدّد صح.
**تبويب التقرير** = التبويب السابع في مركز تقارير المخزون. ⭐ **`git diff --numstat` على ملفات المركز الثلاثة = صفر حذف** ⇒ كل تغيير إدراج محض؛ ولا سطر من التبويبات 0–5 اتعدّل.

**إخفاء عمود القيمة — مش تصفير:** `tagCanViewValue = computed(() => tagData()?.can_view_value === true)` (المصدر الباك إند، ومقارنة `=== true` ⇒ غياب العلم كمان بيخفي). كل مواضع القيمة جوّه `@if` — بطاقة المؤشر والـ`th` والـ`td` **وعمود التصدير**. **مفيش أي بديل `'—'`** ⇒ بلا الصلاحية العمود **مش موجود في الـDOM أصلًا**. والأنواع بتساند ده (`total_value?`, `unit_cost?`, `value?` اختيارية) فأي عرض بلا حارس بيبقى خطأ ترجمة مش صفرًا صامتًا.

**`counted_statuses` منشورة في الواجهة** كسطر رمادي هادي تحت المؤشرات، **مبنيّة من مصفوفة الرد** مش مثبّتة بالكود ⇒ لو قاعدة الباك إند اتغيّرت، الشرح بيتبعها.

**التفصيل:** ضغطة على الصف ⇒ نفس الفترة بالظبط ⇒ حوار بأذون الصرف (رقم الإذن رابط عميق لشاشة الأذون)، وكل إذن يتوسّع لسطوره. شريط الإجماليات بيوضّح إنها **إجماليات الفترة كلها** مش الصفحة.

**⭐ قرار دقيق:** شاشة الإدارة بتعرض `name_en` و`name_ar` **منفصلين** مش الاسم المحلّي، والتعديل بيملا الفورم من `item.name_en` تحديدًا — **لأن العمود الإنجليزي هو اللي عليه الفهرس الفريد**؛ الملء من الاسم المحلّي كان هيكتب عربي جوّه العمود الإنجليزي ويكسر التفرّد.

**قرارات أخرى:** الوسوم المقفولة ظاهرة (`statusFilter` مبدئيًا `'all'` ⇒ افتراضي الـAPI `active` عمره ما يُورَث) · تحذير التقارب موجود في شاشة الإدارة كمان، والاقتراح المقفول **قابل للضغط هنا** (الأدمن يقدر يفتحه) بعكس شاشة الصرف · مفيش أي كنترول معطّل للصلاحيات (إخفاء بـ`*appCan`، وحقل الحالة **غايب من قالب الإنشاء** لأن المسار مابيقبلوش) · `per_page=200` مع **رسالة تحذير صريحة** لو `meta.total > items.length` («القص الصامت» ممنوع) · تحميل تلقائي عند فتح التبويب لأول مرة · ورابط عميق `?tab=6&tag=<id>`.

**54 مفتاح ترجمة** لكل لغة، واتأكّد بسكربت إن **341 مفتاحًا** بتستدعيهم الملفات الستة الملموسة بيتحلّوا في اللغتين. `I18N_VERSION` → `'20260725e'`.

**البناء أخضر — و«صفر تحذير جديد» مُثبَت بطريقة صحيحة:** استخرج كل مسار `src/...` مذكور في تحذيرات **بنائين متتاليين** (85 ملف) والمجموعتان **متطابقتان**؛ والملف الوحيد بتاعه اللي بيظهر تحذيره سطر 143 (كود سابق)، و`git diff` بيثبت إن تعديله الوحيد `@@ -535,0 +536,128 @@`.

---

## الإنهاء (2026-07-27)

- **الاختبارات النهائية:** Inventory **615 / 1** (= خط الأساس بالظبط بعد WP4) · اختبارات الوسوم الثلاثة **74 نجاح / 294 تأكيد** · Production **616 / 10** (625 − 9 محذوفة).
- **النشر:** `main-CMHJRIN2.js` على `/app`، و`apiUrl` على moonui2، والمفاتيح العربية متأكَّدة في الحزمة المنشورة (وسوم الشغلانات · المنصرف حسب الوسم · وسم شغلانة).
- **CHANGELOG:** بند 9296 كان **مكتوبًا كاملًا ودقيقًا** ومابيوعدش بحاجة غير موجودة ⇒ **مش محتاج تعديل**. وبند 9260 اتعدّل في مكانه لإزالة وعد «الأمر المؤقت»، **وبند إضافي** (رسالة الصرف الزائد) اتعدّل مثاله اللي كان بيشاور على الأمر المؤقت. الإشارات الباقية للأمر المؤقت كلها في **إصدارات منشورة** — سجل تاريخي ممنوع تعديله.
- **درس تشغيلي:** حلقات `until ! pgrep -f "..."` **بتطابق نفسها** فماتنتهيش أبدًا — ضيّعت 47 دقيقة. القاعدة: أوامر في المقدّمة.
- **درس تنظيمي:** أنا طلبت سويتات كاملة مع كل حزمة، وده ضاعف الزمن (التشغيلة الواحدة 24.5 دقيقة). القاعدة الصحيحة اللي رجعت لها: **اختبارات مُنطاقة أثناء العمل، والسويت الكاملة بوابة إصدار واحدة في الآخر.**


---

## بوابة الإصدار النهائية (2026-07-27)

- **Inventory: 626 نجاح / 1 فشل** — الفشل الوحيد هو نفس السابق الموثّق (`opening balance creates inventory movement with type opening`). **صفر فشل جديد** (خط الأساس 541/1 ⇒ 542، والنهاية 627 = 542 + 85 اختبار جديد).
- **النشر النهائي:** `main-LGIJ3CEN.js` على `/app` · `I18N_VERSION = 20260725e` · `apiUrl` على moonui2.
- **التذكرة:** التحقّق اتنشر ⇒ **`client_review`** · الخطوة **6621**.

### إصلاحات المراجعة الشاملة (5) — ✅
1. **[HIGH]** `reference_id` بايت عند تغيير نوع المرجع ⇒ اتصلّح في `UpdateInventoryIssueRequest::prepareForValidation()`. القاعدة: **مقارنة نصّية بالنوع المحفوظ** — التصفير بس لما النوع **يتغيّر فعلًا**، ولكل نوع مالوش قاعدة تحقّق خاصة. ⚠️ الفخّ اللي اتفادى: إذن صرف مبيعات مولّد من فاتورة **بيحتفظ** بـ`reference_id` بتاعه في تعديل ملاحظات وفي PUT كامل بنفس النوع — **مُثبَت باختبار صريح**. وفيه refactor صغير (`persistedIssue()` محفوظة) قلّل الاستعلامات من 2 لـ1.
2. **[MED]** اختبار صفر الأثر المحاسبي اتوسّع ليؤكّد **صفر `journal_entries`** (كان بيغطّي قيود «تحت التشغيل» بس ⇒ ماكانش هيمسك قيدًا من نوع تاني — وده بالظبط شكل النتيجة رقم 1).
3. **[MED]** علامات الاتجاه الخفيّة (`\x{200C}-\x{200F}` … `\x{FEFF}`) اتضافت للتطبيع — اسم ملزوق من واتساب كان بيعمل وسمًا **مطابق بالبكسل** والتقرير يقسم شغلانة لصفين. متحقَّق: `RLM+صيانة` ⇒ تطابق 1.0.
4. **[LOW]** `status` بقى متحقَّقًا في قائمة الوسوم (كان `?status=bogus` بيرجّع قائمة فاضية بصمت).
5. **[LOW]** زرار التقرير في شاشة الإدارة اتبوّب على `inventory.reports.view`.
+ **التعليق الكاذب اتصحّح** بدقة: مسح الكنترول **بيحمي فعلًا** في حالة أمر الإنتاج (الحمولة بتبعت المفتاح) و**مابيحميش حاجة** في الأنواع الخمسة التاريخية — وسمّى الحارس الجديد على الباك إند كحماية حقيقية.

**النتائج بعد الإصلاحات:** اختبارات الوسوم **85 / 347** (كانت 74/294) · اختبارات أذون الصرف عبر 10 ملفات **166 / 802** · بناء أخضر.

### مؤجّلات ⇒ تذاكر لاحقة
- **⚠️ لغم سابق مش من شغلنا:** `Modules/Sales/tests/Feature/CogsAtDeliveryCancelTest.php` بيفشل **6/6** بـ`no_open_period` — **تركيبة منتهية الصلاحية** بتثبّت فترة مالية `2026-06-01 → 2026-06-30` بتعليق «الفترة اللي بتغطّي النهارده». **بيفشل يوميًا من أول يوليو** ومالوش أي علاقة بالوسوم.
- `\x{200B}` (ZERO WIDTH SPACE) مش ضمن التطبيع (اتّبع النطاق المحدَّد حرفيًا) — إضافته تحسين خالص.
- `total_quantity` بيجمع وحدات مدخلة مختلطة — عُرف البيت (نفس تقرير أمر الإنتاج)، و`base_quantity` متاح لو حبينا الدقة.
- التفصيل بيقرا حقول التاريخ الحالية مش الفترة المحمّلة (تجميلي، الحوار بيطبع فترته).
