inventory-management

star 1

نظام إدارة المخزون والمواد الاستهلاكية (مختلف عن المستودعات والأصول الثابتة). استخدم هذا الـ skill عند: إدارة مخزون المواد الاستهلاكية، الأدوات المكتبية، قطع الغيار، المواد الطبية، إدارة الحد الأدنى والأعلى، التنبؤ بالطلب، إدارة الموردين، أوامر التوريد التلقائية، تتبع الصلاحية، أو FIFO/LIFO.

mtatwercom By mtatwercom schedule Updated 2/14/2026

name: inventory-management description: > نظام إدارة المخزون والمواد الاستهلاكية (مختلف عن المستودعات والأصول الثابتة). استخدم هذا الـ skill عند: إدارة مخزون المواد الاستهلاكية، الأدوات المكتبية، قطع الغيار، المواد الطبية، إدارة الحد الأدنى والأعلى، التنبؤ بالطلب، إدارة الموردين، أوامر التوريد التلقائية، تتبع الصلاحية، أو FIFO/LIFO.

Inventory Management System

إدارة المخزون

Domain Model

// === المنتج/الصنف ===
public class InventoryItem : BaseAuditableEntity
{
    public int Id { get; set; }
    public string ItemCode { get; set; } = string.Empty;        // INV-MAT-001
    public string NameAr { get; set; } = string.Empty;
    public string? NameEn { get; set; }
    public string? Description { get; set; }
    public string? Barcode { get; set; }
    public int CategoryId { get; set; }
    public string UnitOfMeasure { get; set; } = string.Empty;   // قطعة، كرتون، لتر
    
    // Stock Levels
    public decimal CurrentStock { get; set; }
    public decimal MinimumStock { get; set; }                    // الحد الأدنى
    public decimal MaximumStock { get; set; }                    // الحد الأعلى
    public decimal ReorderPoint { get; set; }                    // نقطة إعادة الطلب
    public decimal ReorderQuantity { get; set; }                 // كمية إعادة الطلب
    
    // Costing
    public CostingMethod CostingMethod { get; set; }
    public decimal AverageCost { get; set; }
    public decimal LastPurchasePrice { get; set; }
    
    // Tracking
    public bool TrackExpiry { get; set; }                        // تتبع الصلاحية
    public bool TrackBatch { get; set; }                         // تتبع الدُفعات
    public bool TrackSerialNumber { get; set; }
    
    // Status
    public bool IsActive { get; set; } = true;
    public int? PreferredSupplierId { get; set; }
    public int LeadTimeDays { get; set; }                        // وقت التوريد

    public List<StockBatch> Batches { get; set; } = new();
    public List<StockMovement> Movements { get; set; } = new();
}

public enum CostingMethod
{
    WeightedAverage,    // المتوسط المرجح
    FIFO,               // الوارد أولاً صادر أولاً
    LIFO,               // الوارد أخيراً صادر أولاً
    SpecificCost        // تكلفة محددة
}

// === الدُفعات ===
public class StockBatch
{
    public int Id { get; set; }
    public int ItemId { get; set; }
    public string BatchNumber { get; set; } = string.Empty;
    public decimal Quantity { get; set; }
    public decimal UnitCost { get; set; }
    public DateTime ReceivedDate { get; set; }
    public DateTime? ExpiryDate { get; set; }
    public int WarehouseId { get; set; }
    public string? LocationBin { get; set; }                     // رقم الحاوية/الرف
    public BatchStatus Status { get; set; }
}

// === حركات المخزون ===
public class StockMovement : BaseAuditableEntity
{
    public int Id { get; set; }
    public string MovementNumber { get; set; } = string.Empty;
    public int ItemId { get; set; }
    public MovementType Type { get; set; }
    public MovementDirection Direction { get; set; }
    
    public decimal Quantity { get; set; }
    public decimal UnitCost { get; set; }
    public decimal TotalCost => Quantity * UnitCost;
    
    public int? BatchId { get; set; }
    public int WarehouseId { get; set; }
    public int? FromWarehouseId { get; set; }
    public int? ToWarehouseId { get; set; }
    
    // Reference
    public string? ReferenceType { get; set; }                   // PO, SO, Transfer
    public int? ReferenceId { get; set; }
    
    public int? RequestedById { get; set; }
    public int? ApprovedById { get; set; }
    public string? Reason { get; set; }
    public DateTime MovementDate { get; set; }
    
    // Running totals
    public decimal StockBefore { get; set; }
    public decimal StockAfter { get; set; }
}

public enum MovementType
{
    Receipt,            // استلام من مورد
    Issue,              // صرف
    Transfer,           // تحويل بين مستودعات
    Return,             // إرجاع
    Adjustment,         // تسوية
    Disposal,           // إتلاف
    Consumption,        // استهلاك
    Production          // إنتاج
}

// === طلب صرف مواد ===
public class MaterialRequest : BaseAuditableEntity
{
    public int Id { get; set; }
    public string RequestNumber { get; set; } = string.Empty;
    public int RequestorId { get; set; }
    public int DepartmentId { get; set; }
    public string PurposeAr { get; set; } = string.Empty;
    public MaterialRequestStatus Status { get; set; }
    public int? ApprovalWorkflowId { get; set; }
    public List<MaterialRequestItem> Items { get; set; } = new();
}

Auto-Reorder Service

public class AutoReorderService
{
    // Run daily - check items below reorder point
    public async Task CheckAndCreateReordersAsync()
    {
        var lowStockItems = await _context.InventoryItems
            .Where(i => i.IsActive && i.CurrentStock <= i.ReorderPoint)
            .Include(i => i.Batches.Where(b => b.Status == BatchStatus.Active))
            .ToListAsync();

        foreach (var item in lowStockItems)
        {
            // Check if there's already a pending PO
            var hasPendingOrder = await _context.PurchaseOrderItems
                .AnyAsync(po => po.ItemId == item.Id && 
                    po.PurchaseOrder.Status == PurchaseOrderStatus.Pending);
            
            if (hasPendingOrder) continue;

            // Calculate order quantity
            var orderQty = Math.Max(item.ReorderQuantity, item.MaximumStock - item.CurrentStock);

            // Check expiring batches
            var expiringBatches = item.Batches
                .Where(b => b.ExpiryDate.HasValue && 
                    b.ExpiryDate.Value <= DateTime.UtcNow.AddDays(30))
                .ToList();

            await _purchaseService.CreateAutoReorderAsync(new AutoReorderRequest
            {
                ItemId = item.Id,
                Quantity = orderQty,
                SupplierId = item.PreferredSupplierId,
                Priority = item.CurrentStock <= item.MinimumStock 
                    ? OrderPriority.Urgent : OrderPriority.Normal,
                ExpiringBatchCount = expiringBatches.Count
            });

            await _notificationService.SendAsync(new()
            {
                EventType = "inventory.low_stock",
                RecipientRole = "WarehouseManager",
                Data = new { item.NameAr, item.CurrentStock, item.MinimumStock }
            });
        }
    }
}

Demand Forecasting

public class DemandForecastService
{
    // Simple moving average forecast
    public async Task<ForecastResult> ForecastDemandAsync(int itemId, int monthsAhead = 3)
    {
        var history = await _context.StockMovements
            .Where(m => m.ItemId == itemId && m.Direction == MovementDirection.Out)
            .Where(m => m.MovementDate >= DateTime.UtcNow.AddMonths(-12))
            .GroupBy(m => new { m.MovementDate.Year, m.MovementDate.Month })
            .Select(g => new { Period = g.Key, TotalQty = g.Sum(m => m.Quantity) })
            .OrderBy(g => g.Period.Year).ThenBy(g => g.Period.Month)
            .ToListAsync();

        if (history.Count < 3)
            return new ForecastResult { Confidence = "Low", Method = "Insufficient data" };

        // 3-month weighted moving average
        var weights = new[] { 0.5m, 0.3m, 0.2m };
        var recent = history.TakeLast(3).Select(h => h.TotalQty).ToArray();
        var forecast = recent.Zip(weights, (qty, w) => qty * w).Sum();

        return new ForecastResult
        {
            ItemId = itemId,
            ForecastedMonthlyDemand = forecast,
            RecommendedStock = forecast * monthsAhead,
            Confidence = history.Count >= 6 ? "High" : "Medium",
            Method = "Weighted Moving Average"
        };
    }
}

SQL Schema

CREATE SCHEMA [Inventory];

CREATE TABLE [Inventory].[Items] (
    [Id] INT IDENTITY(1,1) PRIMARY KEY,
    [ItemCode] NVARCHAR(50) NOT NULL UNIQUE,
    [NameAr] NVARCHAR(300) NOT NULL,
    [Barcode] NVARCHAR(50) NULL,
    [CategoryId] INT NOT NULL,
    [UnitOfMeasure] NVARCHAR(20) NOT NULL,
    [CurrentStock] DECIMAL(18,3) NOT NULL DEFAULT 0,
    [MinimumStock] DECIMAL(18,3) NOT NULL DEFAULT 0,
    [MaximumStock] DECIMAL(18,3) NOT NULL DEFAULT 0,
    [ReorderPoint] DECIMAL(18,3) NOT NULL DEFAULT 0,
    [CostingMethod] NVARCHAR(20) NOT NULL DEFAULT 'WeightedAverage',
    [AverageCost] DECIMAL(18,4) NOT NULL DEFAULT 0,
    [TrackExpiry] BIT NOT NULL DEFAULT 0,
    [TrackBatch] BIT NOT NULL DEFAULT 0,
    [IsActive] BIT NOT NULL DEFAULT 1,
    [PreferredSupplierId] INT NULL
);

CREATE TABLE [Inventory].[StockBatches] (
    [Id] INT IDENTITY(1,1) PRIMARY KEY,
    [ItemId] INT NOT NULL FOREIGN KEY REFERENCES [Inventory].[Items]([Id]),
    [BatchNumber] NVARCHAR(50) NOT NULL,
    [Quantity] DECIMAL(18,3) NOT NULL,
    [UnitCost] DECIMAL(18,4) NOT NULL,
    [ReceivedDate] DATE NOT NULL,
    [ExpiryDate] DATE NULL,
    [WarehouseId] INT NOT NULL,
    [Status] NVARCHAR(20) NOT NULL DEFAULT 'Active'
);

CREATE TABLE [Inventory].[Movements] (
    [Id] INT IDENTITY(1,1) PRIMARY KEY,
    [MovementNumber] NVARCHAR(50) NOT NULL UNIQUE,
    [ItemId] INT NOT NULL FOREIGN KEY REFERENCES [Inventory].[Items]([Id]),
    [Type] NVARCHAR(20) NOT NULL,
    [Direction] NVARCHAR(5) NOT NULL,
    [Quantity] DECIMAL(18,3) NOT NULL,
    [UnitCost] DECIMAL(18,4) NOT NULL,
    [WarehouseId] INT NOT NULL,
    [StockBefore] DECIMAL(18,3) NOT NULL,
    [StockAfter] DECIMAL(18,3) NOT NULL,
    [MovementDate] DATETIME2 NOT NULL DEFAULT GETUTCDATE(),
    INDEX IX_Item_Date ([ItemId], [MovementDate] DESC)
);

-- Expiring Items Alert
CREATE VIEW [Inventory].[vw_ExpiringItems] AS
SELECT b.*, i.NameAr, i.ItemCode
FROM [Inventory].[StockBatches] b
JOIN [Inventory].[Items] i ON i.Id = b.ItemId
WHERE b.ExpiryDate <= DATEADD(DAY, 30, GETDATE()) 
    AND b.Status = 'Active' AND b.Quantity > 0;
Install via CLI
npx skills add https://github.com/mtatwercom/masarat-erp --skill inventory-management
Repository Details
star Stars 1
call_split Forks 0
navigation Branch main
article Path SKILL.md
Occupations
More from Creator