نظام إدارة المخزون والمشتريات - Moving-Average Costing & Stock Management
Inventory Module هو نظام متكامل لإدارة المخزون والمشتريات في المستشفى. يوفر تحكم دقيق في المخزون مع دعم Moving-Average Costing، FIFO Batch Selection، و Stock Reservations.
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
location() → BusinessLocationcategory() → Taxonomyunits() → InventoryProductUnit (hasMany)batches() → InventoryBatch (hasMany)stockMovements() → InventoryStockMovement (hasMany)drugs() → Drug (belongsToMany) - Integration مع DrugCatalogPath: Modules/Inventory/Entities/InventoryBatch.php
الوصف: Batches للمنتجات مع تواريخ الإنتاج والانتهاء
- id - product_id (FK: inventory_products) - batch_number - manufactured_at - expiry_at - is_expired (computed)
Path: Modules/Inventory/Entities/InventoryWarehouse.php
الوصف: المخازن في المستشفى
- id - location_id (FK: business_locations) - name - code - type (main, pharmacy, lab, etc.) - is_default (boolean) - is_active
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
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() → Supplierwarehouse() → InventoryWarehousePath: 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)
يتم حجز المخزون أولاً قبل الصرف الفعلي، وعند الصرف يتم تحرير الحجز وخصم المخزون فعلياً.
Path: Modules/Inventory/Domain/Services/StockDeductionService.php
الوصف: خصم المخزون مع Moving-Average Costing و FIFO Batch Selection
// خصم المخزون
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);
Path: Modules/Inventory/Domain/Services/StockReservationService.php
الوصف: حجز المخزون قبل الصرف
// حجز مخزون
reserve(StockReservationDTO $dto): InventoryReservation
// تحرير حجز
release(string $reservationUuid): bool
// تحرير جميع الحجوزات لمرجع معين
releaseByReference(string $refType, int $refId): int
Path: Modules/Inventory/Application/Services/DrugInventoryMappingService.php
الوصف: ربط الأدوية من DrugCatalog مع InventoryProducts
// ربط 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/inventory
/products
قائمة المنتجات مع pagination و filtering
search - البحث في الاسم/SKUcategory_id - فلترة حسب الفئةis_active - فلترة حسب الحالةinclude - category,units,batches/products
إنشاء منتج جديد
{
"name": "Paracetamol 500mg",
"sku": "MED-001",
"description": "Pain relief medication",
"track_batch": true,
"track_serial": false,
"category_id": 1
}
/products/{id}
تفاصيل منتج محدد
/products/{id}
تحديث بيانات منتج
/products/{id}
حذف منتج
/stock-balance
رصيد المخزون لحظياً
product_id - (required) معرف المنتجwarehouse_id - المخزن (optional)batch_id - الـ Batch (optional){
"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
قائمة المشتريات
/purchases
إنشاء أمر شراء جديد
{
"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"
}
/purchases/{id}/receive
استلام بضاعة (يخصم المخزون)
/stock-movements
سجل حركات المخزون
product_id - فلترة حسب المنتجwarehouse_id - فلترة حسب المخزنmovement_type - IN أو OUTdate_from - من تاريخdate_to - إلى تاريخالتكامل مع Pharmacy Module من خلال:
// في 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));
});
}
الربط مع DrugCatalog من خلال DrugInventoryMappingService:
// ربط Drug مع InventoryProduct
$drug = Drug::find(1);
$product = $mappingService->syncDrugToInventory($drug, $locationId);
// استعلام الأدوية المتاحة
$availableDrugs = $mappingService->getAvailableDrugsWithStock($locationId);
batchId فارغاً للسماح بـ FIFO auto-selectionmovement_uuid لتتبع الحركاتunit_cost يدوياً - يحسب تلقائياً بـ Moving-Averageنظام حساب التكلفة بالمتوسط المتحرك:
// الصيغة
New Average Cost = (
(Current Stock × Current Average Cost) +
(Incoming Stock × Incoming Cost)
) / (Current Stock + Incoming Stock)
اختيار الـ 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
حساب الرصيد لحظياً:
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 = ?