وحدات القياس عبر البرنامج — تحليل الفجوة وكشف المشاكل
Units of Measure across Moon ERP — Gap Analysis & Problem Report
النطاق: كل الموديولاتالخطورة: عالية — [FIN] مخزون/تكلفةالحالة: موجود كبيانات · غير مُستخدَم فعليًاالتاريخ: 2026-07-15
1 المشكلة
طلبت مرجعية واضحة لوحدات القياس: منتج له وحدة جرام / كيلو / طن — إزاي تتعمل في البرنامج؟ موجودة فعلًا ولا ناقصة؟ وفين بالظبط بتُستخدم الوحدة في كل البرنامج، وهل بتُستخدم صح؟ مع تحليل تفصيلي وكشف المشاكل.
الخلاصة في سطر: نظام الوحدات موجود بالكامل كبنية بيانات وشاشات إدخال (فئات + وحدات بمعامل تحويل + وحدات متعددة للصنف الواحد بباركود وسعر لكل وحدة)، لكن القطعة اللي بتخلّيه «يشتغل» — التحويل الفعلي — مش موجودة نهائيًا. كل المعاملات بتخزّن الوحدة بس بتتعامل مع الكمية كأنها بوحدة الأساس، والرصيد نفسه مالوش عمود وحدة. النتيجة: لو اخترت وحدة غير وحدة الأساس (كرتونة/طن) → الرصيد والتكلفة والتسعير كلها تطلع غلط.
2 الوضع الحالي (ما هو موجود) — مُتتبَّع بالكود
أ) محرك الوحدات في Core — بنية بيانات ممتازة
العنصر
الوصف
المرجع
فئات الوحداتunit_groups
مجموعة (وزن، عدد، حجم…) باسم عربي/إنجليزي
Core/…/create_unit_groups_table.php
الوحداتunits
اسم + رمز + conversion_factor(15,6) + is_base + الفئة
Core/…/create_units_table.php
وحدات الصنفproduct_units
لكل منتج: وحدة + معامل تحويل + باركود + سعر شراء/بيع + is_purchase/is_sale
Core/…/create_product_units_table.php
وحدة أساس الصنف
products.base_unit_id (+ وراثة default_unit_id من فئة المنتج)
Product.php · CreateProduct.php:44-49
شاشات الإدخال (FE)
شاشة الوحدات + شاشة الفئات + قسم «وحدات بديلة» في فورم المنتج — كلها تشتغل للإدخال
إجابة «هل موجودة؟»: نعم — بنية «جرام/كيلو/طن للصنف الواحد» مبنية بالكامل، والمستخدم يقدر يدخّلها اليوم من الواجهة.
ب) الوحدة على سطور المستندات — مُلتقَطة
كل جداول سطور المستندات فيها عمود unit_id (مرتبط بجدول units): المبيعات (عرض سعر/أمر/فاتورة/إذن تسليم/مرتجع)، المشتريات (طلب/أمر/GRN/فاتورة/مرتجع)، حركات المخزون (استلام/صرف/تحويل/جرد/تسوية)، والإنتاج (مكوّنات BOM + مواد + ناتج). فالواجهة بتسمح للمستخدم يختار وحدة على السطر.
3 المطلوب
مرجعية وحدات قياس شغّالة فعليًا: صنف بوحدات متعددة (جرام/كيلو/طن) بمعامل تحويل بينها.
لما أتعامل بأي وحدة على أي مستند → البرنامج يحوّلها لوحدة الأساس عشان الرصيد والتكلفة والتسعير يطلعوا صح.
تحديد كل الأماكن اللي بتستخدم الوحدة + كشف كل المشاكل فيها.
4 الفجوة (GAP) — الموجود مقابل المطلوب
الجانب
الموجود اليوم
المطلوب
الحالة
بنية الوحدات + وحدات الصنف
كاملة (فئات/وحدات/معامل تحويل/باركود/سعر لكل وحدة)
نفسه
موجود
دالة التحويل (تحويل كمية بالمعامل)
غير موجودة إطلاقًا — conversion_factor بيانات ميتة
دالة/خدمة تحويل مشتركة
ناقص
وحدة على الرصيد + حركات المخزون
لا يوجد عمود وحدة على inventory_stock_balances ولا inventory_movements
الرصيد دائمًا بوحدة الأساس + عرض الوحدة
ناقص
خصم/إضافة المخزون
بالكمية الخام بلا تحويل (StockService بلا بارامتر وحدة)
تحويل الكمية لوحدة الأساس قبل تحريك الرصيد
غلط
التسعير بالوحدة
دائمًا سعر الأساس — product_units.sale_price/purchase_priceلا يُقرأ
سعر الوحدة المختارة
غلط
باركود الوحدة في الكاشير
POS يقرأ products.barcode فقط — يتجاهل باركود الوحدة
مسح باركود الكرتونة/العبوة
ناقص
قائمة الوحدات في السطر (FE)
تعرض كل وحدات الشركة مش وحدات الصنف؛ ومفيش معالج تغيير/تحويل
وحدات الصنف فقط + إعادة تسعير/تحويل عند التغيير
غلط
فحص التوافر / FIFO / الحجز / MRP
يقارن كمية بوحدة السطر مع رصيد بوحدة الأساس؛ طبقات التكلفة والحجز بلا وحدة
الكل بوحدة الأساس
غلط
التحقق (Validation)
conversion_factor يقبل 0؛ لا «أساس واحد لكل فئة»؛ وحدة الأساس nullable
معامل > 0 + أساس واحد + توافق الفئة
ضعيف
البيانات الافتراضية (Seeding)
شركة جديدة = صفر وحدات (يبنيها المستخدم يدويًا)
وحدات افتراضية جاهزة
ناقص
وحدات المعمل (LIS)
lab_units نظام منفصل تمامًا بلا تحويل — ازدواجية
(توحيد مستقبلي — خارج النطاق)
ملاحظة
المشكلة الجوهرية (بالأدلة): الرصيد الحالي مُخزّن في inventory_stock_balances.quantityبلا وحدة، وكل مَن يكتب فيه (StockService) بيثق إن المُدخَل بوحدة الأساس — وكل المُنادِين بيمرّروا كمية السطر الخام بالوحدة اللي اختارها المستخدم بلا ضرب في معامل التحويل. يعني الرصيد صحيح «بالصدفة» فقط لو المستخدم صادف وأدخل بوحدة الأساس. لو أدخلت استلام بـ«كرتونة» (معامل 12) → الرصيد يزيد 12 مش 144.
TransactionLineItemsComponent + descriptors — عمود الوحدة على السطر (اليوم يعرض كل الوحدات، مقفول في doc-config).
sales/* · purchases/* · POS — معالج تغيير الوحدة (تحويل + تسعير).
features/*/stock-balances · reports — عرض رمز الوحدة.
i18n: ar.json/en.json مفاتيح UNIT.*.
الاعتماديّة الحرجة [FIN]: أي تحويل بيمسّ كمية المخزون → يمسّ التكلفة (FIFO/المتوسط) وقيمة المخزون وقيود WIP وربحية المبيعات. لازم يتبني خلف إعداد افتراضي = سلوك اليوم بالحرف، ويتراجع عليه Codex + استشاري مالي.
6 الحالات الحدّية
الحالة
المعالجة المقترحة
وحدة مختارة مش ضمن وحدات الصنف (لا يوجد معامل)
نقصر قائمة السطر على وحدات الصنف + الأساس؛ ولو وحدة مجهولة → معامل=1 مع تحذير/منع.
معامل تحويل = 0 أو سالب
تحقق > 0 على مستوى الطلب + قيد DB.
الصنف بلا وحدة أساس (base_unit_id فارغ)
نعامل الكمية كوحدة أساس ضمنيًا (سلوك اليوم) لو الإعداد OFF؛ ولو ON نطلب وحدة أساس.
أكثر من وحدة أساس في نفس الفئة
تحقق «أساس واحد لكل فئة».
كسور بعد التحويل (144.000000)
الرصيد decimal(15,3) — نثبّت سياسة تقريب واضحة على وحدة الأساس.
مرتجع/إلغاء لمستند اتعمل بوحدة غير الأساس
نفس معامل التحويل المخزَّن وقت السطر يُعكَس (نثبّت المعامل على السطر لتفادي انجراف لو اتغيّر لاحقًا).
تحويل بين مخازن بوحدات مختلفة
الطرفان بوحدة الأساس داخليًا → لا مشكلة بعد التوحيد.
بيانات قديمة أُدخلت بوحدة غير الأساس قبل الإصلاح
قرار مالك: تصحيح بأثر رجعي أم إيقاف الجديد فقط (على moonui2 كله تجريبي).
7 خطة التنفيذ (WPs) — مرحلية، خلف إعداد
المبدأ: إعداد inventory.enable_unit_conversion افتراضي OFF = سلوك اليوم بايت-بايت (صفر انحدار). التفعيل اختياري للشركة اللي محتاجة وحدات متعددة.
WP
النطاق
Repo
[FIN]
Migration
WP1 — محرك التحويل + نقطة واحدة
خدمة UnitConversionService::toBase(product, unitId, qty) تقرأ product_units.conversion_factor؛ تُستدعى عند كل حدّ مستند→مخزون فيحُوّل لوحدة الأساس قبل StockService؛ تثبيت المعامل على السطر.
BE
✅
محتمل (عمود معامل على السطر)
WP2 — تصحيح فحص التوافر/FIFO/الحجز/MRP
كلها تعمل بوحدة الأساس بعد WP1 (إزالة المقارنة عبر وحدات مختلفة).
BE
✅
—
WP3 — واجهة السطر
قصر قائمة الوحدات على وحدات الصنف؛ معالج تغيير الوحدة (تحويل الكمية المعروضة + إعادة التسعير)؛ فكّ قفل عمود الوحدة بشكل مضبوط.
FE
—
—
WP4 — التسعير بالوحدة + باركود الكاشير
قراءة product_units.sale_price/purchase_price؛ POS يمسح باركود الوحدة ويحمّل وحدات الصنف.
BE+FE
✅
—
WP5 — العرض + Seeding + التحقق
عرض رمز الوحدة على الرصيد/الحركات/التقارير/الطباعة؛ Seeder وحدات افتراضية؛ تشديد التحقق (معامل>0، أساس واحد، توافق الفئة)؛ إضافة destroy لوحدة الصنف.
BE+FE
—
محتمل
مؤجَّل صراحةً: توحيد lab_units مع Core (ازدواجية تاريخية) · الرصيد لكل تشغيلة×وحدة (per-lot) — مبادرات منفصلة.
8 قرارات تحتاج المالك
ق1 — مستوى الطموح: نبني المحرك الكامل الشغّال (تحويل فعلي عبر كل المستندات) مرحليًا؟ أم نكتفي مؤقتًا بـإيقاف/إخفاء اختيار الوحدات غير الأساس لحد ما نبني المحرك (يمنع البيانات الغلط بأقل تكلفة)؟ التوصية: المحرك الكامل مرحليًا (WP1→WP5) خلف إعداد.
ق2 — التفعيل الافتراضي: إعداد enable_unit_conversion افتراضي OFF (سلوك اليوم بالحرف) وتفعّله الشركة المحتاجة؟ التوصية: نعم — OFF افتراضيًا، صفر انحدار.
ق3 — التسعير عند اختيار وحدة أكبر: السعر ييجي من سعر الوحدة المُدخَل (product_units.sale_price)؟ أم سعر الأساس × معامل التحويل (محسوب تلقائيًا)؟ التوصية: سعر الوحدة المُدخَل إن وُجد، وإلا سعر الأساس×المعامل كاحتياطي.
ق4 — وحدة تخزين الرصيد: نثبّت إن الرصيد يُخزَّن دائمًا بوحدة الأساس (المعياري عالميًا)، والعرض يحوّل للوحدة المطلوبة؟ التوصية: نعم — الرصيد بوحدة الأساس؛ العرض فقط يحوّل.
ق5 — البيانات القائمة: فيه أصناف متعددة الوحدات أُدخلت فعليًا واتعملها معاملات بوحدة غير الأساس (تحتاج تصحيح بأثر رجعي)؟ أم نكتفي بمنع الغلط الجديد؟ التوصية: على moonui2 كله تجريبي → لا تصحيح؛ للعملاء نوفّر أمر فحص/تصحيح لو لزم.
9 معاينة الواجهة المتوقّعة
التغيير الأبرز على الواجهة = عمود الوحدة على سطر المستند: النهارده يعرض كل وحدات الشركة بلا أي تحويل؛ بعد التنفيذ يعرض وحدات الصنف فقط، وعند تغيير الوحدة يحوّل الكمية ويعيد التسعير تلقائيًا.
قبل — سطر فاتورة اليوم (الوحدة ديكور فقط)
فاتورة بيع — سطر الأصناف
الصنف
الكمية
الوحدة
السعر
الإجمالي
سكر ناعم
2
كرتونة ▾ (كل وحدات الشركة)
25.000
50.000
⚠️ يخصم من المخزون 2 فقط (مش 2×12)، والسعر سعر الأساس — الرصيد والربح غلط.
بعد — سطر فاتورة بعد التنفيذ (تحويل + تسعير)
فاتورة بيع — سطر الأصناف
الصنف
الكمية
الوحدة
السعر/وحدة
الإجمالي
يُخصَم من المخزون
سكر ناعم
2
كرتونة ▾(كيلو · كرتونة)
300.000
600.000
24 كيلو(2×12)
✅ الوحدة من وحدات الصنف فقط · الكمية تتحوّل لوحدة الأساس (كيلو) للمخزون والتكلفة · السعر من سعر الكرتونة.
شاشة أرصدة المخزون — عرض الوحدة (WP5)
أرصدة المخزون
الصنف
المخزن
الرصيد
الوحدة (الأساس)
سكر ناعم
الرئيسي
1,240.000
كيلو
الرصيد دائمًا بوحدة الأساس؛ الرمز يظهر بجانب الكمية.
تقرير مرحلة أولى — لا يُكتب أي كود قبل موافقتك على النطاق أعلاه. بعد الموافقة تُنفَّذ WP1→WP5 بالترتيب مع مراجعة مستقلة واختبارات لكل حزمة، والإعداد OFF يضمن عدم تأثّر أي عميل حالي.