تحليل تفصيلي بالكود · 3 محاور

تحليل دورة المشتريات والترابط مع المخازن

فحص الدورة الكاملة (PR → PO → GRN → فاتورة → دفع + مرتجع + تكلفة وصول)، هل صحيحة، الترحيل للمخازن بكل الاحتمالات، وإجابة كل أسئلتك بأدلة من الكود.

2026-06-16 · Modules/Purchases · أدلة file:line

٠ — إجابات أسئلتك المباشرة

الثلاث أسئلة اللي سألتها، بإجابة قاطعة من الكود.
📎
أمر الشراء مُرفَق داخل فاتورة الشراء؟
✅ نعم

الفاتورة بتحمل purchase_order_id + كل سطر purchase_order_item_id، والأصناف منسوخة من الأمر. فاتورة واحدة = أمر واحد.

🔗
طلب البيع مُرفَق داخل أمر الشراء؟
❌ لا (ناقص)

مفيش sales_order_id على أمر الشراء خالص. الربط الوحيد غير مباشر عبر MRP وهو مكسور عند التحويل.

📦
أمر الشراء يورّد للمخزن؟
❌ لا

أمر الشراء التزام فقط — صفر تأثير على المخزون. اللي بيورّد = إذن الاستلام (GRN) أو الفاتورة (في الوضع المباشر).

ملاحظة مهمة: طلب الشراء (PR) مُرفَق داخل أمر الشراء (header link purchase_request_id، الأصناف منسوخة) — بس بدون تتبّع على مستوى السطر.
🔄

١ — خريطة الدورة الكاملة

سلسلة المستندات وآلات الحالة. الدورة منطقية وكاملة.
طلب شراء ──convertFromRequest──► أمر شراء ──┬─ createFromOrder ──► فاتورة شراء ──► دفع
  (PR)                              (PO)      │                        (Bill)
                                              └─ GRN.store(po_id) ──► إذن استلام ──► (مخزون+)
                                                                         (GRN)
مرتجع شراء ◄── مربوط بالفاتورة (bill_id)        تكلفة وصول ◄── مربوطة بالـ GRN (purchase_grn_id)
المستندآلة الحالة (Status)مين بينشئ/يرحّل
طلب شراء (PR)Draft → PendingApproval → Approved → Converted (+Rejected/Cancelled)CreatePurchaseRequest (مدخل MRP كمان)
أمر شراء (PO)Draft → Approved → Confirmed → Sent → PartiallyReceived → FullyReceived → PartiallyBilled → FullyBilled → Closedالاستلام/الفوترة تلقائية (recalculateReceiveStatus/recalculateBillStatus)
إذن استلام (GRN)Draft → PendingQuality → QualityApproved/Rejected → ApprovedPurchaseGrnController::approve (المنطق inline، مفيش Action)
فاتورة (Bill)Draft → Approved → Posted → PartiallyPaid → PaidPostPurchaseBill (قيد + مخزون مباشر + كمية مفوترة)
دفعDraft → PostedPostPurchasePayment
مرتجعDraft → Approved → PostedPostPurchaseReturn (عكس AP + خصم مخزون)
تكلفة وصولDraft → PostedPostLandedCostVoucher (إعادة تقييم، مش كمية)
الحكم: الدورة منطقية وكاملة وصحيحة في المسار السليم. آلات الحالة واضحة، والكميات المستلمة/المفوترة متتبّعة على مستوى سطر الأمر (3-way match data).
📦

٣ — هل أمر الشراء يورّد للمخزن؟ — لا ❌

أمر الشراء التزام فقط — صفر تأثير على المخزون.

كل نقاط دورة أمر الشراء (approve / confirm / send / close / convert) بتغيّر الحالة + التدقيق فقط. مفيش InventoryReceipt بيتعمل وStockService مبيتنادهش أبداً من PurchaseOrderController. عمود received_quantity بيتكتب بس من PurchaseGrnController:525 (اعتماد الـ GRN) — الأمر بيقراه بس.

اللي بيورّد فعلاً: إذن الاستلام (GRN) — أو الفاتورة في الوضع المباشر — هو اللي بيستدعي ApproveReceipt → StockService::increaseStock (يبني cost layer + movement). أمر الشراء مجرد التزام بالشراء.
🧮

٤ — مصفوفة الترحيل للمخازن (كل الاحتمالات)

مين بيحرّك المخزون فعلاً، حسب إعداد grn_mode (direct / grn / grn_quality).
المستندdirect (افتراضي)grngrn_qualityالآلية
طلب شراء (PR)لالالامستند تخطيط
أمر شراء (PO)لالالاالتزام فقط
إذن استلام (GRN)محظور (403)نعم ↑نعم ↑ينشئ إذن + ApproveReceipt
الفاتورة (Bill)نعم ↑لالاhandleDirectModeStock (في direct بس)
المرتجع (Return)نعم ↓نعم ↓نعم ↓decreaseStock (مستقل عن الوضع)
تكلفة وصولإعادة تقييمإعادة تقييمإعادة تقييميعيد تقييم الطبقات (مش كمية)
الخلاصة: مستند واحد بس بيزوّد المخزون في كل وضع — الفاتورة في direct، والـ GRN في grn/grn_quality (والـ GRN محظور في direct). ده تصميم صحيح، مفيش ازدواج في المسار السليم.
شروط التأثير: الصنف لازم track_inventory=true و له warehouse_id. الخدمات → مصروف (GL) بدون مخزون. (تحذير: صنف مخزني بدون warehouse → بيتسجّل في GL مخزون بدون كمية فعلية — مفيش تحقّق يمنع ده).
⚖️

٥ — الفجوة المحاسبية: مفيش حساب GRNI

في وضع grn، القيمة المخزنية في GL بتتأخّر عن الكمية الفعلية.

الفاتورة دايماً بتعمل DR مخزون للأصناف المخزنية في GL — في كل الأوضاع. لكن إذن الاستلام مبيعملش قيد محاسبي (بيحرّك الكمية والقيمة في الـ subledger بس).

اللحظة (في وضع grn)الكمية الفعليةقيمة المخزون في GL
عند اعتماد الـ GRN↑ تزيدمفيش قيد
عند ترحيل الفاتورةثابتة (مفيش حركة)↑ تتسجّل
المشكلة: بين الاستلام والفاتورة، عندك بضاعة فعلية بدون قيمة مقابلة في GLمفيش حساب وسيط "بضاعة مستلمة ولم تُفوتر" (GRNI)، ومفيش حساب فرق سعر (PPV) لو سعر الفاتورة اختلف عن سعر الاستلام. الحل المعياري:
GRN: مدين مخزون / دائن GRNI  ←→  الفاتورة: مدين GRNI / دائن موردين.
🐞

٦ — الأخطاء والفجوات

المسار السليم صحيح — الفجوات في الإلغاء/المرتجع والحالات الحدية.
#المشكلةالمكانالخطورة
1المرتجع مبيقللش received_quantity ولا يعيد حساب حالة الاستلام → أمر الشراء يفضل "مستلَم" غلطPostPurchaseReturnHIGH
2إلغاء GRN معتمد مبيعكسش المخزون ولا يقلّل received_quantity (بس بيقلب الحالة) → مخزون منفوخPurchaseGrnController:440HIGH
3فجوة GRNI (القسم ٥) — قيمة GL تتأخّر، مفيش حساب وسيط ولا PPVPostPurchaseBill:97HIGH
4خطر ازدواج المخزون لو grn_mode اتقرى مختلف بين الاستلام والفاتورة (default=direct) — مفيش فحص "إذن موجود بالفعل"PostPurchaseBill:187,221MED
5صنف مخزني بدون warehouse → DR مخزون في GL بدون كمية فعلية (مفيش تحقّق)PostPurchaseBill:89 vs 195MED
6فاتورة بسطور من أوامر شراء مختلفة → الأوامر التانية حالة فوترتها متتحدّثش (over-bill)PostPurchaseBill:265MED
7converted_at بيتكتب بس مش في $fillable → بيُهمل بصمت (mass-assignment)PurchaseOrderController:489LOW
8المرتجع بيخصم بتكلفة سطر المرتجع مش طبقة FIFO الأصلية → احتمال فرق تقييمPostPurchaseReturn:191LOW

مسموح بالتصميم (مش أخطاء بس فضفاض)

🏁

٧ — الحكم والتوصيات

الحكم العام: دورة المشتريات مبنية صح ومنطقية، والترحيل للمخازن صحيح في المسار السليم (مستند واحد بيورّد لكل وضع، أمر الشراء التزام فقط). الفجوات مركّزة في: المرتجع/إلغاء GRN (مبيرجّعوش الكميات)، والفجوة المحاسبية GRNI، وغياب ربط طلب البيع بأمر الشراء.
الأولويةالتوصية
١المرتجع + إلغاء GRN يقللوا received_quantity ويعيدوا حساب حالة الاستلام (ويعكسوا المخزون في إلغاء GRN)
٢ضيف حساب GRNI (بضاعة مستلمة ولم تُفوتر) + حساب فرق السعر (PPV) لفصل قيمة الاستلام عن الفاتورة
٣فحص "إذن استلام موجود بالفعل لهذا المصدر" قبل إنشاء إذن في الفاتورة (يمنع الازدواج)؛ وألزم warehouse_id للأصناف المخزنية
٤لو محتاج تربط طلب بيع بأمر شراء: ضيف sales_order_id على أمر الشراء (+ propagate من MRP)، وأضف purchase_order_id على الـ pegging عشان تتبع SO→PR→PO
٥تطابق ثلاثي حقيقي (المفوتر ≤ المستلَم)؛ وإصلاح converted_at fillable
تحليل دورة المشتريات والترابط مع المخازن — Moon ERP · أدلة من الكود · 2026-06-16