# WP1 — جدول الوسوم + CRUD + الصلاحيات + بحث التقارب

**التذكرة:** ISS-2026-9296 · **الريبو:** BE (`/home/moonui2/moon-erp-be`, فرع `hazemdev2`) · **migration: نعم** · **مش [FIN]** (مفيش أي أثر محاسبي)

## الهدف
كيان **«وسم شغلانة»** (job tag) في موديول Inventory: أمين المخزن بيسمّي به شغلانة (صيانة ماكينة 3، عيّنة عميل، إصلاح خط 2…) ويربط بيه أذون الصرف، وبعدين يطلع تقرير بالوسوم. **مش مركز تكلفة ولا حساب** — مجرّد تصنيف تشغيلي للمراقبة.

## الملفات (أنشئ)
- migration: `Modules/Inventory/database/migrations/<timestamp>_create_inventory_issue_tags_table.php`
- `Modules/Inventory/app/Models/InventoryIssueTag.php`
- `Modules/Inventory/app/Http/Controllers/InventoryIssueTagController.php`
- `Modules/Inventory/app/Http/Requests/Store|UpdateInventoryIssueTagRequest.php`
- `Modules/Inventory/app/Http/Resources/InventoryIssueTagResource.php`
- مسارات في `Modules/Inventory/routes/api.php`
- الصلاحيات في `Modules/Core/database/seeders/RolePermissionSeeder.php`
- اختبار: `Modules/Inventory/tests/Feature/InventoryIssueTagTest.php`

## الجدول
`inventory_issue_tags`: `id` · `company_id` (مفهرس، **NOT NULL**) · `name` · `name_ar` (nullable) · `description` (nullable) · `status` (`active`/`closed`، افتراضي `active`) · `created_by` (nullable) · `timestamps` · `softDeletes`.
- **فهرس فريد** على (`company_id`, `name`) — الاسم مايتكرّرش جوّه الشركة. خُد بالك من `deleted_at` مع الفريد (اتبع نمط الجداول المشابهة في الموديول؛ لو الفريد بيتعارض مع الحذف الناعم، اعمله فريد على (`company_id`,`name`,`deleted_at`) أو تحقّق برمجي — قرّر واشرح).
- ⚠️ طابع الوقت في اسم الـmigration **لازم يترتّب بعد آخر migration موجودة**.
- **شغّل الـmigration على قاعدة التطوير في نفس الخطوة** (`bash local-deploy.sh`) — الباك إند التطويري بيشتغل مباشر من الشجرة، وأي تأخير بيدّي 1054 لأي حد بيستخدم الـdev.
- ⛔ **ممنوع نهائيًا** `migrate:fresh` / `migrate:refresh` / `db:wipe` / `RefreshDatabase` على `moonui2_dev_be` — القاعدة **مش** عليها binlog.

## الصلاحيات (نمط `inventory.issues.*` الموجود)
- `inventory.issue-tags.view` — يشوف القائمة والتقرير.
- `inventory.issue-tags.create` — **يضيف وسم جديد** (ده اللي العميل قرّر يبقى **منفصل**).
- `inventory.issue-tags.update` — يعدّل/يقفل/يفتح وسم.
- `inventory.issue-tags.delete` — حذف ناعم.
اتبع بالظبط طريقة تسجيل `inventory.issues.*` في `RolePermissionSeeder` (شامل أي خريطة تبعية/أدوار افتراضية زي `inventory.issues.create => [update, delete]` في :210)، **وشغّل الـseeder** بعدها.

## المسارات
- `GET  /api/inventory/issue-tags` — قائمة مصفّحة، company-scoped، بفلاتر `status` و`search`، مرتّبة (الأحدث أو الاسم — قرّر واشرح). صلاحية `…view`.
- `POST /api/inventory/issue-tags` — إنشاء. صلاحية **`…create`**.
- `GET  /api/inventory/issue-tags/{id}` · `PUT` · `DELETE` بالصلاحيات المقابلة.
- **`GET /api/inventory/issue-tags/similar?name=<q>`** — ⭐ **مسار التقارب** (تحت `…view`): بيرجّع الوسوم اللي اسمها قريب من `name` عشان الواجهة تحذّر **قبل** الإنشاء.
  - المطلوب يمسك: **صيانة / صيانه / الصيانة** (اختلاف تاء مربوطة/هاء، أل التعريف، الهمزات، المسافات الزيادة، حالة الأحرف في الإنجليزي).
  - **الطريقة:** طبّع الاسم (شيل «ال» البادئة، وحّد `ة→ه` و`أإآ→ا` و`ى→ي`، شيل التشكيل والمسافات المكرّرة، lowercase) وقارن على المطبَّع — في PHP، **مش** في SQL (عشان يشتغل على sqlite في الاختبارات وعلى MySQL في الإنتاج). قصّ النتيجة (مثلًا 5 أقرب) واحسب تشابهًا بسيطًا (`similar_text`/Levenshtein) فوق عتبة معقولة.
  - ⛔ **مايمنعش الإنشاء** — تحذير بس؛ القرار للمستخدم. (المنع الوحيد هو الاسم **المطابق تمامًا** عبر الفهرس الفريد.)

## معايير القبول
- الجدول واقع، والـCRUD شغّال، وكله **company-scoped** (الإنشاء بياخد `company_id` من المستخدم مش من الحمولة).
- `…create` **وحدها** بتسمح بالإنشاء؛ ومن غيرها 403 حتى لو معاه صلاحية الصرف.
- إقفال وسم بيمنعه من الظهور كخيار قابل للاختيار (`status=active` هو الفلتر الافتراضي للمنتقي) **من غير** ما يمس الأذون المربوطة بيه قبل كده ولا التقارير التاريخية.
- `similar` بيرجّع «صيانة» لما تدوّر على «صيانه» أو «الصيانة».

## الاختبارات (Pest، ملف جديد)
إنشاء بصلاحية · **403 من غيرها** · company-scoped في القائمة والعرض والتعديل (ووسم شركة تانية = 404/403) · الاسم المكرّر داخل نفس الشركة يترفض · **نفس الاسم مسموح في شركتين مختلفتين** · الإقفال والفتح · الحذف الناعم · و**اختبارات التقارب**: «صيانه» ⇒ بترجّع «صيانة» · «الصيانة» ⇒ بترجّع «صيانة» · اسم بعيد تمامًا ⇒ بترجّع فاضي.

**شغّل بـCLI php صراحةً** (الافتراضي php-cgi وبيقتل الرَنَر بصمت — exit 255 بلا مخرجات):
```bash
cd /home/moonui2/moon-erp-be && /opt/cpanel/ea-php82/root/usr/bin/php -d memory_limit=1G vendor/bin/pest Modules/Inventory/tests
```
**الأساس:** Inventory **541 نجاح / 1 فشل سابق** (`OpeningBalanceApiTest > opening balance creates inventory movement with type opening`). **صفر فشل جديد.**

## خارج النطاق
- ⛔ أي لمس لإذن الصرف نفسه — ده WP2.
- ⛔ أي تقرير — ده WP3.
- ⛔ أي شاشة واجهة.
- ⛔ أي علاقة بالمحاسبة أو مراكز التكلفة.

## بعد الانتهاء
`./vendor/bin/pint` **على الملفات اللي لمستها بس** (⚠️ متشغّلوش على الموديول كله — ده لوّث 41 ملف في تذكرة سابقة) + `chown moonui2:moonui2` عليها + `bash local-deploy.sh`. **بلا commit وبلا git.**
