Moon ERP · Inventory · مراجعة Fable للـUX + خطة متكاملة

شاشة أرصدة المخزون بعد المرحلة ٤ — اللوطات + بضاعة الأمانة: مراجعة شاملة وتصوّر مستهدف كامل

المرحلة ٤ أوصلت لوطات حيّة مفصولة بالمالك إلى توسعة الصف، وشارة «مستحق الإرجاع» إلى الترويسة — لكن صف القائمة نفسه ما زال أعمى للملكية: الكمية والقيمة والمتاح وأقرب انتهاء كلها فيزيائية تخلط بضاعتك ببضاعة عملائك. هذا الملف مراجعة مبنية على قراءة فعلية للكود المشحون (FE + BE)، يرتّب كل المشاكل بالخطورة، ويرسم التصوّر النهائي لكل جزء من الشاشة، ويسلّم خطة بناء مرتّبة بالقيمة.

📅 2026-07-11 🌿 hazemdev2 🔍 مصدر: stock-balances.component.* · StockBalanceController · StockBalanceResource 🔗 يبني على phase4-per-lot-fefo.html §10 + phase4.4-fe-build-spec.md

١ الخلاصة التنفيذية — الحكم

توسعة اللوطات المشحونة (Surface 1) ممتازة بنيويًّا: مجموعات ملّاك بنفسجية، صف «غير مخصّص»، تذييل تطابق بسطرين — التصميم الأصلي وصل سليمًا إلى ٨٠٪. المشكلة أن الطابق الأعلى — صف القائمة وبطاقات الإحصاءات — لم يلحق بالمرحلة ٤: حاجز owned_value الذي أُصلح في تقرير التقييم (4.4) ما زال مثقوبًا هنا، في الشاشة التي يفتحها أمين المخزن كل يوم.

🛑
الاكتشاف الأهم

الـBE يشحن منذ 4.4 حقول owned_value / owned_quantity / consignment_value / consignment_quantity على كل صف رصيد (StockBalanceResource.php:57-60) — والواجهة لا تقرأها إطلاقًا: واجهة StockBalance في inventory.model.ts لا تعرّفها أصلًا. عمود «إجمالي القيمة» وبطاقة «إجمالي القيمة» يعرضان القيمة الفيزيائية شاملةً بضاعة العملاء — نفس التسريب التأسيسي («بضاعة العملاء بتضخّم قيمة مخزوني») الذي بنينا المرحلة ٤ كلها لإغلاقه. البيانات موجودة على السلك، ينقصها العرض فقط.

أعلى خمس مشاكل (التفصيل الكامل في §٣)

  1. تسريب القيمة على القائمة — القيمة/الكمية/المتاح كلها فيزيائية بلا أي فصل ملكية ولا حتى تلميح. حرج
  2. تصادم المتغيّرات (variants) — مفتاح الصف product_id_warehouse_id يتجاهل product_variant_id: منتج بمتغيّرين في نفس المخزن ⇒ صفّان بنفس المفتاح، توسعة أحدهما تفتح الاثنين، ولوحة اللوطات تخلط لوطات كل المتغيّرات وتطابقها ضد إجمالي المنتج بينما الصف يعرض كمية متغيّر واحد. حرج
  3. لا عدسة ملكية على القائمة — لا يمكن مسح «أي الأصناف تحمل بضاعة عملاء؟» ولا «أرصدة شركة النور فقط» دون توسيع كل صف يدويًّا؛ التقارير حصلت على العدسة والقائمة لا. عالٍ
  4. «إجمالي القيمة» يجمع الصفحة الحالية فقط — ٢٥ صفًّا؛ الرقم يتغيّر مع كل صفحة ويُقرأ كإجمالي الشركة. عالٍ
  5. «أقرب انتهاء» أعمى للمالك ويقرأ مصادر ما قبل المرحلة ٤ — الخريطة تُبنى من السيريالات وأنساب الاستلام، لا من سجل اللوطات الحي؛ لوط عميل منتهٍ إما لا يظهر أصلًا أو يظهر كأنه خسارتك. عالٍ
🎯
التوصيات الرئيسية

١) أكمِل حاجز الملكية إلى الطابق الأعلى: خلية رصيد مركّبة 105 +45 🤝، عمود قيمة يقرأ owned_value، بطاقة إحصاء بنفسجية للأمانة — كلها FE فقط، الحقول مشحونة بالفعل (خطوة B1، الأصغر والأعلى قيمة).

٢) انقل عدسة الملكية من التقارير إلى القائمة بنفس اللغة المشحونة (p-select + شريط بنفسجي دائم + URL) — يحتاج باراميتر owner على index في الـBE.

٣) وحّد مصدر «أقرب انتهاء» على سجل اللوطات (inventory_lot_balances) مع نسبة المالك — المرحلة ٤ بَنَت مصدر الحقيقة؛ القائمة ما زالت تقرأ الأنساب القديمة.

ما هو صحيح — أبقِه كما هو

التحميل الكسول للوطات مع كاش لكل صف (أداء سليم) · ترتيب المجموعات: ملكنا أولًا ثم العملاء بالبنفسجي 🤝 · صف «غير مخصّص (ما قبل اللوطات)» غير مقصوص حتى لا يكذب التطابق · تذييل التطابق بسطرين · شارة «مستحق الإرجاع» في الترويسة بصمت-عند-الصفر · وسم ~ للتقديري مع تلميحة «أكّد بجرد» · فصل ألوان الصلاحية (أحمر/كهرماني) عن الملكية (بنفسجي) — القوانين الأربعة محترمة داخل التوسعة.

٢ الشاشة اليوم — ما المشحون فعلًا (قراءة كود، لا ذاكرة)

المسار: /app/core/stock-balances · الملفات: features/stock-balances/stock-balances.component.{ts,html,scss} + core/services/stock-balance.service.ts + BE StockBalanceController/StockBalanceResource

٢.١ لقطة الشاشة الحالية بالكلمات (مطابقة للكود)

الوضع الحالي — كما يراه المستخدم اليوم (إعادة رسم من الكود المشحون)
أرصدة المخزون 🤝 مستحق الإرجاع (3)
الأرصدة الحالية لكل منتج في كل مخزن
⟳ تحديث
🔍 بحث باسم المنتج أو الكود… كل المخازن التصنيف
128
إجمالي المنتجات
191,650.00
إجمالي القيمة
⚠ فيزيائي (يشمل بضاعة العملاء) + صفحة حالية فقط
4
مخزون منخفض
⚠ من صفوف الصفحة فقط
2
مخزون صفري
⚠ من صفوف الصفحة فقط
الكودالاسمالمتغيّرالمخزن الكميةالمتاحمتوسط التكلفةإجمالي القيمةأقرب انتهاء
YRN-040غزل قطن 40/1المخزن الرئيسي 150.00 150.00 80.00 12,000.00 ~2026-08-20 قارب على الانتهاء 📊 🗓
الكمية 150 = 105 ملكك + 45 بضاعة عميلين — لا شيء في الصف يقول ذلك. القيمة 12,000 تشمل 3,600 قيمة بضاعة ليست ملكك. شارة الانتهاء قد تكون أصلًا للوط العميل.

٢.٢ جرد المكوّنات المشحونة

المكوّنالمشحون (من الكود)التقييم
شريط الفلاتربحث (debounce 400ms) · مخزن · تصنيف/فرعي · «مسح الفلاتر» الشرطيأبقِه — ناقصه عدسة الملكية وhide_zero
بطاقات الإحصاءات٤ بطاقات: منتجات (من meta.total) · قيمة (مجموع total_value للصفحة) · منخفض/صفري (للصفحة)أصلِح — قيمة فيزيائية + نطاق صفحة
أعمدة الجدولكود · اسم · متغيّر · مخزن · كمية · متاح · متوسط تكلفة · إجمالي قيمة · أقرب انتهاء · إجراءات (كارت صنف + تقرير انتهاء)أصلِح — كلها عمياء للملكية؛ لا فرز
شارة الانتهاءp-tag أحمر (منتهٍ) / كهرماني (≤ near_expiry_days) + وسم ~ للتقريبي مع تلميحةأبقِه — أضِف نسبة المالك (§٩)
توسعة اللوطاتسهم لكل صف → تحميل كسول getProductLots + كاش · مجموعات ملّاك (ملكنا أولًا، العملاء بـ🤝 بنفسجي) · مستلَم/مصروف/متبقٍ · صف «غير مخصّص LEGACY» · «معلنة» تحت مجموعة العميل · تذييل تطابق بسطرينأبقِه + صقل §٨
تذييل التطابقسطر ١: الإجمالي الفيزيائي ✓/⚠ · سطر ٢: ملكنا (كمية) · أمانة (كمية، «معلنة»)حسّن — بلا قيم مالية ولا جملة «التقييم يستخدم ملكنا فقط» (كانت في التصميم الأصلي §10.2)
شارة «مستحق الإرجاع»due-back-badge في الترويسة: بنفسجية، تختفي عند الصفر، حلقة كهرمانية للتقادم، تنقل للاستلافاتأبقِه
الحالاتتحميل: سبينر p-table · فارغ: صف «لا توجد بيانات» · خطأ قائمة: Toast فقط · خطأ لوطات: Toast + لوحة فارغة تمامًاأصلِح §١٠
🔬
حقيقة سلكية

استجابة GET inventory/stock-balances تحمل اليوم لكل صف: quantity وtotal_value (فيزيائي)، وowned_quantity/owned_value/consignment_quantity/consignment_value (حاجز 4.4)، وnearest_expiry+source+days_until_expiry، وmeta.near_expiry_days. أي أن ٩٠٪ من المستهدف في §٥ و§٧ لا يحتاج BE جديدًا.

٣ جدول المشاكل — مرتّبًا بالخطورة

كل مشكلة مُتحقَّق منها في الكود المشحون (سطر المرجع بجانبها). «الإصلاح» يشير إلى خطوة البناء في §١٤.

الخطورة#المشكلةلماذا تهم (أثرها على الثقة)الإصلاح
حرجP1 تسريب القيمة على القائمة. عمود «إجمالي القيمة» = total_value الفيزيائي؛ حقول owned_*/consignment_* المشحونة من 4.4 غير معرّفة في نموذج الـFE أصلًا (inventory.model.ts:315). الرقم الذي يبني عليه المالك قراره اليومية يشمل بضاعة ليست ملكه. حاجز 4.4 أُغلق في التقارير وبقي مفتوحًا في أكثر شاشة استخدامًا — تناقض بين شاشتين يهدم الثقة في الاثنتين. B1
حرجP2 تصادم المتغيّرات. rowKey = product_id_warehouse_id (component.ts:227) يتجاهل product_variant_id؛ وproductLots في الـBE لا يقبل متغيّرًا ويجمع physical_total عبر كل المتغيّرات. منتج بمقاسين في نفس المخزن: توسعة صف تفتح الصفّين معًا (dataKey مكرّر في PrimeNG)، واللوحة تعرض لوطات المقاسين مخلوطة ويطابقها ضد إجمالي لا يساوي كمية الصف. أرقام «لا تطابق» ظاهرة للمستخدم = فقدان ثقة فوري. B2
عالٍP3 لا عدسة ملكية على القائمة. لا فلتر «ملكنا / عميل محدد»، لا مؤشر بالصف قبل التوسعة، وindex في الـBE بلا باراميتر owner. سؤالا أمين المخزن اليوميان — «أي رف عليه بضاعة عملاء؟» و«ورّيني بضاعة شركة النور» — يستلزمان توسيع الصفوف واحدًا واحدًا. التقارير أخذت العدسة (4.4-FE P2) والقائمة لا: نمط موجود ومجرّب، فقط غير منقول. B3
عالٍP4 بطاقات إحصاء بنطاق الصفحة. totalValue/lowStockCount/zeroStockCount تُحسب من items() = صفوف الصفحة الحالية فقط (component.ts:80-84). «إجمالي القيمة» يتغيّر وأنت تقلّب الصفحات — رقم يبدو إجماليًّا وهو عيّنة. أخطر أنواع الأرقام: الصادق شكلًا الكاذب نطاقًا. B6
عالٍP5 «أقرب انتهاء» يقرأ مصادر ما قبل المرحلة ٤ وبلا مالك. الخريطة تُبنى من product_serials ثم أنساب الاستلام ثم mirror الحركات (Controller:130-232) — لا تقرأ inventory_lot_balances أبدًا. لوطات الأمانة تدخل عبر CRN ولا تمرّ على أنساب الاستلام ⇒ انتهاؤها قد لا يظهر في القائمة إطلاقًا؛ وإن ظهر عبر مصدر آخر يظهر بلا نسبة مالك فيُقرأ كخسارتك. المرحلة ٤ بنت مصدر حقيقة حيًّا والقائمة تتجاهله. B5
عالٍP6 فشل تحميل اللوطات = لوحة فارغة بكماء. عند خطأ getProductLots: Toast + يبقى الصف موسّعًا على لا شيء (لا رسالة ولا زر إعادة) (component.ts:242 + قالب التوسعة). المستخدم يرى مساحة رمادية فارغة تحت الصف ويستنتج «لا لوطات» — استنتاج خاطئ عن البيانات بسبب حالة خطأ. (نفس ملاحظة Fable المؤجلة #3 على dialog التخصيص — العائلة كلها تحتاج نمط خطأ موحّدًا.) B4
متوسطP7 كاش اللوطات لا يُبطل عند التحديث. زر ⟳ يعيد تحميل القائمة لكن lotsCache يبقى — اللوحات الموسّعة تعرض أرقامًا قديمة بجانب صف محدَّث. رقمان متناقضان على الشاشة نفسها بعد ضغطة «تحديث» — بالضبط ما بُني تذييل التطابق لمنعه. B4
متوسطP8 تذييل التطابق ناقص المعلومة المالية والتفسير. سطر ٢ كميات فقط (التصميم الأصلي §10.2 نصّ على القيم + «التقييم والتكلفة يستخدمان ملكنا فقط»)؛ وحالة عدم التطابق أيقونة ⚠ بتلميحة COMMON.WARNING العامة بلا مقدار الفرق. التذييل هو «آلية الثقة» المعلنة للمرحلة ٤؛ نسخته المشحونة تقول «يوجد خلل» ولا تقول كم ولا ماذا تفعل. B4
متوسطP9 لا قيمة لكل لوط في التوسعة. unit_cost/declared_unit_cost تصل من الـAPI وتُهمل؛ التصميم الأصلي كان فيه عمود Value (ملكنا بالتكلفة، العميل «معلنة ⓘ»). بدون قيمة اللوط لا يمكن تقدير خسارة لوط منتهٍ أو قيمة عهدة عميل واحد من الشاشة. B8
متوسطP10 سهم التوسعة على كل صف حتى غير المتتبَّع. منتج خدمي/غير batch ⇒ توسعة تنتهي بـ«لا توجد بيانات لوطات بعد» — طريق مسدود. التصميم نصّ على «صفر ضريبة على الـ٩٥٪»؛ السهم الموعود بلا شيء خلفه يعلّم المستخدم تجاهل الأسهم كلها. B4
متوسطP11 فجوات RTL/i18n في الجدول الرئيسي. خلايا الكمية/التكلفة/القيمة/تاريخ الانتهاء بلا dir="ltr" (لوحة اللوطات تفعلها صحيحًا)؛ وسم LEGACY نص إنجليزي ثابت في القالب؛ البنفسجي مكتوب بألوان صلبة عبر ::ng-deep بدل توكنات --moon-consign المعرّفة في 4.4. «~2026-08-20» بجوار نص عربي قابل للانعكاس البصري؛ وسم أجنبي في شاشة عربية-أولًا؛ ولون الملكية غير موحّد المصدر مع باقي السطوح. B4/B8
متوسطP12 لا مدخل جرد من الشاشة. تلميحة ~ تقول حرفيًّا «أكّد بجرد» ولا تعطي طريقًا إليه؛ شاشة الجرد لا تقرأ queryParams للتهيئة المسبقة. الشاشة تشخّص («تقديري — أكّد بجرد») ثم تترك العلاج في مبنى آخر. إغلاق الحلقة = ضغطة واحدة. B7
منخفضP13 قدرات جدول ناقصة. لا فرز بالأعمدة (لا pSortableColumn ولا فرز lazy)، لا hide_zero (الـBE يدعمه)، لا تصدير، وخطأ تحميل القائمة = Toast + «لا توجد بيانات» المضلِّلة. مضايقات تراكمية؛ «لا بيانات» عند الخطأ هي الأسوأ بينها (تكذب عن سبب الفراغ). B8
منخفضP14 كاشف تصادم التشغيلة الواحدة تحت مالكين غير مبني (⚠ في §10.2)، ولا زر «إظهار المستنفَد» (الـBE يقصّ اللوطات المستنفدة بـlive()). نفس رقم الدفعة عند مالكين = كارثة جرد صامتة؛ والمستنفَد أحيانًا مطلوب للمراجعة التاريخية. B8
منخفضP15 ملاحظات أداء BE. consignment_portion_map تُحسب لكل الشركة في كل طلب صفحة (Controller:104-108) لا لصفوف الصفحة فقط؛ غير مؤلم الآن، سيؤلم مع نمو لوطات الأمانة. تحسين نطاق لاحق — لا يغيّر العقد. B5

٤ الحالي مقابل المستهدف — جنبًا إلى جنب

🔴 اليوم — لكل جزء

  • صف القائمة: «الكمية 150 · القيمة 12,000» — أرقام فيزيائية صامتة عن الملكية.
  • الفلاتر: بحث/مخزن/تصنيف — لا مالك، لا إخفاء أصفار.
  • الإحصاءات: قيمة فيزيائية لصفحة واحدة؛ لا أثر للأمانة.
  • أقرب انتهاء: تاريخ + شارة حمراء/كهرمانية بلا مالك، من مصادر ما قبل ٤.
  • التوسعة: مجموعات ملّاك سليمة؛ بلا قيم لوط، تذييل كميات فقط، خطأ = فراغ.
  • الجرد: غير قابل للوصول من هنا.
  • الحالات: فارغ عام؛ خطأ قائمة يظهر «لا بيانات».

🟢 المستهدف — لكل جزء

  • صف القائمة: «الرصيد 105 +45 🤝 · القيمة (ملكنا) 8,400» — الفيزيائي في التلميحة والتوسعة (§٥).
  • الفلاتر: + عدسة الملكية (الكل الفعلي · ملكنا · 🤝 عميل) بشريط بنفسجي وURL (§٦) + «إخفاء الأصفار».
  • الإحصاءات: «قيمة مخزونك (ملكنا)» من meta الخادم لكل النطاق + بطاقة أمانة بنفسجية تظهر فقط عند وجودها (§٧).
  • أقرب انتهاء: من سجل اللوطات الحي، مع 🤝 حين يكون اللوط الأقرب لعميل (§٩).
  • التوسعة: + عمود قيمة (تكلفة/معلنة ⓘ)، تذييل بقيم وجملة الحسم، خطأ بزر إعادة، كاش يُبطل بالتحديث (§٨).
  • الجرد: زر بالصف يفتح جردًا مهيَّأً بالمنتج/المخزن (§١١).
  • الحالات: فارغ حقيقي ≠ فارغ بفلاتر ≠ خطأ — ثلاث حالات مسماة (§١٠).
⚖️
مبدأ حاكم

نفس قوانين §10.1 الأربعة تُطبَّق الآن على الطابق الأعلى: الحقيقتان (فيزيائي/ملكنا) دائمًا ظاهرتان ولا تندمجان — الرقم الافتراضي في كل عمود قيمة هو «ملكنا» والفيزيائي بجانبه لا بداخله؛ البنفسجي للملكية فقط؛ لا رقم في الشاشة يساوي owned + declared أبدًا.

٥ المستهدف: صف القائمة المركّب

أهم تغيير في المراجعة كلها — وكل بياناته على السلك بالفعل. عمودا «الكمية/المتاح» يُستبدلان بعمود «الرصيد» المركّب + يتحوّل عمود القيمة إلى «القيمة (ملكنا)».

المستهدف — القائمة: صف ملكنا-فقط (لا ضريبة بصرية) + صف مختلط الملكية + صف أمانة-فقط
الكودالاسمالمخزن الرصيدالمتاح (ملكنا)متوسط التكلفةالقيمة (ملكنا) ⓘأقرب انتهاء
SLK-102حرير طبيعي 22Dالمخزن الرئيسي 62.00 62.00 210.00 13,020.00 📊 🧮
YRN-040غزل قطن 40/1المخزن الرئيسي 105.00 +45 🤝 105.00 80.00 8,400.00 2026-08-20 قارب 🤝 📊 🗓 🧮
PET-771خيوط بوليستر 150Dمخزن الأمانة 0.00 +120 🤝 0.00 0.00 2026-07-25 منتهٍ قريبًا 🤝 📊 🗓 🧮
تلميحة خلية «الرصيد» المختلطة عند المرور: «الفيزيائي: 150 — ملكنا 105 + أمانة 45 (ليست ملكك)» · تلميحة عمود القيمة: «قيمة ملكيتك فقط — لا تشمل بضاعة الأمانة (معلنة 3,600)».

٥.١ قواعد الخلية المركّبة (المواصفة)

٦ المستهدف: عدسة الملكية + الشريط البنفسجي

نفس النمط المشحون في تقرير اللوطات المنتهية (4.4-FE P2) — نفس المفاتيح، نفس الشريط، نفس عقد owner: لا تخترع لغة ثانية.

المستهدف — شريط الفلاتر مع العدسة (الحالة: عدسة عميل مفعّلة)
🔍 بحث… كل المخازن التصنيف المالك: 🤝 شركة النور ☐ إخفاء الأصفار ✕ مسح الفلاتر
🤝 تعرض بضاعة «شركة النور» (أمانة) — ليست أرصدتك الدفترية
الكودالاسمالمخزنرصيد «شركة النور»القيمة المعلنة ⓘأقرب انتهاء
YRN-040غزل قطن 40/1المخزن الرئيسي 30.00 2,100.00 2026-08-20 قارب 📊 🧮
PET-771خيوط بوليستر 150Dمخزن الأمانة 120.00 6,000.00 2026-07-25 منتهٍ قريبًا 📊 🧮
لاحظ: تحت عدسة عميل تُعاد تسمية الأعمدة («رصيد شركة النور» · «القيمة المعلنة») ولا يظهر عمود «القيمة (ملكنا)» ولا بطاقات إحصاء الملكية — بيان عهدة قابل للمشاركة عبر URL.

٦.١ المواصفة

٧ المستهدف: بطاقات إحصاءات صادقة النطاق والملكية

قاعدتا الصدق: (أ) كل رقم يعلن نطاقه — إجمالي النطاق المفلتر كله من الخادم، لا مجموع صفحة؛ (ب) رقم القيمة الافتراضي «ملكنا»، والأمانة بطاقة بنفسجية منفصلة تظهر فقط عند وجودها.

المستهدف — صف البطاقات (عدسة «الكل الفعلي»، يوجد أمانة)
128
إجمالي المنتجات
184,250.00
قيمة مخزونك (ملكنا)
لا تشمل بضائع الأمانة
3,900.00
🤝 أمانة لدينا (معلنة)
165 وحدة · عميلان · ليست ملكك
7
مخزون منخفض
3
مخزون صفري
بلا أمانة في النطاق: البطاقة البنفسجية لا تُرسم أصلًا (نفس انضباط شارة «مستحق الإرجاع») — أربع بطاقات كما اليوم. ولا يوجد في الصف أي رقم = owned + declared.

٧.١ المواصفة

٨ المستهدف: توسعة اللوطات المصقولة (المشحون + ٦ لمسات)

البنية المشحونة صحيحة — نضيف: عمود القيمة، تذييلًا ماليًّا كاملًا بجملة الحسم، كاشف التصادم ⚠، «إظهار المستنفَد»، خطأً بإعادة محاولة، وأزرار فعل لكل مجموعة.

المستهدف — الصف موسّعًا: ملكنا + عميلان + غير مخصّص + تذييل التطابق الكامل
الكودالاسمالمخزنالرصيدالقيمة (ملكنا)أقرب انتهاء
YRN-040غزل قطن 40/1المخزن الرئيسي 105.00 +45 🤝 8,400.00 2026-08-20 قارب 🤝 📊 🗓 🧮
ملكنا105.00 · قيمة 8,400.00
#رقم الدفعةالانتهاءمستلَممصروفالمتبقيالقيمةالحالة
1B-2201 2026-09-018020604,800.00
2B-22882027-02-115013372,960.00
غير مخصّص (ما قبل اللوطات) قديم8640.00جرد يمسحه ←
🤝 شركة النور أمانة — ليست ملكك 30.00 · معلنة ⓘ 2,100.00 ↩ رد للعميل
#رقم الدفعةالانتهاءمستلَممصروفالمتبقيمعلنة ⓘالحالة
1B-70102026-08-204010302,100.00قارب — أبلغ العميل
🤝 مصنع الأمل أمانة — ليست ملكك 15.00 · معلنة ⓘ 900.00 ↩ رد للعميل
#رقم الدفعةالانتهاءمستلَممصروفالمتبقيمعلنة ⓘالحالة
1B-2201 2027-03-1515015900.00
الإجمالي الفيزيائي: 150.00 ✓ يطابق الرصيد · إظهار المستنفَد (3) ▾
ملكنا (دفتري): 105.00 — قيمة 8,400.00 · أمانة (ليست ملكك): 45.00 — معلنة 3,000.00
التقييم والتكلفة يستخدمان ملكنا (105) فقط.
المستهدف — تذييل التطابق في حالة عدم التطابق (اليوم: أيقونة ⚠ صامتة)
الإجمالي الفيزيائي: 150.00 ⚠ اللوطات تفسّر 153.00 — فرق −3.00
اللوطات تدّعي أكثر من الرصيد الفعلي — الغالب صرف لم يُخصم من لوطه. ابدأ جردًا لهذا الصنف ←

٨.١ اللمسات الست (المواصفة)

  1. عمود «القيمة»: ملكنا = on_hand × unit_cost؛ العميل = on_hand × declared_unit_cost برأس «معلنة ⓘ» وتلميحة INVENTORY.VAL_DECLARED_HINT الموجودة. البيانات تصل اليوم وتُهمل (P9).
  2. التذييل الكامل: سطر ٢ بالقيم + سطر ٣ «التقييم والتكلفة يستخدمان ملكنا فقط» — الجملة التي كانت في تصميم §10.2 وسقطت في التنفيذ. حالة عدم التطابق تنطق بالفرق الرقمي وتصف السبب الأرجح وتربط بالجرد (الموك الثاني أعلاه).
  3. كاشف التصادم ⚠ (P14): client-side بحت — نفس batch_number في أكثر من مجموعة مالك ⇒ ⚠ كهرمانية بتلميحة «نفس الدفعة عند مالك آخر — افصلها في الجرد». يقي كارثة الجرد الصامتة.
  4. «إظهار المستنفَد (n)»: رابط في التذييل يعيد النداء بـinclude_depleted=1 (§١٢)؛ الصفوف المستنفدة رمادية بلا حالة.
  5. زر «↩ رد للعميل» على رأس كل مجموعة عميل — ينقل إلى dialog الـCRT المشحون (4.4 P5) مهيَّأً بالعميل/المنتج/المخزن، خلف صلاحية production.consignment.return. مراسم العبور تبقى صريحة (القانون ٣) — الزر بوابة للمراسم لا اختصار يتجاوزها.
  6. وسم «قديم» بدل LEGACY: مفتاح i18n (INVENTORY.LEGACY_TAG: «قديم» / «Legacy») بدل النص الثابت، مع نص «جرد يمسحه ←» رابطًا إلى §١١.

٩ الصلاحية عبر الملكية — خسارتك أم مشكلة عميلك

اللون يبقى للصلاحية (أحمر/كهرماني) — الملكية تغيّر النداء لا اللون. نفس مبدأ شِقّي «منتهي» في تقرير 4.4 (خسارة محتملة ↔ ليست خسارتك) يصل الآن إلى القائمة والتوسعة.

لوطك أنت — منتهٍ / قارب

منتهٍ 2026-05-01

تلميحة: «خسارة محتملة — راجِع للإعدام أو تسوية جرد». الإجراء: تقرير الانتهاء / جرد. صف القائمة يلوَّن كما اليوم.

لوط عميل — منتهٍ / قارب

منتهٍ 🤝 شركة النور

تلميحة: «بضاعة العميل — ليست خسارتك: أبلغ العميل أو أرجِعها». الإجراء: «↩ رد للعميل» (§٨.٥) أو تقرير الانتهاء بعدسة العميل (tab=3&owner=88 — يعمل اليوم).

٩.١ شارة «أقرب انتهاء» في القائمة تكتسب نسبة مالك

المستهدف — ثلاث حالات لخلية أقرب انتهاء
أقرب لوط لك أنت: 2026-08-01 قارب على الانتهاء — كما اليوم تمامًا
أقرب لوط لعميل: 2026-07-25 منتهي الصلاحية 🤝 — تلميحة: «الأقرب انتهاءً لوط أمانة لـ«شركة النور» — ليست خسارتك؛ أقرب لوط لك: 2026-11-02»
تقديري: ~2026-08-20 قارب — وسم ~ كما اليوم، والخلية كلها dir="ltr" (إصلاح P11)

١٠ حالات فارغ / تحميل / خطأ — ثلاث حقائق لا تتنكر إحداها بثوب الأخرى

اليوم: «لا توجد بيانات» تُعرض عند الفراغ الحقيقي، وعند فراغ الفلاتر، وعند فشل التحميل — ثلاث حقائق مختلفة بجملة واحدة. وخطأ اللوطات لوحة بكماء.

أ) فارغ حقيقي (لا فلاتر)

📦لا أرصدة مخزون بعدابدأ باستلام مخزون أو رصيد افتتاحي+ استلام مخزون

ب) فارغ بفلاتر

🔍لا نتائج تطابق الفلاتر«غزل» · مخزن الأمانة · 🤝 شركة النور✕ مسح الفلاتر

ج) خطأ تحميل القائمة

⚠ تعذّر تحميل الأرصدة — تحقق من الاتصال ⟳ إعادة المحاولة

داخل بطاقة الجدول بدل صف «لا بيانات» — الـToast وحده يختفي ويترك فراغًا كاذبًا.

د) خطأ تحميل اللوطات (داخل التوسعة)

⚠ تعذّر تحميل اللوطات ⟳ إعادة

يتطلب حالة lotsError ثالثة بجانب cache/loading — «لا بيانات» ممنوعة عند الفشل (P6).

١٠.١ التحميل

١١ مدخل الجرد (الجرد) من الشاشة — إغلاق حلقة «أكّد بجرد»

الشاشة تقول اليوم «تقديري — أكّد بجرد» و«يوجد فرق» ثم تصمت. المستهدف: كل تشخيص يعرض علاجه بضغطة.

المستهدف — ثلاث بوابات إلى الجرد
١. زر بالصف (أيقونة 🧮 بجانب 📊): «ابدأ جردًا لهذا الصنف» ← /core/inventory-counts?new=1&warehouse_id=13&product_id=17159
٢. رابط التذييل غير المتطابق (§٨ الموك الثاني): «ابدأ جردًا لهذا الصنف ←» — نفس الوجهة، يظهر فقط عند reconciled=false.
٣. رابط صف «غير مخصّص»: «جرد يمسحه ←» — جرد اللوطات هو ما يحوّل التقديري ~ إلى مؤكد ويمتص القديم.

١٢ عقد الـBE المطلوب — دلتا صغيرة ومحددة

كل بنود الواجهة أعلاه تعمل بالمشحون الحالي ما عدا هذه الخمسة. النمط كسابقتها: الـFE يكتب دفاعيًّا (??) فلا يفرغ الصفحة أمام BE أقدم.

#التغييرالعقديخدم
D1باراميتر owner على index GET stock-balances?owner=88 — غائب = الكل · 0 = صفوف ملكنا فيها > 0 · id = صفوف لدى العميل لوطات حية فيها (join على inventory_lot_balances). تحت عدسة عميل تُضاف لكل صف owner_quantity/owner_declared_value. ⚠ نفس فخ 4.4: if ($owner) تُسقط owner=0 — اختبر بـ!== null. §٦ (B3)
D2meta.totals للنطاق المفلتر meta: { totals: { owned_value, consignment_declared_value, consignment_quantity, consignment_partners, low_stock, zero_stock } } — استعلام تجميعي واحد على نفس شروط الفلترة قبل الـpaginate. §٧ (B6)
D3lot-owners خفيف GET stock-balances/lot-owners[{ partner_id, partner_name }] للملّاك ذوي لوطات حية (distinct، company-scoped). يغذي p-select العدسة. §٦ (B3)
D4مصدر أقرب-انتهاء = سجل اللوطات + مالك buildNearestExpiryMap: المصدر الأول inventory_lot_balances الحية (batch)، السيريالات للتسلسلي، الأنساب fallback للقديم. الحقول الجديدة: nearest_expiry_owner_id/_name + nearest_own_expiry. وفي نفس الملف: قصّ portionMap على منتجات الصفحة (P15). §٩ (B5)
D5productLots: متغيّر + مستنفَد ?product_variant_id=&include_depleted=1 — الفلترة بالمتغيّر تُصلح خلط اللوحة (P2)، وphysical_total يُحسب بنفس نطاق المتغيّر. المستنفَد يعود بوسم depleted: true. §٥/§٨ (B2, B8)
📏
انضباط الإصدار

لا إعدادات ولا صلاحيات جديدة في هذه الدلتا (نعيد استخدام inventory.stock.view وinventory.counts وproduction.consignment.*) ⇒ لا تغيير على seeders الـMoonStack. أي انحراف عن ذلك يستدعي قاعدة الـtracker: seeder التعريف + GrantRolePermissionsSeeder.

١٣ مفاتيح i18n + قواعد RTL + مصطلحات PrimeNG

١٣.١ مفاتيح جديدة (ar هو المواصفة — تُشحن حرفيًّا، en معها في نفس الـcommit)

المفتاح (تحت INVENTORY)aren
BAL_ON_HANDالرصيدOn hand
BAL_OWNED_VALUEالقيمة (ملكنا)Value (own)
BAL_OWNED_VALUE_HINTقيمة ملكيتك فقط — لا تشمل بضاعة الأمانة (معلنة {{declared}})Your owned value only — excludes consignment (declared {{declared}})
BAL_MIXED_HINTالفيزيائي: {{physical}} — ملكنا {{owned}} + أمانة {{consignment}} (ليست ملكك)Physical: {{physical}} — own {{owned}} + consignment {{consignment}} (not yours)
BAL_AVAILABLE_OWNالمتاح (ملكنا)Available (own)
STAT_OWNED_VALUEقيمة مخزونك (ملكنا)Your stock value (own)
STAT_OWNED_NOTEلا تشمل بضائع الأمانةExcludes consignment goods
STAT_CONSIGN🤝 أمانة لدينا (معلنة)🤝 Consignment held (declared)
STAT_CONSIGN_NOTE{{qty}} وحدة · {{partners}} عميل · ليست ملكك{{qty}} units · {{partners}} customers · not yours
OWNER_BALANCE_COLرصيد «{{name}}»“{{name}}” balance
HIDE_ZEROإخفاء الأصفارHide zeros
LEGACY_TAGقديمLegacy
LOT_VALUEالقيمةValue
RECON_EXPLAINSاللوطات تفسّر {{sum}} — فرق {{diff}}Lots explain {{sum}} — difference {{diff}}
RECON_OVERCLAIM_HINTاللوطات تدّعي أكثر من الرصيد الفعلي — الغالب صرف لم يُخصم من لوطهLots claim more than the actual balance — likely an issue not drawn from its lot
RECON_COSTING_NOTEالتقييم والتكلفة يستخدمان ملكنا ({{qty}}) فقط.Valuation & costing use own ({{qty}}) only.
SHOW_DEPLETEDإظهار المستنفَد ({{n}})Show depleted ({{n}})
BATCH_COLLISION_HINTنفس الدفعة عند مالك آخر — افصلها في الجردSame batch under another owner — separate it in the count
NEAREST_IS_CONSIGN_HINTالأقرب انتهاءً لوط أمانة لـ«{{name}}» — ليست خسارتك؛ أقرب لوط لك: {{own}}Nearest expiry is “{{name}}” consignment — not your loss; your nearest: {{own}}
LOTS_FETCH_ERRORتعذّر تحميل اللوطاتFailed to load lots
NOT_BATCH_TRACKEDهذا المنتج بلا تتبع دفعات — الرصيد كله «ملكنا» بلا لوطاتNot batch-tracked — the whole balance is own, no lots
COUNT_THIS_PRODUCTابدأ جردًا لهذا الصنفStart a count for this item
EMPTY_NO_BALANCESلا أرصدة مخزون بعدNo stock balances yet
EMPTY_NO_MATCHلا نتائج تطابق الفلاترNo results match the filters
LOAD_ERRORتعذّر تحميل الأرصدةFailed to load balances

مفاتيح تُعاد استخدامها كما هي: OWNER · OWNER_ALL · OWNER_OWN · OWNER_LENS_BANNER · VAL_DECLARED_HINT · DECLARED · CONSIGNMENT_NOT_YOURS · LOT_EXPIRED/LOT_NEAR_EXPIRY · UNASSIGNED_LEGACY · EXPIRED_CONSIGN_SUB («ليست خسارتك — أبلغ العميل / أرجِعها»).

١٣.٢ قواعد RTL (قائمة فحص المراجعة)

١٣.٣ مصطلحات PrimeNG v21

١٤ خطة البناء — مرتّبة بالقيمة، قابلة للشحن خطوة خطوة

كل خطوة تُشحن مستقلة وتترك الشاشة أفضل. B1 وB2 لا تنتظران أي BE (عدا باراميتر المتغيّر الصغير في B2).

B1
جهد: صغير · FE فقط

حاجز الملكية على القائمة ⭐ (P1)

أضِف owned_*/consignment_* إلى واجهة StockBalance · خلية الرصيد المركّبة 105 +45 🤝 · عمود «القيمة (ملكنا)» = owned_value بتلميحة الفيزيائي · «المتاح (ملكنا)» · إصلاح تلوين الصف الصفري/أمانة-فقط · إعادة تسمية بطاقة القيمة إلى «قيمة الصفحة (ملكنا)» بجمع owned_value (الصدق الفوري قبل D2). هذا وحده يغلق التسريب التأسيسي على الشاشة.

B2
جهد: صغير · FE + D5

إصلاح تصادم المتغيّرات (P2)

rowKey يشمل product_variant_id · getProductLots يمرّر المتغيّر · الـBE يفلتر به ويحسب physical_total بنفس النطاق. اختبار القبول: منتج بمتغيّرين في مخزن واحد — توسعتان مستقلتان وتطابق صحيح لكلٍّ.

B3
جهد: متوسط · FE + D1 + D3

عدسة الملكية + الشريط + URL (P3)

p-select (الكل الفعلي · ملكنا · 🤝 عملاء من lot-owners) · شريط OWNER_LENS_BANNER · إعادة تسمية الأعمدة تحت عدسة عميل · مزامنة ?owner= · تمرير العدسة لأزرار تقرير الانتهاء · «إخفاء الأصفار».

B4
جهد: صغير · FE فقط

صلابة التوسعة والحالات (P6 · P7 · P8 · P10 · P11 جزئيًّا)

حالة lotsError + «⟳ إعادة» · إبطال الكاش عند ⟳/الفلاتر · تذييل عدم التطابق الناطق بالفرق + رابط الجرد · وسم «قديم» i18n · سهم التوسعة يتحوّل لرسالة NOT_BATCH_TRACKED للمنتجات غير المتتبَّعة (بيانات tracking_type على الصف إن وُجدت، وإلا رسالة التوسعة المحسّنة) · حالات فارغ/خطأ الثلاث للقائمة · dir="ltr" على أعمدة الجدول الرئيسي.

B5
جهد: متوسط · BE أساسًا (D4)

أقرب انتهاء من سجل اللوطات + نسبة المالك (P5 · P15)

مصدر الخريطة = اللوطات الحية أولًا · حقول المالك وnearest_own_expiry · شارة 🤝 وتلميحة «ليست خسارتك» في القائمة · قصّ portionMap على الصفحة.

B6
جهد: صغير · FE + D2

بطاقات إحصاء بنطاق كامل + بطاقة الأمانة (P4)

meta.totals يغذي: قيمة مخزونك (ملكنا) · 🤝 أمانة لدينا (تظهر عند > 0، تنقر فتفعّل العدسة) · منخفض/صفري على النطاق كله · بطاقات عهدة بديلة تحت عدسة عميل.

B7
جهد: صغير · FE + لمسة على شاشة الجرد

مدخل الجرد (P12)

زر 🧮 بالصف + رابطا التذييل/غير المخصّص ← /core/inventory-counts?new=1&warehouse_id&product_id · دعم queryParams في شاشة الجرد.

B8
جهد: متوسط · FE + جزء من D5

صقل التوسعة والجدول (P9 · P13 · P14)

عمود قيمة اللوط (تكلفة/معلنة ⓘ) · «إظهار المستنفَد» · كاشف التصادم ⚠ · زر «↩ رد للعميل» على مجموعة العميل · فرز أعمدة lazy · ترحيل البنفسجي للتوكنات · (اختياري) تصدير.

🏁
تعريف الاكتمال للشاشة كلها

مالك مخزن عنده لوطاته + بضاعة عميلين على نفس الرفوف يفتح الشاشة فيقرأ — دون توسيع أي صف — كم يملك وكم يستضيف وقيمة كلٍّ منهما منفصلة؛ يفلتر بعميل فيحصل على بيان عهدة برابط قابل للمشاركة؛ يرى انتهاءً أحمر فيعرف فورًا أهو خسارته أم بلاغ لعميله؛ وحيثما قالت الشاشة «تقديري» أو «فرق» وجد زر الجرد بجانب الكلمة. ولا يوجد على الشاشة — ولا في تلميحة — رقم واحد يساوي owned + declared.

مراجعة Fable · Moon ERP — شاشة أرصدة المخزون (لوطات + أمانة) · 2026-07-11 · المصادر: الكود المشحون على hazemdev2 + phase4-per-lot-fefo.html §10 + phase4.4-fe-build-spec.md + phase4-execution-tracker.md