ISS-2026-9105 · تقرير تشخيص (تحقيق بلا كود) · planning-gate

الصرف الجزئي لخامات أمر الإنتاج — «مش لاقي مكان أصرف الجزء المتبقّي» + الصرف الزائد Partial material issue on a production order — the "no place to issue the remainder" bug

الأمر: PRD-2026-00022 الخطورة: متوسطة (حظر سير عمل) [FIN]: غير مباشر (صرف الخامات يرحّل WIP) الشاشة: أوامر الإنتاج — نافذة صرف الخامات

1 العرض / الأعراض

العميل بيجرّب الصرف الجزئي لخامات أمر الإنتاج، وبيبلّغ بمشكلتين:

تحديث (ملاحظة العميل — 2026-07-15، بعد الموافقة على التوصيات الأولى): «الصرف الزائد طبيعي — مثلاً خامة اتهدرت، وده اللي بيعمل تكلفة أعلى وانحراف في التكلفة لو اتحسبت الخامة أقل من اللازم. فالمفروض يقدر يصرف عادي زي ما عايز على أمر الإنتاج. لكن لو تجاوز التقييم الأوّلي (أعلى من المطلوب) → لازم يبقى فيه موافقة أخرى — دورة موافقة قبل الإتاحة للصرف.» → الخطة اتعدّلت تحت (WP3 صرف حرّ حتى المطلوب · WP4 دورة موافقة للتجاوز فوق المطلوب).
المتوقّع: طالما فيه كمية متبقّية لمادة، أقدر أفتح شاشة الصرف وأصرفها (وأزيد فوق المطلوب لو حبيت).
الفعلي على PRD-2026-00022: الأمر حالته completed بينما المادة «خام بودرة ميلامين» متصرّف منها 5.000 من 12.360 (متبقّي 7.360) → زر الصرف مختفي تمامًا فمفيش أي مكان للصرف — لا للمتبقّي ولا للزيادة.

خطوات الاستنساخ (تم التحقق على بيانات dev — قراءة فقط)

الأمرالحالةالمادةمطلوبمتصرّفمتبقّي
PRD-2026-00022 (id 39)completedخام بودرة ميلامين12.3605.0007.360
بودرة جليز0.4000.4000.000

produced_quantity = 0.000 — الأمر «مكتمل» رغم عدم إنتاج شيء وعدم اكتمال صرف الخامات.

2 خط التتبّع (hop-by-hop، بالأدلة)

DB

بيانات الأمر الفعلية

production_orders.status = 'completed' · مادة id 84 consumed 5.000 < planned 12.360. moonui2_dev_be (SELECT فقط)
حالة

الحالة تمنع الصرف

canIssue() = Released || InProcess فقط — completed غير مشمولة. Production/app/Enums/ProductionOrderStatus.php:74-77
FE

الواجهة تخفي الزر

canIssueMaterials(status) = status==='released' || status==='in_process' → على أمر مكتمل الزر لا يُعرض إطلاقًا (قائمة وتفاصيل). production-orders.component.ts:1329-1330 · .html:120-123, 316-319
اكتمال

كيف اكتمل بمواد ناقصة

complete() يتحقّق من canComplete() (الحالة = InProcess) فقط — لا يوجد حارس يمنع الاكتمال ومواد لسه ناقصة الصرف. ProductionOrderController.php:314-329 · Enum transitions InProcess→[Completed] :47
BE

الصرف نفسه سليم (لو الحالة تسمح)

consumed_quantity بيتراكم (يُضاف لا يُستبدل)، فالصرف على دفعات مدعوم بالكامل خادميًا. IssueMaterials.php:300-303 · resolveLines:465-528

3 السبب الجذري

أساسي السبب الجذري (يوحّد المشكلتين): أمر الإنتاج يُسمح بنقله إلى completed بينما بعض المواد لسه ناقصة الصرف (الحارس complete() يفحص الحالة فقط بلا فحص «اكتمال صرف الخامات» — ProductionOrderController.php:314-329)، وcanIssue()/الواجهة تستثني completed — فبعد الاكتمال يختفي مدخل الصرف نهائيًا. النتيجة: لا يمكن صرف المتبقّي ولا الزائد لأن أي صرف أصبح ممنوعًا بالحالة.
الدليل: PRD-00022 status=completed مع consumed 5.000 < planned 12.360
مساهم الصرف الزائد غير مُقيَّد أصلًا بقاعدة إنتاج: لا يوجد أي سقف على الكمية مقابل المطلوب/المتبقّي — لا في تحقّق الكنترولر (:599 gt:0 فقط) ولا في IssueMaterials.resolveLines:470 ولا في الموديل. الصرف الزائد يُرفض فقط عند نقص الرصيد الفعلي (insufficient_stock) في Inventory/app/Actions/ApproveIssue.php:200-207. يعني إحساس العميل بأن «النظام يمنع الصرف فوق المطلوب» سببه إمّا حالة الأمر المكتمل (أعلاه) أو نقص رصيد — مش قاعدة إنتاج.
سليم القدرة الأساسية موجودة وشغّالة على أمر released/in_process: الواجهة تبني صفًّا لكل مادة بأعمدة مطلوب/متصرّف/متبقّي وتملأ الكمية بالمتبقّي وتعيد الجلب عند كل فتح؛ الصرف الجزئي واختيار جزء من المواد (كمية 0 = تخطّي) وصرف أكتر من المتبقّي (لا يوجد max) كلها تعمل. production-orders.component.ts:882-900,1051-1074 · .html:591-618 · IssueMaterials.php:300-303 · canIssue InProcess=true

4 ليه حصلت — الآليّة

حارس ناقص عند الاكتمال (missing guard) + حالة تمنع الصرف. منطق أوامر الإنتاج يسمح بالاكتمال بمجرد أن الحالة in_process بغضّ النظر عن اكتمال صرف الخامات أو الإنتاج (produced_quantity=0). لأن الصرف الناقص (under-issue) سلوك مشروع أحيانًا، مفيش حارس. لكن ما إن يكتمل الأمر حتى تُغلق كل مداخل الصرف (canIssue() تستثني completed) — فالمستخدم اللي أكمل الأمر مبكرًا (يدويًا أو بالخطأ) يعلق بلا طريق لصرف الباقي. مش انحدار (regression) — ده فجوة تصميمية في دورة حياة الأمر ظهرت أول ما العميل جرّب الصرف على دفعات.

5 كل الأبعاد

البُعدالنتيجة
البياناتPRD-00022 عالق فعلًا (completed + متبقّي 7.360). أوامر أخرى مكتملة بمواد ناقصة محتملة — يُفحص عند الإصلاح. لا يحتاج migration بيانات لو أضفنا «إعادة فتح».
مسار الكود (كل المنادين)الصرف يمرّ من ProductionOrderController::issueMaterials وConfirmOperation::backflush (كلاهما IssueMaterials). الحارس المفقود في complete() فقط. ProductionOrderController.php:314
الإعداد/الأنماطفيتشر ISS-9103 (موافقة المخزن، افتراضي OFF) موجود على نفس الشجرة. لا يغيّر الجذر: الحالة completed تمنع الصرف في الوضعين.
الصلاحياتغير معتمِد على الدور؛ زر الصرف محكوم بالحالة لا بالصلاحية.
مالي [FIN]صرف الخامات يرحّل قيد WIP؛ صرف مواد على أمر «مكتمل» له معنى محاسبي حسّاس (WIP بعد الاكتمال) → قرار التصميم يجب أن يراعيه.
حالات حدّيةالصرف الزائد فوق المطلوب: مسموح فنيًّا (يقيّده الرصيد فقط). under-issue: مشروع. الأمر المُقفل (Closed) لا يُعاد فتحه.
مواضع مشابهة كامنةcanIssue() يُستدعى من الـFE والـBE بنفس الاستثناء — نقطة واحدة. لا يوجد حارس اكتمال مشابه في close() أيضًا (يُراجع).

6 الحلول المقترحة

الخيار A — حارس عند الاكتمال + توضيح الصرف الزائد موصى به

عند الضغط على «إنهاء»، لو فيه مواد متبقّية > 0 → تأكيد صريح («فيه خامات لسه ما اتصرفتش — تصرفها الأول ولا تكمّل بصرف ناقص؟») بدل الاكتمال الصامت. + رسالة insufficient_stock أوضح. لا يمنع under-issue المشروع، بس يمنع الاكتمال «بالغلط».

الخيار C — إجراء «إعادة فتح» (Completed → InProcess) موصى به مع A

زر «إعادة فتح للصرف» ينقل الأمر من completed لـin_process → يظهر زر الصرف → يصرف المتبقّي/الزائد → يكمّل تاني. يصلح PRD-00022 والحالات العالقة فورًا بلا تعديل بيانات يدوي.

الخيار B — السماح بالصرف على أمر مكتمل مباشرةً أضعف

توسيع canIssue() لتشمل completed (حتى الإقفال). أبسط لكن يُضعف معنى «مكتمل» ويسمح بترحيل WIP بعد الاكتمال بلا وعي — غير مفضّل مقارنة بـA+C.

الخيار D — دورة موافقة للصرف الزائد فوق المطلوب مطلوب العميل (معتمد)

الصرف حتى الكمية المطلوبة (التقييم الأوّلي) = حرّ زي ما هو (يشمل المتبقّي والصرف على دفعات). أمّا الصرف اللي يتجاوز المطلوب (consumed + الكمية الجديدة > planned للمادة) → يُحجز بانتظار موافقة مسؤول الإنتاج قبل تحريك المخزون، مع سبب إلزامي (هدر/سقط/إعادة تشغيل). الجزء المعتمَد يُصرف ويظهر انحرافه في التكلفة عبر production.cost_variance_account_id (موجود). يُبنى خلف إعداد production.over_issue_requires_approval بصلاحية جديدة production.orders.approve_over_issue (مسؤول إنتاج، منفصلة عن أمين المخزن في ISS-9103).

التوصية: A + C + D — حارس تأكيد عند الاكتمال (يمنع التكرار) + إعادة فتح (يصلح العالق) + صرف حرّ حتى المطلوب + دورة موافقة للتجاوز فوق المطلوب (سبب إلزامي → انحراف تكلفة). يعالج جذر المشكلتين ويحترم متطلب العميل الجديد.

7 خطة الإصلاح (WPs)

WPالنطاقRepoالاختبار[FIN]Migration
WP1حارس تأكيد عند «إنهاء» أمر فيه مواد متبقّية > 0 (تأكيد صريح بدل الاكتمال الصامت). حماية BE + رسالة FE.BE+FEPest: إنهاء بمواد ناقصة يتطلّب تأكيد/يُرفض بلا العلم
WP2إجراء «إعادة فتح للصرف» Completed→InProcess (صلاحية + endpoint + زر). يصلح الأوامر العالقة.BE+FEPest: reopen يعيد الحالة ويسمح بصرف المتبقّي✅ (WIP)
WP3الصرف حتى المطلوب حرّ: تحسين وضوح زر الصرف (label) وعرض المتبقّي + توضيح رسالة نقص الرصيد. الصرف على دفعات/المتبقّي يشتغل بلا موافقة إضافية.FE (+رسائل BE)Pest: صرف المتبقّي حتى planned ينجح مباشرةً
WP4دورة موافقة للصرف الزائد فوق المطلوب: إعداد production.over_issue_requires_approval + صلاحية production.orders.approve_over_issue (مسؤول إنتاج)؛ لمّا consumed+الكمية > planned → يُحجز الجزء الزائد بانتظار موافقة مع سبب إلزامي؛ بعد الاعتماد يُصرف ويظهر في انحراف التكلفة. يتوافق مع موافقة أمين المخزن (ISS-9103) كطبقتين مستقلتين.BE+FEPest: تجاوز planned يُحجز؛ بعد approve يُصرف؛ رفض الموافقة لا يحرّك مخزون✅ (WIP/انحراف)محتمل (جدول/عمود طلب موافقة زائد)

مؤجّل/للمراجعة: هل نضيف حارسًا مشابهًا عند الإقفال (close)؟ · فحص أوامر مكتملة أخرى بمواد ناقصة (تقرير، لا migration).

8 قرارات تحتاج المالك

ق1 — الاكتمال بمواد ناقصة: نمنعه تمامًا، أم نسمح به بتأكيد صريح فقط (لأن الصرف الناقص أحيانًا مشروع)؟
التوصية: نسمح بتأكيد صريح (مش منع تام) — under-issue سلوك حقيقي.
ق2 — الأوامر العالقة (زي PRD-00022): نضيف زر «إعادة فتح للصرف» (Completed→InProcess) لحلّها بنفس المستخدم؟
التوصية: نعم — أنظف من تعديل بيانات يدوي.
ق3 — الصرف الزائد (معتمد بتعديل العميل): الصرف حتى المطلوب حرّ؛ التجاوز فوق المطلوب يمرّ بدورة موافقة. ✅ مثبّت (WP3 + WP4).
ق4 — مَن يعتمد الصرف الزائد؟ صلاحية جديدة لمسؤول الإنتاج (production.orders.approve_over_issue) منفصلة عن أمين المخزن (ISS-9103)؟ أم نفس معتمِد المخزن؟
التوصية: مسؤول إنتاج منفصل — لأن التجاوز قرار تكلفة/إنتاج لا مخزن.
ق5 — تفعيل موافقة التجاوز: إعداد production.over_issue_requires_approval افتراضي ON (يطابق طلبك: أي تجاوز يحتاج موافقة) وتقدر الشركة تقفله؟
التوصية: نعم — ON افتراضيًا.
ق6 — سبب التجاوز: سبب إلزامي عند الصرف الزائد (هدر/سقط/إعادة تشغيل) يظهر مع انحراف التكلفة؟
التوصية: نعم — إلزامي.
ق7 — صرف على أمر مكتمل مباشرة (الخيار B): نرفضه لصالح «إعادة الفتح» (A+C)؟
التوصية: نعم — المسار الصحيح = إعادة فتح.

تقرير مرحلة أولى (تشخيص) — لا يُكتب أي كود قبل موافقتك على الحلّ والخطة أعلاه. بعد الموافقة تُنفَّذ WP1→WP3 مع اختبار يثبت الإصلاح ومراجعة مستقلة.