تحليل تفصيلي بالكود · 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