وحدتا Modules\Inventory وModules\Purchases هما الجاران الأكثر التصاقاً بوحدة التصنيع المقترحة: منهما تُحجَز المواد، وإليهما تُصرَف للإنتاج، ومنهما تُستلَم المنتجات النهائية، وعبرهما يُطلِق نظام تخطيط الاحتياجات (MRP) طلبات الشراء. هذا القسم يوثّق ما هو جاهز للاستخدام كما هو، وما ينقص ويجب بناؤه، مع تحديد نقاط الربط البرمجية بدقّة.
سيِّد المنتجات مملوك لوحدة Modules\Core ومشترَك بين كل الوحدات؛ المخزون والمشتريات يشيران إليه بمفتاح خارجي فقط. البنية الأساسية لوحدات القياس والتحويلات قوية وجاهزة لإعادة الاستخدام في قوائم المكوّنات (BOM) ومسارات التصنيع.
products يحوي حقول إعادة الطلب جاهزة: min_stock_level وmax_stock_level وreorder_point، إضافةً إلى track_inventory ووحدة الأساس base_unit_id.product_variants وعلَم has_variants.unit_groups ثم units بمعامل تحويل conversion_factor، مع تحويلات على مستوى المنتج في product_units.ProductType لا يحوي سوى Product وService — لا يوجد تمييز مادة خام / تحت التشغيل (WIP) / منتج نهائي، ولا procurement_type (تصنيع مقابل شراء)، ولا material_ownership (ملكيتنا مقابل أمانة العميل). استنتاجالتتبّع بالرقم التسلسلي (Serial) مكتمل ومُدار بجدول product_serials مع دورة حياة مُلزَمة عند الاستلام والصرف. أمّا التتبّع بالدُفعة (Batch/Lot) فهو ناقص جوهرياً: لا يوجد جدول رئيسي للدُفعات ولا أرصدة على مستوى الدُفعة؛ batch_number وexpiry_date مجرّد حقول نصّية على بنود الاستلام/الصرف. أرصدة المخزون وطبقات التكلفة مفهرسة بـ(منتج/متغيّر/مستودع) فقط دون الدُفعة.
فجوة التصنيع يحتاج نَسَب الدُفعات (أي دُفعات المواد الخام دخلت في أي دُفعة منتج نهائي) والصرف وفق الأقرب انتهاءً (FEFO) — وهذا غير ممكن في النموذج الحالي ويتطلّب جدول أرصدة دُفعات وخدمة مخزون واعية بالدُفعة. استنتاج
كل حركات المخزون تمرّ عبر Modules\Inventory\Services\StockService. تدعم الخدمة طريقتَي تقييم: الوارد أولاً صادر أولاً (FIFO) عبر طبقات التكلفة، والمتوسّط المرجّح (WAC) عبر الرصيد. الطريقة تُضبَط على مستوى الشركة بإعداد inventory.valuation_method (الافتراضي weighted_avg).
increaseStock() يزيد الكمية ويُحدِّث المتوسّط ويُنشئ دائماً طبقة تكلفة inventory_cost_layers وحركة في inventory_movements.decreaseStock() يستهلك أقدم الطبقات في وضع FIFO أو يستخدم متوسّط التكلفة في وضع WAC.getIssueCost() يحسب تكلفة الصرف للمعاينة دون استهلاك، وgetProductCost() يُرجِع التكلفة الجارية.reserved_quantity موجود وكذلك خاصية available_quantity (الكمية المتاحة = الكمية − المحجوزة)، لكن لا توجد أي دالّة تكتب قيمة الحجز. على التصنيع بناء منطق الحجز والإفراج بنفسه. استنتاجMovementType لا تشمل حركات إنتاجية (صرف إنتاج / استلام إنتاج / هالك / إعادة تشغيل).cost_method على المنتج لا تقرؤه الخدمة.النمط المرجعي الذي يجب أن يحاكيه التصنيع لاستلام المنتجات النهائية موجود في PurchaseGrnController::approve(): يُنشئ مستند استلام InventoryReceipt بحالة مسوّدة ثم يستدعي ApproveReceipt::execute() الذي يزيد المخزون فعلياً ويُنشئ طبقة التكلفة. أوضاع الاستلام (purchases.grn_mode): مباشر، أو باستلام، أو باستلام مع فحص جودة.
SequenceService::generateNext($company,'inventory','receipt').InventoryReceipt بحالة Draft وبنوده مع تمرير batch_number وexpiry_date وserial_numbers.app(ApproveReceipt::class)->execute($receipt, $userId) لزيادة المخزون.سيُنشئ تخطيط الاحتياجات (MRP) طلبات شراء آلياً. الطلب موجود في purchase_requests (مع needed_by وpriority وcost_center_id وconverted_to_order_id)، ويتحوّل إلى أمر شراء عبر مسار convertFromRequest الذي يَسِم الطلب بحالة Converted. حدود إعادة الطلب مخزّنة على المنتج، لكن وحدة التنبيهات ReorderAlertController تنبيهية فقط ولا تُنشئ طلبات شراء آلياً.
فجوة إنشاء طلب الشراء يقع اليوم داخل المتحكّم فقط؛ يُنصَح باستخراج إجراء Purchases\Actions\CreatePurchaseRequest ليستدعيه MRP داخلياً دون المرور عبر واجهة HTTP. استنتاج
نقطة جوهرية: وحدة المخزون لا تُرحِّل أي قيود محاسبية — حركة المخزون منفصلة عن الترحيل المحاسبي. الوحدة المستهلِكة هي من تكتب القيد دائماً عبر البوّابة الوحيدة Modules\Accounting\Actions\CreateJournalEntry:
PostPurchaseBill يُرحِّل مدين المخزون / دائن الموردين.PostSalesInvoice::handleCogsAndStock يحسب التكلفة عبر StockService ثم يُرحِّل مدين تكلفة البضاعة المباعة / دائن المخزون ويستدعي decreaseStock وينشئ InventoryIssue.| الغرض | الاستدعاء / الجدول | الحالة |
|---|---|---|
| حجز المواد عند إطلاق أمر الإنتاج | زيادة inventory_stock_balances.reserved_quantity (عبر إجراء جديد ReserveOrderComponents) | يُبنى |
| صرف المواد للإنتاج (خام ← تحت التشغيل) | ApproveIssue::execute($issue) ← StockService::decreaseStock + قيد مدين WIP/دائن المخزون عبر CreateJournalEntry | جاهز جزئياً |
| استلام المنتج النهائي (تحت التشغيل ← نهائي) | InventoryReceipt (Draft) ← ApproveReceipt::execute($receipt) + قيد مدين المخزون النهائي/دائن WIP | جاهز |
| إطلاق طلبات الشراء من MRP | إنشاء PurchaseRequest + بنوده، ثم convertFromRequest ← PurchaseOrder | جاهز جزئياً |
| تكلفة الصرف للمعاينة | StockService::getIssueCost() / getProductCost() | جاهز |
| مواقع الورشة وتحت التشغيل | تمثيلها كسجلّات warehouses (لا يوجد نموذج مواقع/أرفف فرعية) | يُبنى |
reserved_quantity.CreateJournalEntry.