المتوقع: فاتورة فيها البنود اللي اخترتها بالكميات اللي كتبتها.
الحاصل: الفاتورة بتتعمل فعلاً، فيها المورد والفرع والعملة — وجدول البنود فاضي والإجمالي صفر.
خطوات التكرار: أوامر الشراء ← أمر confirmed لسه ما اتستلمش ← زرار «إنشاء فاتورة»
← الحوار بيعرض البنود وكمياتها ← تأكيد ← افتح الفاتورة: مفيش بنود.
مين بيتأثر: أي شركة في الوضع المراقَب (procurement_mode = controlled) أو
grn_mode = grn / grn_quality. الشركات في الوضع المباشر (direct) ما بتتأثرش إطلاقًا.
| الحلقة | الموضع | اللي اتلاحظ |
|---|---|---|
| الداتا | purchase_bills #19 | BILL-2026-00018 · من أمر ٢١ · صفر بند · الإجمالي 0.000 · اتعملت 2026-08-15 13:02 |
| الأمر | purchase_orders #21 | confirmed · receive_status = pending · بند واحد: مطلوب ٣٠ · مستلَم 0 · مفوتر 0 |
| الواجهة | orders.component.ts :: openCreateBill() | بتجيب المستند كامل بـgetById وبتفلتر على i.remaining_bill_qty > 0 وبتملّي الكمية بيه |
| المورد | PurchaseOrderItemResource:37 | remaining_bill_qty => remainingBillQuantity() |
| الحساب | PurchaseOrderItem::remainingBillQuantity():123 | quantity − billed_quantity = ٣٠ ← ده اللي الحوار عرضه |
| السيرفر | PurchaseBillController::createFromOrder() | في غير الوضع المباشر: received_quantity − billed_quantity = صفر ← continue لكل بند |
| النتيجة | — | الهيدر اتكتب، ولا بند اتكتب، ومفيش أي رفض |
في الوضع المراقَب القاعدة المحاسبية سليمة: ما تفوترش حاجة ما استلمتهاش. المشكلة إن الواجهة
ما اتعلّمتش القاعدة دي أبدًا — هي بتسأل عن remaining_bill_qty وده محسوب على المطلوب،
من غير أي اعتبار لوضع الاستلام.
لما كل البنود تتخطّى، الدالة بتكمل وتحفظ الهيدر. مفيش أي فحص «الخطة طلعت فاضية → ارفض». ده ثالث حالة من نفس العيلة النهاردة: التحويل الفاضي لطلب الشراء، وأمر الشراء بلا سطور، ودي.
لأ، مش انحدار من تعديل امبارح. الفرضية الأولى كانت إن كوميت b7d73ea3e
(الكميات المطلوبة) هو السبب. الدليل نفاها: قاعدة
received_quantity − billed_quantity جت من 3d453860f بتاريخ
2026-07-09 — قبل تعديل امبارح بشهر. تعديل امبارح ضاف طبقة فوقها وما سببش ده.
الآلية: فجوة تصميم بين طرفين اتغيّر أحدهما ولم يُبلَّغ الآخر. لما الوضع المراقَب اتبنى في يوليو، السيرفر اتعلّم «فوتر على المستلَم»، والواجهة فضلت على «فوتر على المطلوب». ومحدش لاحظ لأن الفرق ما بيظهرش غير لما تحاول تفوتر قبل الاستلام.
| البُعد | النتيجة |
|---|---|
| الداتا | فاتورة واحدة فاضية على الديف (#19). مسودة وإجماليها صفر، فمفيش قيد اترحّل ولا رصيد اتأثر. الإصلاح مش محتاج ترحيل بيانات — بس الفاتورة الفاضية دي تتحذف يدويًا. |
| الإعدادات | هي محور العطل. direct سليم تمامًا · grn وgrn_quality والوضع المراقَب متأثرين. |
| الصلاحيات | لا علاقة — مش متوقف على دور. |
| المال [FIN] | الفاتورة الفاضية ما بتفسدش الدفاتر (صفر، ومسودة). الخطر إنها بتوهم المستخدم إن الفوترة تمّت، فيدوّر على فلوس مش موجودة. |
| i18n | لا علاقة. |
| حالات حدّية | استلام جزئي: الحوار هيعرض المطلوب كله والسيرفر هيقصّه للمستلَم — فاتورة أقل من المتوقع من غير أي رسالة. أخطر من الحالة الفاضية لأنها مش ملحوظة. |
createFromGrn بيفوتر على accepted_quantity — متسق مع قاعدة السيرفر، فمش متأثر بنفس التناقض.يتضاف حقل remaining_bill_qty_effective على مورد بند الأمر يحترم وضع الاستلام،
والحوار يفلتر ويملّي منه. + حارس رفض على السيرفر: لو الخطة طلعت فاضية → 422 برسالة
«لازم تستلم البضاعة الأول».
المكسب: بيصلح الفئة مش الحالة — الحوار مش هيعرض حاجة السيرفر هيرفضها، لا فاضية ولا جزئية. التكلفة: حقل جديد في المورد + تعديل الحوار.
422 لما الخطة تطلع فاضية، والحوار زي ما هو.
المكسب: سطرين. العيب: الحالة الجزئية تفضل صامتة — تختار ٣٠ وتاخد ١٠ من غير ما حد يقولك.
مرفوض: بيكسر قاعدة محاسبية مقصودة (ما تفوترش قبل ما تستلم) وبيخالف حارس المطابقة الثلاثية.
| WP | النطاق | الريبو | الاختبار | علم |
|---|---|---|---|---|
| ١ | حارس الرفض: خطة فاضية → 422 برسالة «استلم الأول» (ar+en) | BE | أحمر-قبل-أخضر: أمر مستلَم صفر → 422 ومفيش فاتورة اتعملت | [FIN] |
| ٢ | remaining_bill_qty_effective على مورد بند الأمر، محسوب بنفس قاعدة السيرفر | BE | الحقل = صفر في المراقَب بلا استلام · = المتبقي في المباشر | — |
| ٣ | الحوار يفلتر ويملّي من الحقل الجديد، ورسالة واضحة لو مفيش حاجة قابلة للفوترة | FE | بناء نضيف + فحص يدوي على أمر غير مستلَم | — |
| ٤ | حذف الفاتورة الفاضية #19 من الديف | data | — | — |