«أمر الشراء يفضل مفتوح بدل ما يتعملش له إذن، حتى لو اتعمل له فاتورة شراء واتدفعت يبقى لسه ما اتسلمش — ممكن يتعمل له إذن استلام في أي وقت، لأن ساعات بندفع وبعدين بنستلم.»
وده امتداد طبيعي لخيار «الفوترة قبل الاستلام» اللي فتحناه قبل كده: فتحنا الفوترة، بس الأمر كان بيتقفل بعدها فما يبقاش ينفع تستلم عليه.
أمر الشراء عنده تلات حقول بيوصفوا حالته:
| الحقل | بيقول إيه | كان بيتحسب صح؟ |
|---|---|---|
receive_status | وصل قد إيه — pending/partial/complete |
آه، دايمًا صح |
bill_status | اتفوتر قد إيه — نفس القيم | آه، دايمًا صح |
status | حالة واحدة عامة بتخلط الاتنين:
confirmed · partially_received · fully_received ·
partially_billed · fully_billed |
لأ — هنا المشكلة |
PurchaseOrder::recalculateBillStatus() كان مكتوب:
if ($allBilled) {
$this->update(['status' => PurchaseOrderStatus::FullyBilled]);
}
من غير أي شرط. فأمر اتفوتر بالكامل وما وصلش منه ولا قطعة كان بياخد حالة
fully_billed — والنظام بيقراها كأن الأمر خلص.
orders.component.html:136:
@if (o.status === 'confirmed' || o.status === 'partially_received')
fully_billed مش في القائمة، فزرار «إنشاء إذن استلام» كان
بيختفي خالص — مش بيبقى مقفول، بيختفي. وده بالظبط اللي إنت شايفه.
recalculateReceiveStatus() بتحرّك الحالة لـ«مستلم جزئيًا» بس لو الحالة الحالية
واحدة من confirmed / sent / fully_received — وحالات الفوترة مش فيهم.
فحتى لو دخلت إذن استلام بطريقة تانية، الحالة كانت هتفضل متجمّدة على حالة الفوترة.
النتيجة المجمّعة: بمجرد ما تفوتر أمر ما وصلش، بيدخل في طريق مسدود — الزرار بيختفي، والحالة مابتتحركش، والبضاعة لما توصل مالهاش مكان تتسجّل فيه على الأمر.
Modules/Purchases/app/Models/PurchaseOrder.php — recalculateBillStatus()
$stillAwaitingGoods = $items->contains(
fn ($item) => (float) $item->received_quantity < (float) $item->quantity
);
if ($stillAwaitingGoods) {
return; // الأمر لسه عليه بضاعة لم تصل — حالة الاستلام تفضل زي ما هي
}
يعني الأمر اللي لسه مستني بضاعة بيحتفظ بحالة الاستلام بتاعته،
والفوترة بتتسجّل في مكانها الصح: bill_status.
نفس الملف — recalculateReceiveStatus(): ضفت حالتين للقائمة اللي بتسمح
بالانتقال لـ«مستلم جزئيًا»:
PurchaseOrderStatus::PartiallyBilled, PurchaseOrderStatus::FullyBilled,
وكمان لما الاتنين يخلصوا (وصلت كلها + متفوترة كلها) الحالة بتروح
fully_billed بدل ما تدهس الفوترة بـfully_received —
عشان الحقيقة الأحدث هي اللي تبان.
orders.component.html — الشرط بقى مبني على الحقل اللي بيقول الحقيقة فعلًا:
@if ( o.receive_status !== 'complete' && (o.status === 'confirmed' || o.status === 'partially_received' || o.status === 'partially_billed' || o.status === 'fully_billed') )
حالات الفوترة مضافة عشان الأوامر العالقة عندك دلوقتي يرجع لها الزرار،
وreceive_status !== 'complete' عشان الزرار يختفي لما البضاعة توصل بالكامل.
كتبت ملف اختبار انحدار جديد:
Modules/Purchases/tests/Feature/OrderStaysReceivableAfterBillingTest.php —
خمس حالات:
| # | الحالة | بيثبت إيه |
|---|---|---|
| ١ | أمر اتفوتر بالكامل وما وصلش منه حاجة | bill_status = complete لكن الحالة تفضل confirmed
وreceive_status = pending — ده العيب المبلَّغ |
| ٢ | وصل ١٠ من ٣٠ على أمر متفوتر | الحالة تتحرك لـpartially_received فعلًا |
| ٣ | فوترة الأول ثم استلام كامل | ينتهي على fully_billed — الفوترة ما اتفقدتش |
| ٤ | الطريق العادي: استلام ثم فوترة | ما اتغيّرش ولا حاجة — fully_received ثم fully_billed |
| ٥ | فاتورة جزئية على أمر ما وصلش | يفضل مفتوح للاستلام برضه |
مع الإصلاح: 5 passed بعد تعطيل الحارس: 2 failed, 3 passedالحالتان ١ و٥ — اللي بيثبتوا الحارس بالظبط — فشلوا. (٢ و٣ و٤ فضلوا خُضر لأنهم بيختبروا النص التاني من الإصلاح، وهو كان لسه مكانه.) وبعدين رجّعت الملف لحالته المصلَّحة وتأكدت إن الحارس موجود والتعطيل اتشال.
وبعدها شغّلت سويت المشتريات كاملة عشان أتأكد إن مفيش حاجة تانية اتكسرت،
وبنيت الواجهة (ng build) للتأكد من التعديل.
confirmed
على الأقل — ما ضفتش draft ولا approved ولا الملغي.الأوامر اللي اتفوترت قبل الإصلاح واقفة دلوقتي على fully_billed /
partially_billed وهي ما وصلتش. تعديل الواجهة عامل حسابهم:
الزرار هيرجع يبان لهم فورًا، وأول ما تعمل إذن استلام الحالة هتتصحح لوحدها
(بفضل التعديل رقم ٢).
prod.elbaset،
قولّي — بس ده استعلام على بيئة الإنتاج، مش هعمله من غير كلمة منك.
الشغل على hazemdev2 ومنشور على /app بتاع moonui2 —
ما اتعملش دمج لـmain، مستني اختبارك.