Inventory Module

نظام إدارة المخزون والمشتريات - Moving-Average Costing & Stock Management

Ready Enhanced Integration

نظرة عامة (Overview)

Inventory Module هو نظام متكامل لإدارة المخزون والمشتريات في المستشفى. يوفر تحكم دقيق في المخزون مع دعم Moving-Average Costing، FIFO Batch Selection، و Stock Reservations.

الميزات الرئيسية

  • Moving-Average Costing: حساب تكلفة المخزون بالمتوسط المتحرك
  • FIFO Batch Selection: اختيار تلقائي للـ batches حسب أقدمها
  • Stock Reservations: حجز المخزون قبل الصرف
  • Batch & Serial Tracking: تتبع Batches والأرقام التسلسلية
  • Multi-Warehouse: دعم عدة مخازن
  • Purchase Management: إدارة المشتريات والمرتجعات
  • Stock Adjustments: تسويات المخزون
  • Real-time Stock Balance: رصيد المخزون لحظياً

الكيانات (Entities)

Entity InventoryProduct

Path: Modules/Inventory/Entities/InventoryProduct.php

الوصف: المنتجات في المخزون - يمكن ربطها مع Drug من DrugCatalog

الخصائص الرئيسية:

- id
- location_id (FK: business_locations)
- name
- sku (Stock Keeping Unit)
- description
- barcode
- track_batch (boolean)
- track_serial (boolean)
- category_id
- is_active

العلاقات (Relationships):

  • location() → BusinessLocation
  • category() → Taxonomy
  • units() → InventoryProductUnit (hasMany)
  • batches() → InventoryBatch (hasMany)
  • stockMovements() → InventoryStockMovement (hasMany)
  • drugs() → Drug (belongsToMany) - Integration مع DrugCatalog

Entity InventoryBatch

Path: Modules/Inventory/Entities/InventoryBatch.php

الوصف: Batches للمنتجات مع تواريخ الإنتاج والانتهاء

الخصائص الرئيسية:

- id
- product_id (FK: inventory_products)
- batch_number
- manufactured_at
- expiry_at
- is_expired (computed)

Entity InventoryWarehouse

Path: Modules/Inventory/Entities/InventoryWarehouse.php

الوصف: المخازن في المستشفى

الخصائص الرئيسية:

- id
- location_id (FK: business_locations)
- name
- code
- type (main, pharmacy, lab, etc.)
- is_default (boolean)
- is_active

Entity InventoryStockMovement

Path: Modules/Inventory/Entities/InventoryStockMovement.php

الوصف: حركات المخزون (IN/OUT) - Posting Pattern

الخصائص الرئيسية:

- id
- movement_uuid (unique identifier)
- location_id
- warehouse_id
- product_id
- batch_id
- unit_id
- quantity_in (decimal)
- quantity_out (decimal)
- quantity_base (base unit quantity)
- unit_cost (moving average)
- total_cost
- movement_type (IN/OUT)
- reference_type (purchase, sale, adjustment, etc.)
- reference_id
- moved_at

المميزات:

  • UUID Tracking: كل حركة لها UUID فريد
  • Moving-Average: حساب التكلفة بالمتوسط المتحرك
  • Polymorphic Reference: ربط مع أي entity (Purchase, Sale, etc.)

Entity InventoryPurchase

Path: Modules/Inventory/Entities/InventoryPurchase.php

الوصف: أوامر الشراء من الموردين

الخصائص الرئيسية:

- id
- location_id
- warehouse_id
- supplier_id
- purchase_no (auto-generated)
- purchase_date
- status_id
- total_amount
- tax_amount
- discount_amount
- notes

العلاقات:

  • lines() → InventoryPurchaseLine (hasMany)
  • supplier() → Supplier
  • warehouse() → InventoryWarehouse

Entity InventoryReservation

Path: Modules/Inventory/Entities/InventoryReservation.php

الوصف: حجز المخزون قبل الصرف الفعلي

الخصائص الرئيسية:

- id
- reservation_uuid
- product_id
- batch_id
- warehouse_id
- quantity_reserved
- reference_type (pharmacy_sale, etc.)
- reference_id
- reserved_at
- released_at (nullable)

الاستخدام:

يتم حجز المخزون أولاً قبل الصرف الفعلي، وعند الصرف يتم تحرير الحجز وخصم المخزون فعلياً.

الخدمات (Services)

Service StockDeductionService

Path: Modules/Inventory/Domain/Services/StockDeductionService.php

الوصف: خصم المخزون مع Moving-Average Costing و FIFO Batch Selection

Main Methods:

// خصم المخزون
deduct(StockDeductionDTO $dto): StockMovementDTO

// اختيار Batches تلقائياً (FIFO)
selectBatchesForDeduction(
    int $productId,
    int $warehouseId,
    float $quantity
): Collection

مثال الاستخدام:

$dto = new StockDeductionDTO(
    productId: 1,
    warehouseId: 1,
    unitId: 1,
    quantity: 10.0,
    locationId: 1,
    referenceType: 'pharmacy_sale',
    referenceId: 123,
    batchId: null // FIFO auto-selection
);

$movement = $stockDeductionService->deduct($dto);

Service StockReservationService

Path: Modules/Inventory/Domain/Services/StockReservationService.php

الوصف: حجز المخزون قبل الصرف

Main Methods:

// حجز مخزون
reserve(StockReservationDTO $dto): InventoryReservation

// تحرير حجز
release(string $reservationUuid): bool

// تحرير جميع الحجوزات لمرجع معين
releaseByReference(string $refType, int $refId): int

Service DrugInventoryMappingService

Path: Modules/Inventory/Application/Services/DrugInventoryMappingService.php

الوصف: ربط الأدوية من DrugCatalog مع InventoryProducts

Main Methods:

// ربط Drug مع InventoryProduct
syncDrugToInventory(
    Drug $drug,
    int $locationId,
    array $additionalData = []
): InventoryProduct

// البحث عن Products لدواء معين
findInventoryProductsForDrug(
    int $drugId,
    ?int $locationId = null
): Collection

// البحث عن Drug من Product
findDrugForInventoryProduct(
    InventoryProduct $product
): ?Drug

// الأدوية المتاحة مع المخزون
getAvailableDrugsWithStock(
    int $locationId,
    ?int $warehouseId = null
): Collection

مثال الاستخدام:

// ربط دواء مع المخزون
$drug = Drug::find(1);
$product = $mappingService->syncDrugToInventory($drug, 1, [
    'sku' => 'MED-001',
    'track_batch' => true
]);

// الحصول على جميع المنتجات لدواء
$products = $mappingService->findInventoryProductsForDrug(1, 1);

// الأدوية المتاحة مع المخزون
$availableDrugs = $mappingService->getAvailableDrugsWithStock(1, 1);

API Endpoints

Base URL: /api/inventory

Products Management

GET /products

قائمة المنتجات مع pagination و filtering

Query Parameters
  • search - البحث في الاسم/SKU
  • category_id - فلترة حسب الفئة
  • is_active - فلترة حسب الحالة
  • include - category,units,batches
POST /products

إنشاء منتج جديد

Request Body
{
  "name": "Paracetamol 500mg",
  "sku": "MED-001",
  "description": "Pain relief medication",
  "track_batch": true,
  "track_serial": false,
  "category_id": 1
}
GET /products/{id}

تفاصيل منتج محدد

PUT /products/{id}

تحديث بيانات منتج

DELETE /products/{id}

حذف منتج

Stock Balance

GET /stock-balance

رصيد المخزون لحظياً

Query Parameters
  • product_id - (required) معرف المنتج
  • warehouse_id - المخزن (optional)
  • batch_id - الـ Batch (optional)
Response Example
{
  "product_id": 1,
  "warehouse_id": 1,
  "total_quantity": 150.0,
  "reserved_quantity": 20.0,
  "available_quantity": 130.0,
  "batches": [
    {
      "batch_id": 1,
      "batch_number": "B001",
      "quantity": 100.0,
      "expiry_at": "2025-12-31"
    }
  ]
}

Purchases Management

GET /purchases

قائمة المشتريات

POST /purchases

إنشاء أمر شراء جديد

Request Body
{
  "warehouse_id": 1,
  "supplier_id": 5,
  "purchase_date": "2025-12-06",
  "lines": [
    {
      "product_id": 1,
      "batch_number": "B001",
      "quantity": 100,
      "unit_id": 1,
      "unit_cost": 10.50,
      "expiry_at": "2025-12-31"
    }
  ],
  "notes": "Monthly stock replenishment"
}
POST /purchases/{id}/receive

استلام بضاعة (يخصم المخزون)

Stock Movements

GET /stock-movements

سجل حركات المخزون

Query Parameters
  • product_id - فلترة حسب المنتج
  • warehouse_id - فلترة حسب المخزن
  • movement_type - IN أو OUT
  • date_from - من تاريخ
  • date_to - إلى تاريخ

Integration

Integration مع Pharmacy Module

التكامل مع Pharmacy Module من خلال:

  • Stock Reservation: حجز المخزون عند إنشاء فاتورة صيدلية
  • Stock Deduction: خصم المخزون عند الصرف الفعلي
  • Stock Release: تحرير المخزون عند الإلغاء
  • Events: الاستماع لـ MedicationDispensedEvent

مثال التكامل:

// في PharmacyDispenseService
public function dispense(PharmacyDispenseDTO $dto, int $saleLineId)
{
    DB::transaction(function () use ($dto, $saleLineId) {
        // 1. Reserve stock
        $reservation = $this->stockReservation->reserve($reservationDTO);

        // 2. Deduct stock
        $movement = $this->stockDeduction->deduct($deductionDTO);

        // 3. Release reservation
        $this->stockRelease->release($reservation->reservation_uuid);

        // 4. Dispatch event
        event(new MedicationDispensedEvent($saleLineId, $movement));
    });
}

Integration مع DrugCatalog Module

الربط مع DrugCatalog من خلال DrugInventoryMappingService:

// ربط Drug مع InventoryProduct
$drug = Drug::find(1);
$product = $mappingService->syncDrugToInventory($drug, $locationId);

// استعلام الأدوية المتاحة
$availableDrugs = $mappingService->getAvailableDrugsWithStock($locationId);

Best Practices

التوصيات

  • استخدم Stock Reservations دائماً قبل الصرف الفعلي
  • اترك batchId فارغاً للسماح بـ FIFO auto-selection
  • استخدم DB::transaction() عند العمل مع حركات المخزون
  • تأكد من تحرير الحجوزات عند الإلغاء
  • استخدم movement_uuid لتتبع الحركات

تحذيرات

  • لا تخصم المخزون مباشرة - استخدم Services المخصصة
  • لا تعدل unit_cost يدوياً - يحسب تلقائياً بـ Moving-Average
  • تأكد من وجود stock كافي قبل الحجز
  • راقب الـ Batches منتهية الصلاحية

Technical Details

Moving-Average Costing

نظام حساب التكلفة بالمتوسط المتحرك:

// الصيغة
New Average Cost = (
    (Current Stock × Current Average Cost) +
    (Incoming Stock × Incoming Cost)
) / (Current Stock + Incoming Stock)

FIFO Batch Selection

اختيار الـ Batches تلقائياً حسب الأقدم:

SELECT * FROM inventory_batches
WHERE product_id = ?
  AND warehouse_id = ?
  AND available_quantity > 0
  AND (expiry_at IS NULL OR expiry_at > NOW())
ORDER BY manufactured_at ASC, created_at ASC

Stock Balance Calculation

حساب الرصيد لحظياً:

SELECT
    SUM(quantity_in) - SUM(quantity_out) as available_quantity,
    SUM(CASE WHEN reserved THEN quantity ELSE 0 END) as reserved_quantity
FROM inventory_stock_movements
WHERE product_id = ?
  AND warehouse_id = ?

إحصائيات Module

10+
Entities
8+
Services
40+
API Endpoints
80+
Tests