مراجعة للدورة اللي وصفتها: طلب شراء → أمر شراء → استلام مبدئي على الأمر → استلام جزئي/كلي → إذن إضافة للمخزن → فاتورة على أساس المُستَلم فعلًا. الخلاصة: الدورة موجودة كمستندات وحالات بالفعل — بس محاسبتها محاسبة دورة مبسّطة. التحليل مبني على فحص كود مباشر، متقاطع من مصدرين مستقلّين (Codex deep code audit + Fable 5).
PR → PO → GRN (ببوابة جودة) → إذن إضافة → رصيد: كلها موجودة وتتفعّل بضبط grn_mode=grn_quality + إعدادات. بدون كود.
مفيش GR/IR (بضاعة مستلمة بلا فاتورة) ولا 3-way match. الفاتورة بتخصم "مخزون/دائنون" مباشرة في كل الأوضاع، بلا التحقق من المُستَلم.
محور مادي (grn_mode) + محور محاسبي جديد (inventory_recognition_point) — نفس نمط sales.cogs_recognition_point المُصلَّح. direct يفضل الافتراضي.
للمصنع الدوائي (دُفعات/صلاحية + بوابة جودة إلزامية)، ده المرجع الصح. لاحظ فصل الاستلام عن الفاتورة محاسبيًا عبر حساب GR/IR.
| الخطوة | القيد | ملاحظة |
|---|---|---|
| ③ الاستلام الفعلي (إذن إضافة) | DR مخزون / CR GR-IR clearing = كمية مقبولة × تكلفة الأمر | الرصيد يدخل هنا. GR-IR = التزام "بضاعة مستلمة لم تصلها فاتورة" — رصيده آخر الشهر = مخصص المستلم-غير-المفوتر. |
| ④ الفاتورة (3-way matched) | DR GR-IR DR ض.ق.م مدخلات ±DR/CR فرق سعر PPV / CR دائنون | الفاتورة تصفّي GR-IR (مش تخصم مخزون تاني). فرق السعر يروح حساب PPV. الضريبة تفضل عند الفاتورة (صح قانونيًا). |
| الدفع | DR دائنون / CR البنك/الخزنة | موجود (PurchasePayment). |
| المرتجع | DR GR-IR (لو مش مفوتر) أو DR دائنون / CR مخزون | موجود (PurchaseReturn). |
إعداد purchases.grn_mode = direct | grn | grn_quality (افتراضي direct). ده اللي بيحدّد إزاي البضاعة بتدخل.
| الوضع | وصول البضاعة / إدخال الرصيد | قيد الاستلام | قيد الفاتورة | تحقّق كمية |
|---|---|---|---|---|
direct(الافتراضي) | مفيش مستند استلام — الفاتورة نفسها تنشئ InventoryReceipt وتعتمده بكمية الفاتورة. | — | DR مخزون/CR دائنون بكمية الفاتورة + يضيف الرصيد | لا |
grn | GRN مسودة → اعتماد ينشئ InventoryReceipt ويضيف الرصيد (كمية GRN). | لا قيد | DR مخزون/CR دائنون نفسها (مش بيصفّي حاجة) | لا |
grn_quality | GRN → بوابة جودة (مقبول/مرفوض) → اعتماد يضيف الرصيد بالكمية المقبولة + دُفعة/صلاحية. | لا قيد | DR مخزون/CR دائنون نفسها | لا |
direct: الفاتورة بتقود المخزون — ممكن تعمل رصيد من فاتورة حتى لو البضاعة ما وصلتش.grn/grn_quality: الرصيد بيدخل عند اعتماد GRN بلا أي قيد GL، والفاتورة لسه بتخصم "مخزون" تاني → الـGL يتحدّث عند الفاتورة مش الاستلام، ومفيش GR-IR يربطهم. نفس باج الـsales-partial-issue بالظبط.📌 ملاحظات تدفّق (Codex): PR→PO موصول (requests/{id}/convert-to-order) لكن مفيش تحويل PO→GRN ولا GRN→فاتورة (تُنشأ يدويًا بمرجع). "reorder المخزون → PR" مش موصول (المخزون فيه تنبيهات بس)؛ "MRP الإنتاج → PR" موصول. الفاتورة-من-الأمر بتاخد المتبقّي المطلوب لا المُستَلم.
التشخيص الحاكم: مستندات دورة صح، ومحاسبة دورة مبسّطة. 12 خطأ مُرتّب + باجان كامنان.
| # | الخطأ | الشدّة | الأثر · file:line |
|---|---|---|---|
| 1 | مفيش محاسبة GR/IR إطلاقًا | Critical | الفاتورة تخصم مخزون وتدائن دائنون مباشرة؛ الاستلام مالوش قيد → التزام "مستلم-غير-مفوتر" غير مرئي، القوائم تُظهر التزامات أقل. PostPurchaseBill.php:96-151 · بحث exact عن grni/grir = صفر. |
| 2 | direct: الفاتورة تقود المخزون الفعلي | Critical | اعتماد الفاتورة يسجّل دائنون ويزيد المخزون بلا حدث استلام مستقل → رصيد من فواتير لبضاعة ما وصلتش. PostPurchaseBill.php:185-190, 233-261 · الافتراضي direct SettingDefinitionSeeder.php:965. |
| 3 | غير-direct: الفاتورة تخصم مخزون رغم إضافته في GRN، بلا قيد استلام | Critical | المخزون الفرعي يتحدّث عند GRN، والـGL عند الفاتورة → فرق توقيت + فشل تدقيق، بلا GR-IR. PurchaseGrnController.php:365-406 + PostPurchaseBill.php:85-104. |
| 4 | كمية الفاتورة لا تُطابَق بالمُستَلم — فوترة بضاعة غير مستلمة | High | الفاتورة-من-الأمر تاخد "المطلوب−المفوتر" لا "المستلم". PurchaseBillController.php:427-453 · PurchaseOrderItem.php:95-97. |
| 5 | فاتورة يدوية تقدر تتخطّى كمية الأمر (over-bill) | High | مفيش سقف مقابل الأمر/المستلم؛ يزيد billed_quantity بلا تحقق. StorePurchaseBillRequest.php:38-40 · PurchaseBillController.php:509-528. |
| 6 | GRN يقدر يستلم أكتر من الأمر (over-receive) | High | مفيش تحقق مقابل remainingReceiveQuantity(). PurchaseGrnController.php:508-525. |
| 7 | اعتماد GRN يدمج "المبدئي" و"إذن الإضافة" في خطوة واحدة | High | مفيش بوابة إضافة-للمخزن منفصلة؛ الرصيد يدخل فور اعتماد GRN. PurchaseGrnController.php:365-406. |
| 8 | direct: التقييم بتكلفة الفاتورة لا تكلفة الاستلام | High | طبقة تكلفة المخزون تاخد سعر الفاتورة → فروق السعر غير مرئية. PostPurchaseBill.php:247-253. |
| 9 | grn_quality: مقبول+مرفوض غير مُقيَّد بكمية GRN | Medium | ممكن قبول أكتر من المستلم فعلًا → تضخّم رصيد. PurchaseGrnController.php:290-306. |
| 10 | إذن إضافة يدوي يُعتمد تلقائيًا بلا PO/GRN | Medium | تخطّي ضوابط المشتريات + 3-way. InventoryReceiptController.php:126-130. |
| 11 | مرتجع مستقل يخصم مخزون بلا ربط بفاتورة | Medium | PostPurchaseReturn.php:176-207. |
| 12 | المرتجع يخصم بتكلفة المرتجع لا المتوسط الحالي (WAC) | Medium | تشويه تقييم في المتوسط المرجّح. StockService.php:135-139. |
PurchaseGrnController::getGrnMode() يرجع 'grn' عند غياب الإعداد (سطر 494) بينما PostPurchaseBill يرجع 'direct' (سطر 187) → شركة بلا صف إعداد: GRN يضيف رصيد والفاتورة تعيد الاستلام → مخزون مضاعف. الإصلاح: توحيد الاثنين على 'direct'. (2) فحص الوضع فضفاض (!== 'direct') → أي نص غير معروف يوقف إدخال الرصيد → يُقيَّد بالـenum.الفكرة: محورين مستقلّين، كلاهما على مستوى الشركة. الوضع الحالي يفضل الافتراضي — الدورة الصح opt-in.
| الإعداد | القيم | المعنى |
|---|---|---|
purchases.grn_mode موجود | direct / grn / grn_quality | التدفّق المادي: مين/امتى البضاعة تدخل المخزن. |
purchases.inventory_recognition_point جديد | bill (افتراضي) / receipt | المحاسبة: أي مستند يحمل قيمة المخزون. receipt = GR-IR (نفس نمط sales.cogs_recognition_point). |
grn_mode = grn_quality → الخطوات 3→5 (GRN مبدئي + جودة + إذن إضافة + استلام جزئي).enable_purchase_requests = true → الخطوة 1 (أصلًا افتراضي).approval_workflow = simple + thresholds → حوكمة الأمر (الخطوة 2).inventory.auto_approve_receipts = false · auto_approve_issues = false · allow_negative = false.received_quantity/billed_quantity)، الحارس ناقص.require_po_for_bill).purchases.grni_account_id (GR-IR، ضمن الالتزامات — نفس نمط landed_cost_clearing_account_id) + purchases.price_variance_account_id (PPV). عند اعتماد GRN (في وضع receipt): DR مخزون/CR GR-IR (idempotency purchase_grn:{id}). في الفاتورة: تخصم GR-IR بدل المخزون، والفرق لـPPV. direct يفضل يشتغل زي ما هو (المسارات الجديدة ما تتنفّذش). قاعدة تحقّق: receipt يتطلّب grn_mode≠direct + grni معرّف.المطابقة على مستوى سطر أمر الشراء (لا سطر GRN — مبالغة v1). الأعمدة موجودة، الحارس بس ناقص.
billed_qty + bill_qty ≤ received_qty × (1+tolerance)؛ رفض غير كده. إعداد جديد purchases.billing_tolerance_percent (افتراضي 0).received=60 + DR مخزون/CR GR-IR بـ60. فاتورة 60 تعدّي وتصفّي GR-IR. فاتورة الـ40 تُحجب لحد GRN#2. آخر الشهر: رصيد GR-IR = المستلم-غير-المفوتر (مخصص صحيح). و"مفوتر-غير-مستلم" يبقى مستحيلًا بنيويًا — ده المقصد.grn_mode=grn_quality · enable_purchase_requests=true · approval_workflow=simple+thresholds · auto_approve_receipts/issues=false · allow_negative=false.billing_tolerance_percent. require_po_for_bill.inventory_recognition_point + grni_account_id + price_variance_account_id. قيد GR-IR عند اعتماد GRN؛ الفاتورة تصفّي GR-IR + PPV؛ عكوس الإلغاء/المرتجع؛ اختبارات Pest على نمط CogsAtDeliveryCancelTest.| الإعداد | القيمة | المرحلة |
|---|---|---|
purchases.grn_mode | grn_quality | Phase 0 |
purchases.enable_purchase_requests | true | Phase 0 |
purchases.approval_workflow + thresholds | simple (حدود حسب المالك) | Phase 0 |
purchases.use_supplier_ap_account | true | Phase 0 |
purchases.price_includes_tax | false | Phase 0 |
inventory.auto_approve_receipts / auto_approve_issues | false | Phase 0 |
inventory.allow_negative | false | Phase 0 |
purchases.inventory_recognition_point | receipt | Phase 2 |
purchases.grni_account_id | حساب GR-IR جديد (التزامات) | Phase 2 |
purchases.price_variance_account_id | حساب فرق سعر PPV جديد | Phase 2 |
purchases.billing_tolerance_percent · require_po_for_bill | 0 · true | Phase 1 |
direct الحالي، الاستلام والفاتورة حدث واحد — فوقت التحويل يكون المستلم-غير-المفوتر صفر بنيويًا → GR-IR يفتح على صفر، بلا قيد رصيد افتتاحي. الخطوات: اختر بداية شهر → اعتمد الفواتير المسودة القديمة → أنشئ حسابي GR-IR وPPV → فعّل grn_quality → فعّل inventory_recognition_point=receipt. المستندات القديمة تفضل تاريخًا صحيحًا، مفيش إعادة ترحيل.sales.cogs_recognition_point + الترحيل عند ApproveIssue. ده النمط اللي ننسخه: sales-partial-issue-accounting · fix-plan.