تحليل شامل لموديول التصنيع في برنامج حسابات moonui: نقاط القوة، الباجز، الأمان، الأداء، والنواقص — بأدلة من الكود (file:line) وخطة عمل مرتّبة بالأولوية.
ده نظام MRP-II حقيقي ومبني باحتراف: دورة كاملة (MRP ← أمر إنتاج ← صرف خامات ← تأكيد ← استلام ← تكلفة ← قيود GL)، آلة MRP زمنية فعلية بـ BOM explosion و pegging، تصنيع بالأمر للغير (toll) شغّال، وتتبّع تشغيلات (genealogy). صفر TODO/FIXME و18 من 19 شاشة موصولة بالـ backend.
DB::transaction مع lockForUpdate، والقيود لها idempotency_key. العيوب الحقيقية مركّزة في 3 أماكن: حسابات التكلفة/الانحرافات، تزامن الـ MRP، ومسارَي إنشاء (Order/BOM) بدون transaction.'released' ثابتة، ومفيش quarantine/rejected. ده أكبر عائق GMP.EventServiceProvider.php:34-70) — تصميم نموذجي.MfgBatch::availableQuantity() وdrainPendingForOrder() بيستخدموا allocated_pending + lockForUpdate لمنع الحجز المزدوج (TOCTOU).canRelease/canIssue/canComplete)، آلة حالة الانحرافات (canTransitionTo())، isToll().auth:sanctum + صلاحيات granular، وكل الـ controllers بتعمل company-scoping يدوي. صفر SQL injection، صفر $guarded=[].decimal:4، كميات decimal:3) ثابتة.Controller ← Action ← Model (منطق الكتابة في Actions، مفيش service layer للكتابة — مجلد Services/ شبه فاضي). الأحداث/المستمعين منظّمين كويس لقيود الـ GL. التعامل مع الـ transactions متسق في كل الـ actions المعدِّلة.
آلة الـ MRP كلها في class واحد (GC + demand + netting + scheduling + lot-sizing + explosion + pegging). ConfirmOperation أوركستراتور متخفي في صورة action (بيستدعي Inventory + GL + HRM piecework). محتاجين تفكيك لـ classes أصغر (DemandCollector / NettingEngine / LotSizer / BomExploder / Pegger).
غالبية الموديلز enums حقيقية، بس MfgMpsHeader/Line وMfgVarianceRecord بيستخدموا const strings (عُرفين متنافسين). مفيش query scopes — فلترة where('company_id') متكتوبة يدوي في كل مكان. وصول مباشر لموديولات تانية عبر app(...) بدل anti-corruption boundary.
بيقارن ميزانية الفترة كاملة بالمطبّق على أمر واحد، فأي أمر إنتاجه أقل من حجم الفترة بيطلع انحراف ≈ الميزانية الشهرية كلها كـ "غير مواتٍ". الصح: (budgetedVolume − produced) × stdFixedRate. الأثر: انحراف OH ثابت غلط على كل أمر، وتقارير إدارية مشوّهة. (مفيش تست يغطي المسار).
توليد رقم تسلسلي ← إنشاء الأمر ← N خامات ← N عمليات ← تحديث التكاليف المخططة — كله مش داخل DB::transaction. أي فشل في النص بيسيب أمر يتيم نص-مكتمل برقم مستهلك وتكاليف ناقصة. كل مسارات الإنشاء الأخرى عاملة transaction — ده الوحيد المنسي.
إنشاء BOM ← مكوّنات + بدائل ← عمليات، بدون transaction (عكس clone/setDefault بتوعه). BOM نص-مكتمل بيغذّي أوامر إنتاج لاحقة → فساد بيانات أساسية.
| # | المشكلة | المكان | الخطورة |
|---|---|---|---|
| H1 | تشغيلات MRP متزامنة بتفسد بعض — الـ purge على مستوى الشركة كلها مش مستوى الـ run، ومفيش قفل "run واحد نشط" | RunMrp.php:161-184 | HIGH |
| H2 | توليد run_number بـ count()+1 = race condition | RunMrp.php:1143 | HIGH |
| H3 | هيدر الـ run والـ CRP برّه transaction الـ netting — run "فاشل" بيسيب planned orders حيّة | RunMrp.php:100,138 | HIGH |
| H4 | تسوية الـ overhead check-then-act بدون قفل (TOCTOU) → ترحيل مزدوج آخر الشهر | SettleAppliedOverhead.php:58-83 | HIGH |
| H5 | حفظ الانحرافات غير transactional وغير idempotent → سجلات مكرّرة عند إعادة الحساب | ComputeOrderVariances.php:591 | HIGH |
| H6 | recordOutput بيخلّي الكمية المنتجة تكبر بلا سقف مقابل الخطة → تكلفة وحدة و yield مشوّهين | ProductionOrderController.php:538 | HIGH |
| H7 | مفيش تحويل وحدات قياس (UoM) في الـ MRP/BOM explosion — لو مكوّن بوحدة مختلفة عن وحدة المخزون → فساد كميات صامت | RunMrp.php:1018, BomExplosionService.php:103 | HIGH |
| H8 | المطبّق مقابل الفعلي للـ overhead متقاسين بمستوى/نطاق مختلف → انحراف تسوية وهمي | SettleAppliedOverhead.php:129-232 | HIGH |
| M1 | roll-up التكلفة المعيارية بيدمج عمل/OH التجميعة الفرعية في leg "الخامة" للأب → تصنيف انحراف غلط | RollUpStandardCost.php:159 | MED |
| M2 | قاعدة min_max بتغطّي العجز ناقص بصمت بدون split/exception | RunMrp.php:925 | MED |
| M3 | period_order (POQ) لا-عملية وeoq = fixed بس (stubs) | RunMrp.php:916 | MED |
| M9 | per_page بلا سقف في 3 endpoints → full-table hydrate / DoS | ProductionOrderController.php:93 | MED |
| L1 | cancel/unrelease بيبلعوا أخطاء تحرير الحجز وبيغيّروا الحالة برضه → تسريب حجز | ProductionOrderController.php:387 | LOW |
lockForUpdate (خط الدفاع الأساسي) متستّت بشكل تسلسلي على connection واحد. لو شِلت كل الـ lockForUpdate() مش هيفشل ولا تست. كمان الانحرافات متستّتة في اتجاه واحد بس (كلها غير مواتية).owner_user_id (IDOR)
owner_user_id متحقّق منه كرقم موجب بس — مش مقيّد بشركة المستخدم. مهاجم بيـ PATCH انحراف بـ owner_user_id لمستخدم في شركة تانية → فساد ملكية + كشف بيانات. الكنترولر التاني (ConfirmationController:74) بيعمل scoping صح لنفس النوع — يعني سهو. الإصلاح سطر واحد: Rule::exists('users','id')->where('company_id',$companyId).
| # | المشكلة | المكان | الخطورة |
|---|---|---|---|
| M1 | قواعد exists غير مقيّدة بالشركة في الـ BOM (product/unit/center) → ربط كيانات شركة تانية | BomController.php:162,165,205 | MED |
| M2 | نفس المشكلة في صرف الخامة/استلام التام/فاتورة toll (warehouse/customer/tax) | ProductionOrderController.php:584+ | MED |
| L1 | 403 بدل 404 على موارد شركة تانية → كشف وجود الـ ID (enumeration) | 10 controllers | LOW |
Global Tenant Scope — عزل القراءة 100% يدوي. الموديول دلوقتي عامله صح في كل مكان، بس مفيش شبكة أمان: أي query مستقبلي ينسى where('company_id') هيسرّب بصمت. التوصية: اعمل CompanyScope عام (addGlobalScope) يشيل الاعتماد على إن كل مطوّر يفتكر.كل آلة الـ MRP + الـ explosion + الـ pegging + الـ CRP بتشتغل جوه request واحد وtransaction واحدة. مع 40+ منتج → دقايق بتحجز PHP worker وقفل طويل. الحل: ادفعها لـ queued job، ورجّع الـ run فوراً، وخلّي الـ cockpit يـ poll.
resolveDefaultBom() بيتعاد استعلامه لكل منتج بدون memoization (N+1)
نفس الـ BOM SELECT بيتعمل مئات المرات في الـ run. + supplySchedule() بيعمل 4-5 استعلامات منفصلة لكل منتج. الحل: memoize الـ BOM والـ supply لكل منتج طول الـ run.
| الجدول | الفهرس الناقص | الأثر |
|---|---|---|
production_orders | (company_id,product_id,status) + (company_id,planned_end_date) | CRIT full scan في توريد MRP |
bill_of_materials | (company_id,product_id,is_active,is_default) | CRIT أسخن lookup في MRP |
mfg_mrp_planned_orders | (company_id,product_id), (mrp_run_id,due_date) | HIGH |
bom_components | product_id (where-used / LLC) | HIGH |
factory-dashboard بيسحب كل الأوامر للمتصفح ويعدّها client-side (:286). استخدم endpoints تجميع زي ما orders بيعمل.confirmations/batches/board بيـ listAll() بلا حدود + جداول بدون virtualization؛ والـ board بيـ poll snapshot غير محدود كل 30 ثانية.production-orders.component بيستخدم [lazy] server paging + endpoint statusCounts() — طبّقه على الباقي.quality_status='released' ثابتة، وحالة الـ batch بس active|consumed|expired (مفيش quarantine/rejected/under-test). تشغيلة محجوزة بتتصرف بحرية. أكبر عائق GMPexpiry_date حر — مفيش manufacture_date/retest_date/اشتقاق shelf-life/منع صرف تشغيلة منتهية.MfgBatch → فجوة فصل toll في المخزون.| الموجة | الشغل | ليه دلوقتي |
|---|---|---|
| ١ — إصلاحات حرجة | صلّح C1 (معادلة FOVV)، لفّ C2/C3 في transaction، وحطّ سقف للكمية المنتجة (H6) | دي بتفسد بيانات مالية/مخزون فعلياً دلوقتي |
| ٢ — تزامن MRP/OH | قفل "run واحد نشط" + run-scoped purge (H1)، transaction حول الـ run+CRP (H3)، idempotency للـ overhead والانحرافات (H4/H5) | تمنع فساد التخطيط والترحيل المزدوج |
| ٣ — أمان | scope الـ owner_user_id (H1) وكل قواعد exists (M1/M2)، وزوّد CompanyScope عام | إصلاحات صغيرة، تقفل عبور-المستأجرين |
| ٤ — أداء | اعمل queue للـ MRP/rollUp/convertAll، memoize الـ BOM/supply، migration الفهارس، وصلّح N+1 في الصرف/التأكيد | ضروري قبل ما الداتا تكبر |
| ٥ — GMP فارما | حالة batch (quarantine/released/rejected) + بوابة QC قبل الاستلام/الصرف، حقول الصلاحية/الـ potency، وملكية على الـ batch | متطلبات تنظيمية لمصنع أدوية |
| ٦ — اكتمال + جودة كود | subcontracting، rework حقيقي، تستات تزامن، وتفكيك الـ god-classes (RunMrp/ConfirmOperation) | قيمة طويلة المدى وقابلية صيانة |