کانال نوحه و مداحی ناب

Stored Procedure های کامل برای مدیریت جداول سیستم انبارداری

در اینجا Stored Procedure های کامل برای مدیریت جداول سیستم انبارداری ارائه می‌دهم:

1. Stored Procedure برای مدیریت محصولات

1.1. گرفتن لیست محصولات

sql
DELIMITER $$

CREATE PROCEDURE sp_GetProducts(
    IN p_CompanyID INT,
    IN p_CategoryID INT,
    IN p_IsActive BOOLEAN,
    IN p_SearchTerm NVARCHAR(200),
    IN p_Page INT,
    IN p_PageSize INT,
    OUT p_TotalRecords INT,
    OUT p_TotalPages INT
)
BEGIN
    -- محاسبه رکوردهای کل
    SELECT COUNT(*) INTO p_TotalRecords
    FROM Products p
    WHERE p.CompanyID = p_CompanyID
    AND (p_CategoryID IS NULL OR p.CategoryID = p_CategoryID)
    AND p.IsActive = p_IsActive
    AND (p_SearchTerm IS NULL OR 
         p.ProductCode LIKE CONCAT('%', p_SearchTerm, '%') OR 
         p.ProductName LIKE CONCAT('%', p_SearchTerm, '%') OR
         p.Barcode LIKE CONCAT('%', p_SearchTerm, '%'));
    
    SET p_TotalPages = CEIL(p_TotalRecords / p_PageSize);
    
    -- گرفتن لیست محصولات
    SELECT 
        p.ProductID,
        p.ProductCode,
        p.Barcode,
        p.ProductName,
        p.ProductDescription,
        p.CategoryID,
        c.CategoryName,
        p.UnitID,
        u.UnitName,
        p.Brand,
        p.Model,
        p.Weight,
        p.Volume,
        p.MinStockLevel,
        p.MaxStockLevel,
        p.ReorderPoint,
        p.SafetyStock,
        p.IsBatchTracked,
        p.IsSerialTracked,
        p.IsExpiryTracked,
        p.ShelfLife,
        p.IsActive,
        COALESCE(SUM(i.Quantity), 0) as TotalStock,
        COALESCE(SUM(i.AvailableQuantity), 0) as AvailableStock,
        COALESCE(SUM(i.ReservedQuantity), 0) as ReservedStock,
        CASE 
            WHEN COALESCE(SUM(i.AvailableQuantity), 0) <= p.MinStockLevel THEN 'Low'
            WHEN COALESCE(SUM(i.AvailableQuantity), 0) >= p.MaxStockLevel THEN 'High'
            ELSE 'Normal'
        END as StockStatus
    FROM Products p
    LEFT JOIN ProductCategories c ON p.CategoryID = c.CategoryID
    LEFT JOIN Units u ON p.UnitID = u.UnitID
    LEFT JOIN Inventory i ON p.ProductID = i.ProductID
    WHERE p.CompanyID = p_CompanyID
    AND (p_CategoryID IS NULL OR p.CategoryID = p_CategoryID)
    AND p.IsActive = p_IsActive
    AND (p_SearchTerm IS NULL OR 
         p.ProductCode LIKE CONCAT('%', p_SearchTerm, '%') OR 
         p.ProductName LIKE CONCAT('%', p_SearchTerm, '%') OR
         p.Barcode LIKE CONCAT('%', p_SearchTerm, '%'))
    GROUP BY p.ProductID
    ORDER BY p.ProductName
    LIMIT p_PageSize OFFSET (p_Page - 1) * p_PageSize;
END$$

DELIMITER ;

1.2. گرفتن اطلاعات کامل یک محصول

sql
DELIMITER $$

CREATE PROCEDURE sp_GetProductInfo(
    IN p_ProductID INT
)
BEGIN
    -- اطلاعات اصلی محصول
    SELECT 
        p.*,
        c.CategoryName,
        u.UnitName,
        u.UnitCode,
        COALESCE(SUM(i.Quantity), 0) as TotalStock,
        COALESCE(SUM(i.AvailableQuantity), 0) as AvailableStock,
        COALESCE(SUM(i.ReservedQuantity), 0) as ReservedStock,
        COALESCE(SUM(i.TotalValue), 0) as TotalValue,
        COUNT(DISTINCT i.WarehouseID) as WarehouseCount
    FROM Products p
    LEFT JOIN ProductCategories c ON p.CategoryID = c.CategoryID
    LEFT JOIN Units u ON p.UnitID = u.UnitID
    LEFT JOIN Inventory i ON p.ProductID = i.ProductID
    WHERE p.ProductID = p_ProductID
    GROUP BY p.ProductID;
    
    -- موجودی در انبارهای مختلف
    SELECT 
        i.InventoryID,
        i.WarehouseID,
        w.WarehouseCode,
        w.WarehouseName,
        i.ZoneID,
        z.ZoneCode,
        z.ZoneName,
        i.BatchNumber,
        i.SerialNumber,
        i.ExpiryDate,
        i.ProductionDate,
        i.Quantity,
        i.ReservedQuantity,
        i.AvailableQuantity,
        i.UnitCost,
        i.TotalValue,
        i.Status,
        DATEDIFF(i.ExpiryDate, CURDATE()) as DaysToExpiry
    FROM Inventory i
    INNER JOIN Warehouses w ON i.WarehouseID = w.WarehouseID
    INNER JOIN WarehouseZones z ON i.ZoneID = z.ZoneID
    WHERE i.ProductID = p_ProductID
    AND i.Quantity > 0
    ORDER BY w.WarehouseName, z.ZoneCode;
    
    -- آخرین تراکنش‌ها
    SELECT 
        it.TransactionID,
        it.TransactionDate,
        it.TransactionType,
        it.ReferenceNumber,
        it.QuantityIn,
        it.QuantityOut,
        it.UnitCost,
        it.TotalCost,
        w.WarehouseName,
        z.ZoneCode,
        it.Remarks
    FROM InventoryTransactions it
    LEFT JOIN Warehouses w ON it.WarehouseID = w.WarehouseID
    LEFT JOIN WarehouseZones z ON it.ZoneID = z.ZoneID
    WHERE it.ProductID = p_ProductID
    ORDER BY it.TransactionDate DESC, it.TransactionID DESC
    LIMIT 10;
END$$

DELIMITER ;

1.3. ایجاد محصول جدید

sql
DELIMITER $$

CREATE PROCEDURE sp_CreateProduct(
    IN p_CompanyID INT,
    IN p_ProductCode VARCHAR(50),
    IN p_Barcode VARCHAR(100),
    IN p_ProductName NVARCHAR(200),
    IN p_ProductDescription TEXT,
    IN p_CategoryID INT,
    IN p_UnitID INT,
    IN p_Brand NVARCHAR(100),
    IN p_Model NVARCHAR(100),
    IN p_Weight DECIMAL(10,3),
    IN p_Volume DECIMAL(10,3),
    IN p_MinStockLevel DECIMAL(12,3),
    IN p_MaxStockLevel DECIMAL(12,3),
    IN p_ReorderPoint DECIMAL(12,3),
    IN p_SafetyStock DECIMAL(12,3),
    IN p_IsBatchTracked BOOLEAN,
    IN p_IsSerialTracked BOOLEAN,
    IN p_IsExpiryTracked BOOLEAN,
    IN p_ShelfLife INT,
    IN p_StorageConditions TEXT,
    IN p_Specifications JSON,
    OUT p_ProductID INT,
    OUT p_ResultMessage VARCHAR(500)
)
BEGIN
    DECLARE v_Count INT DEFAULT 0;
    
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        SET p_ResultMessage = 'خطا در ایجاد محصول';
        SET p_ProductID = -1;
    END;
    
    START TRANSACTION;
    
    -- بررسی تکراری نبودن کد محصول
    SELECT COUNT(*) INTO v_Count 
    FROM Products 
    WHERE CompanyID = p_CompanyID AND ProductCode = p_ProductCode;
    
    IF v_Count > 0 THEN
        SET p_ResultMessage = 'کد محصول تکراری است';
        SET p_ProductID = -1;
        ROLLBACK;
    ELSE
        -- بررسی تکراری نبودن بارکد
        IF p_Barcode IS NOT NULL THEN
            SELECT COUNT(*) INTO v_Count 
            FROM Products 
            WHERE CompanyID = p_CompanyID AND Barcode = p_Barcode;
            
            IF v_Count > 0 THEN
                SET p_ResultMessage = 'بارکد تکراری است';
                SET p_ProductID = -1;
                ROLLBACK;
            ELSE
                -- ایجاد محصول
                INSERT INTO Products (
                    CompanyID, ProductCode, Barcode, ProductName, ProductDescription,
                    CategoryID, UnitID, Brand, Model, Weight, Volume,
                    MinStockLevel, MaxStockLevel, ReorderPoint, SafetyStock,
                    IsBatchTracked, IsSerialTracked, IsExpiryTracked, ShelfLife,
                    StorageConditions, Specifications
                ) VALUES (
                    p_CompanyID, p_ProductCode, p_Barcode, p_ProductName, p_ProductDescription,
                    p_CategoryID, p_UnitID, p_Brand, p_Model, p_Weight, p_Volume,
                    p_MinStockLevel, p_MaxStockLevel, p_ReorderPoint, p_SafetyStock,
                    p_IsBatchTracked, p_IsSerialTracked, p_IsExpiryTracked, p_ShelfLife,
                    p_StorageConditions, p_Specifications
                );
                
                SET p_ProductID = LAST_INSERT_ID();
                SET p_ResultMessage = 'محصول با موفقیت ایجاد شد';
                COMMIT;
            END IF;
        ELSE
            -- ایجاد محصول بدون بارکد
            INSERT INTO Products (
                CompanyID, ProductCode, Barcode, ProductName, ProductDescription,
                CategoryID, UnitID, Brand, Model, Weight, Volume,
                MinStockLevel, MaxStockLevel, ReorderPoint, SafetyStock,
                IsBatchTracked, IsSerialTracked, IsExpiryTracked, ShelfLife,
                StorageConditions, Specifications
            ) VALUES (
                p_CompanyID, p_ProductCode, p_Barcode, p_ProductName, p_ProductDescription,
                p_CategoryID, p_UnitID, p_Brand, p_Model, p_Weight, p_Volume,
                p_MinStockLevel, p_MaxStockLevel, p_ReorderPoint, p_SafetyStock,
                p_IsBatchTracked, p_IsSerialTracked, p_IsExpiryTracked, p_ShelfLife,
                p_StorageConditions, p_Specifications
            );
            
            SET p_ProductID = LAST_INSERT_ID();
            SET p_ResultMessage = 'محصول با موفقیت ایجاد شد';
            COMMIT;
        END IF;
    END IF;
END$$

DELIMITER ;

1.4. به‌روزرسانی محصول

sql
DELIMITER $$

CREATE PROCEDURE sp_UpdateProduct(
    IN p_ProductID INT,
    IN p_ProductName NVARCHAR(200),
    IN p_ProductDescription TEXT,
    IN p_CategoryID INT,
    IN p_UnitID INT,
    IN p_Brand NVARCHAR(100),
    IN p_Model NVARCHAR(100),
    IN p_Weight DECIMAL(10,3),
    IN p_Volume DECIMAL(10,3),
    IN p_MinStockLevel DECIMAL(12,3),
    IN p_MaxStockLevel DECIMAL(12,3),
    IN p_ReorderPoint DECIMAL(12,3),
    IN p_SafetyStock DECIMAL(12,3),
    IN p_IsBatchTracked BOOLEAN,
    IN p_IsSerialTracked BOOLEAN,
    IN p_IsExpiryTracked BOOLEAN,
    IN p_ShelfLife INT,
    IN p_StorageConditions TEXT,
    IN p_Specifications JSON,
    IN p_IsActive BOOLEAN,
    OUT p_ResultMessage VARCHAR(500)
)
BEGIN
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        SET p_ResultMessage = 'خطا در به‌روزرسانی محصول';
    END;
    
    START TRANSACTION;
    
    -- به‌روزرسانی محصول
    UPDATE Products 
    SET 
        ProductName = p_ProductName,
        ProductDescription = p_ProductDescription,
        CategoryID = p_CategoryID,
        UnitID = p_UnitID,
        Brand = p_Brand,
        Model = p_Model,
        Weight = p_Weight,
        Volume = p_Volume,
        MinStockLevel = p_MinStockLevel,
        MaxStockLevel = p_MaxStockLevel,
        ReorderPoint = p_ReorderPoint,
        SafetyStock = p_SafetyStock,
        IsBatchTracked = p_IsBatchTracked,
        IsSerialTracked = p_IsSerialTracked,
        IsExpiryTracked = p_IsExpiryTracked,
        ShelfLife = p_ShelfLife,
        StorageConditions = p_StorageConditions,
        Specifications = p_Specifications,
        IsActive = p_IsActive,
        UpdatedAt = CURRENT_TIMESTAMP
    WHERE ProductID = p_ProductID;
    
    SET p_ResultMessage = 'محصول با موفقیت به‌روزرسانی شد';
    COMMIT;
END$$

DELIMITER ;

2. Stored Procedure برای مدیریت موجودی

2.1. گرفتن لیست موجودی

sql
DELIMITER $$

CREATE PROCEDURE sp_GetInventory(
    IN p_CompanyID INT,
    IN p_WarehouseID INT,
    IN p_ProductID INT,
    IN p_ShowLowStock BOOLEAN,
    IN p_ShowExpired BOOLEAN,
    IN p_ShowZeroStock BOOLEAN,
    IN p_Page INT,
    IN p_PageSize INT,
    OUT p_TotalRecords INT,
    OUT p_TotalValue DECIMAL(15,2)
)
BEGIN
    -- محاسبه رکوردهای کل و مجموع ارزش
    SELECT 
        COUNT(DISTINCT i.InventoryID),
        COALESCE(SUM(i.TotalValue), 0)
    INTO p_TotalRecords, p_TotalValue
    FROM Inventory i
    INNER JOIN Products p ON i.ProductID = p.ProductID
    INNER JOIN Warehouses w ON i.WarehouseID = w.WarehouseID
    WHERE w.CompanyID = p_CompanyID
    AND (p_WarehouseID IS NULL OR i.WarehouseID = p_WarehouseID)
    AND (p_ProductID IS NULL OR i.ProductID = p_ProductID)
    AND (p_ShowZeroStock = TRUE OR i.Quantity > 0)
    AND (p_ShowLowStock = FALSE OR i.AvailableQuantity <= p.MinStockLevel)
    AND (p_ShowExpired = FALSE OR (i.ExpiryDate IS NOT NULL AND i.ExpiryDate < CURDATE()));
    
    -- گرفتن لیست موجودی
    SELECT 
        i.InventoryID,
        i.ProductID,
        p.ProductCode,
        p.ProductName,
        p.Barcode,
        i.WarehouseID,
        w.WarehouseCode,
        w.WarehouseName,
        i.ZoneID,
        z.ZoneCode,
        z.ZoneName,
        i.BatchNumber,
        i.SerialNumber,
        i.ExpiryDate,
        i.ProductionDate,
        i.Quantity,
        i.ReservedQuantity,
        i.AvailableQuantity,
        i.UnitCost,
        i.TotalValue,
        i.Status,
        p.MinStockLevel,
        p.MaxStockLevel,
        DATEDIFF(i.ExpiryDate, CURDATE()) as DaysToExpiry,
        CASE 
            WHEN i.ExpiryDate IS NOT NULL AND i.ExpiryDate < CURDATE() THEN 'Expired'
            WHEN i.AvailableQuantity <= p.MinStockLevel THEN 'Low'
            WHEN i.AvailableQuantity >= p.MaxStockLevel THEN 'High'
            ELSE 'Normal'
        END as StockStatus
    FROM Inventory i
    INNER JOIN Products p ON i.ProductID = p.ProductID
    INNER JOIN Warehouses w ON i.WarehouseID = w.WarehouseID
    INNER JOIN WarehouseZones z ON i.ZoneID = z.ZoneID
    WHERE w.CompanyID = p_CompanyID
    AND (p_WarehouseID IS NULL OR i.WarehouseID = p_WarehouseID)
    AND (p_ProductID IS NULL OR i.ProductID = p_ProductID)
    AND (p_ShowZeroStock = TRUE OR i.Quantity > 0)
    AND (p_ShowLowStock = FALSE OR i.AvailableQuantity <= p.MinStockLevel)
    AND (p_ShowExpired = FALSE OR (i.ExpiryDate IS NOT NULL AND i.ExpiryDate < CURDATE()))
    ORDER BY p.ProductName, w.WarehouseName, z.ZoneCode
    LIMIT p_PageSize OFFSET (p_Page - 1) * p_PageSize;
END$$

DELIMITER ;

2.2. به‌روزرسانی موجودی

sql
DELIMITER $$

CREATE PROCEDURE sp_UpdateInventory(
    IN p_InventoryID INT,
    IN p_Quantity DECIMAL(12,3),
    IN p_UnitCost DECIMAL(15,2),
    IN p_BatchNumber VARCHAR(100),
    IN p_SerialNumber VARCHAR(100),
    IN p_ExpiryDate DATE,
    IN p_ProductionDate DATE,
    IN p_Status ENUM('Available', 'Quarantine', 'Blocked', 'Expired'),
    OUT p_ResultMessage VARCHAR(500)
)
BEGIN
    DECLARE v_OldQuantity DECIMAL(12,3);
    DECLARE v_OldUnitCost DECIMAL(15,2);
    DECLARE v_ProductID INT;
    DECLARE v_WarehouseID INT;
    DECLARE v_ZoneID INT;
    
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        SET p_ResultMessage = 'خطا در به‌روزرسانی موجودی';
    END;
    
    START TRANSACTION;
    
    -- گرفتن مقادیر قبلی
    SELECT Quantity, UnitCost, ProductID, WarehouseID, ZoneID
    INTO v_OldQuantity, v_OldUnitCost, v_ProductID, v_WarehouseID, v_ZoneID
    FROM Inventory
    WHERE InventoryID = p_InventoryID;
    
    -- به‌روزرسانی موجودی
    UPDATE Inventory 
    SET 
        Quantity = p_Quantity,
        UnitCost = p_UnitCost,
        BatchNumber = p_BatchNumber,
        SerialNumber = p_SerialNumber,
        ExpiryDate = p_ExpiryDate,
        ProductionDate = p_ProductionDate,
        Status = p_Status,
        UpdatedAt = CURRENT_TIMESTAMP
    WHERE InventoryID = p_InventoryID;
    
    -- ثبت تراکنش تعدیل
    INSERT INTO InventoryTransactions (
        ProductID, WarehouseID, ZoneID, TransactionType, ReferenceID,
        QuantityIn, QuantityOut, UnitCost, TotalCost, NewQuantity, NewUnitCost,
        Remarks
    ) VALUES (
        v_ProductID, v_WarehouseID, v_ZoneID, 'Adjustment', p_InventoryID,
        CASE WHEN p_Quantity > v_OldQuantity THEN p_Quantity - v_OldQuantity ELSE 0 END,
        CASE WHEN p_Quantity < v_OldQuantity THEN v_OldQuantity - p_Quantity ELSE 0 END,
        v_OldUnitCost,
        ABS(p_Quantity - v_OldQuantity) * v_OldUnitCost,
        p_Quantity,
        p_UnitCost,
        'Manual inventory adjustment'
    );
    
    SET p_ResultMessage = 'موجودی با موفقیت به‌روزرسانی شد';
    COMMIT;
END$$

DELIMITER ;

3. Stored Procedure برای مدیریت دریافت کالا

3.1. ایجاد رسید دریافت

sql
DELIMITER $$

CREATE PROCEDURE sp_CreateGoodsReceipt(
    IN p_CompanyID INT,
    IN p_OrderID INT,
    IN p_ReceiptDate DATE,
    IN p_ReceivedBy INT,
    IN p_WarehouseID INT,
    IN p_SupplierID INT,
    IN p_ReceiptType ENUM('Purchase', 'Return', 'Transfer', 'Production'),
    IN p_Notes TEXT,
    IN p_Items JSON,
    OUT p_ReceiptID INT,
    OUT p_ReceiptNumber VARCHAR(50),
    OUT p_ResultMessage VARCHAR(500)
)
BEGIN
    DECLARE v_TotalQuantity DECIMAL(12,3) DEFAULT 0;
    DECLARE v_TotalValue DECIMAL(15,2) DEFAULT 0;
    DECLARE v_ItemCount INT DEFAULT 0;
    DECLARE i INT DEFAULT 0;
    DECLARE v_OrderItemID INT;
    DECLARE v_ProductID INT;
    DECLARE v_ReceivedQuantity DECIMAL(12,3);
    DECLARE v_UnitPrice DECIMAL(12,2);
    DECLARE v_BatchNumber VARCHAR(100);
    DECLARE v_SerialNumber VARCHAR(100);
    DECLARE v_ExpiryDate DATE;
    DECLARE v_ProductionDate DATE;
    DECLARE v_ZoneID INT;
    DECLARE v_QualityStatus ENUM('Accepted', 'Rejected', 'Quarantine');
    
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        SET p_ResultMessage = 'خطا در ایجاد رسید دریافت';
        SET p_ReceiptID = -1;
        SET p_ReceiptNumber = '';
    END;
    
    START TRANSACTION;
    
    -- تولید شماره رسید
    SET p_ReceiptNumber = fn_GenerateReceiptNumber(p_CompanyID, p_ReceiptType);
    
    -- ایجاد رسید
    INSERT INTO GoodsReceipts (
        ReceiptNumber, OrderID, ReceiptDate, ReceivedBy, WarehouseID,
        SupplierID, ReceiptType, Notes
    ) VALUES (
        p_ReceiptNumber, p_OrderID, p_ReceiptDate, p_ReceivedBy, p_WarehouseID,
        p_SupplierID, p_ReceiptType, p_Notes
    );
    
    SET p_ReceiptID = LAST_INSERT_ID();
    
    -- پردازش اقلام
    SET v_ItemCount = JSON_LENGTH(p_Items);
    SET i = 0;
    
    WHILE i < v_ItemCount DO
        SET v_OrderItemID = NULLIF(JSON_EXTRACT(p_Items, CONCAT('$[', i, '].OrderItemID')), 'null');
        SET v_ProductID = JSON_EXTRACT(p_Items, CONCAT('$[', i, '].ProductID'));
        SET v_ReceivedQuantity = JSON_EXTRACT(p_Items, CONCAT('$[', i, '].ReceivedQuantity'));
        SET v_UnitPrice = JSON_EXTRACT(p_Items, CONCAT('$[', i, '].UnitPrice'));
        SET v_BatchNumber = NULLIF(JSON_UNQUOTE(JSON_EXTRACT(p_Items, CONCAT('$[', i, '].BatchNumber'))), 'null');
        SET v_SerialNumber = NULLIF(JSON_UNQUOTE(JSON_EXTRACT(p_Items, CONCAT('$[', i, '].SerialNumber'))), 'null');
        SET v_ExpiryDate = NULLIF(JSON_UNQUOTE(JSON_EXTRACT(p_Items, CONCAT('$[', i, '].ExpiryDate'))), 'null');
        SET v_ProductionDate = NULLIF(JSON_UNQUOTE(JSON_EXTRACT(p_Items, CONCAT('$[', i, '].ProductionDate'))), 'null');
        SET v_ZoneID = JSON_EXTRACT(p_Items, CONCAT('$[', i, '].ZoneID'));
        SET v_QualityStatus = JSON_UNQUOTE(JSON_EXTRACT(p_Items, CONCAT('$[', i, '].QualityStatus')));
        
        -- ایجاد آیتم رسید
        INSERT INTO GoodsReceiptItems (
            ReceiptID, OrderItemID, ProductID, ReceivedQuantity, UnitPrice,
            BatchNumber, SerialNumber, ExpiryDate, ProductionDate, ZoneID, QualityStatus
        ) VALUES (
            p_ReceiptID, v_OrderItemID, v_ProductID, v_ReceivedQuantity, v_UnitPrice,
            v_BatchNumber, v_SerialNumber, v_ExpiryDate, v_ProductionDate, v_ZoneID, v_QualityStatus
        );
        
        -- به‌روزرسانی موجودی برای کالاهای پذیرفته شده
        IF v_QualityStatus = 'Accepted' THEN
            CALL sp_UpdateInventoryOnReceipt(
                v_ProductID, p_WarehouseID, v_ZoneID, v_ReceivedQuantity, v_UnitPrice,
                v_BatchNumber, v_SerialNumber, v_ExpiryDate, v_ProductionDate,
                p_ReceiptID, p_ReceiptNumber
            );
        END IF;
        
        SET v_TotalQuantity = v_TotalQuantity + v_ReceivedQuantity;
        SET v_TotalValue = v_TotalValue + (v_ReceivedQuantity * v_UnitPrice);
        
        SET i = i + 1;
    END WHILE;
    
    -- به‌روزرسانی مقادیر کل رسید
    UPDATE GoodsReceipts 
    SET TotalQuantity = v_TotalQuantity, TotalValue = v_TotalValue, Status = 'Received'
    WHERE ReceiptID = p_ReceiptID;
    
    SET p_ResultMessage = 'رسید دریافت با موفقیت ایجاد شد';
    COMMIT;
END$$

DELIMITER ;

3.2. Stored Procedure کمکی برای به‌روزرسانی موجودی هنگام دریافت

sql
DELIMITER $$

CREATE PROCEDURE sp_UpdateInventoryOnReceipt(
    IN p_ProductID INT,
    IN p_WarehouseID INT,
    IN p_ZoneID INT,
    IN p_Quantity DECIMAL(12,3),
    IN p_UnitCost DECIMAL(12,2),
    IN p_BatchNumber VARCHAR(100),
    IN p_SerialNumber VARCHAR(100),
    IN p_ExpiryDate DATE,
    IN p_ProductionDate DATE,
    IN p_ReferenceID INT,
    IN p_ReferenceNumber VARCHAR(50)
)
BEGIN
    DECLARE v_InventoryID INT;
    DECLARE v_ExistingQuantity DECIMAL(12,3);
    DECLARE v_ExistingUnitCost DECIMAL(12,2);
    
    -- پیدا کردن یا ایجاد رکورد موجودی
    SELECT InventoryID, Quantity, UnitCost
    INTO v_InventoryID, v_ExistingQuantity, v_ExistingUnitCost
    FROM Inventory
    WHERE ProductID = p_ProductID 
    AND WarehouseID = p_WarehouseID 
    AND ZoneID = p_ZoneID
    AND (BatchNumber = p_BatchNumber OR (BatchNumber IS NULL AND p_BatchNumber IS NULL))
    AND (SerialNumber = p_SerialNumber OR (SerialNumber IS NULL AND p_SerialNumber IS NULL));
    
    IF v_InventoryID IS NOT NULL THEN
        -- محاسبه میانگین موزون قیمت تمام شده
        SET v_ExistingUnitCost = (
            (v_ExistingQuantity * v_ExistingUnitCost) + (p_Quantity * p_UnitCost)
        ) / (v_ExistingQuantity + p_Quantity);
        
        -- به‌روزرسانی موجودی موجود
        UPDATE Inventory 
        SET 
            Quantity = Quantity + p_Quantity,
            UnitCost = v_ExistingUnitCost,
            UpdatedAt = CURRENT_TIMESTAMP
        WHERE InventoryID = v_InventoryID;
    ELSE
        -- ایجاد رکورد جدید موجودی
        INSERT INTO Inventory (
            ProductID, WarehouseID, ZoneID, BatchNumber, SerialNumber,
            ExpiryDate, ProductionDate, Quantity, UnitCost, Status
        ) VALUES (
            p_ProductID, p_WarehouseID, p_ZoneID, p_BatchNumber, p_SerialNumber,
            p_ExpiryDate, p_ProductionDate, p_Quantity, p_UnitCost, 'Available'
        );
        
        SET v_InventoryID = LAST_INSERT_ID();
    END IF;
    
    -- ثبت تراکنش
    INSERT INTO InventoryTransactions (
        ProductID, WarehouseID, ZoneID, TransactionType, ReferenceID, ReferenceNumber,
        QuantityIn, UnitCost, TotalCost, NewQuantity, NewUnitCost, Remarks
    ) VALUES (
        p_ProductID, p_WarehouseID, p_ZoneID, 'PurchaseReceipt', p_ReferenceID, p_ReferenceNumber,
        p_Quantity, p_UnitCost, p_Quantity * p_UnitCost, 
        v_ExistingQuantity + p_Quantity, v_ExistingUnitCost,
        'Goods receipt'
    );
END$$

DELIMITER ;

4. Stored Procedure برای مدیریت حواله خروج

4.1. ایجاد درخواست خروج

sql
DELIMITER $$

CREATE PROCEDURE sp_CreateIssueRequest(
    IN p_CompanyID INT,
    IN p_IssueDate DATE,
    IN p_RequestedBy INT,
    IN p_DepartmentID INT,
    IN p_Purpose ENUM('Production', 'Sales', 'Maintenance', 'Sample', 'InternalUse'),
    IN p_Priority ENUM('Low', 'Medium', 'High', 'Urgent'),
    IN p_RequiredDate DATE,
    IN p_Notes TEXT,
    IN p_Items JSON,
    OUT p_IssueID INT,
    OUT p_IssueNumber VARCHAR(50),
    OUT p_ResultMessage VARCHAR(500)
)
BEGIN
    DECLARE v_TotalQuantity DECIMAL(12,3) DEFAULT 0;
    DECLARE v_ItemCount INT DEFAULT 0;
    DECLARE i INT DEFAULT 0;
    DECLARE v_ProductID INT;
    DECLARE v_RequestedQuantity DECIMAL(12,3);
    DECLARE v_AvailableStock DECIMAL(12,3);
    
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        SET p_ResultMessage = 'خطا در ایجاد درخواست خروج';
        SET p_IssueID = -1;
        SET p_IssueNumber = '';
    END;
    
    START TRANSACTION;
    
    -- تولید شماره درخواست
    SET p_IssueNumber = fn_GenerateIssueNumber(p_CompanyID);
    
    -- ایجاد درخواست
    INSERT INTO IssueRequests (
        IssueNumber, IssueDate, RequestedBy, DepartmentID, Purpose,
        Priority, RequiredDate, Notes
    ) VALUES (
        p_IssueNumber, p_IssueDate, p_RequestedBy, p_DepartmentID, p_Purpose,
        p_Priority, p_RequiredDate, p_Notes
    );
    
    SET p_IssueID = LAST_INSERT_ID();
    
    -- پردازش اقلام
    SET v_ItemCount = JSON_LENGTH(p_Items);
    SET i = 0;
    
    WHILE i < v_ItemCount DO
        SET v_ProductID = JSON_EXTRACT(p_Items, CONCAT('$[', i, '].ProductID'));
        SET v_RequestedQuantity = JSON_EXTRACT(p_Items, CONCAT('$[', i, '].RequestedQuantity'));
        
        -- بررسی موجودی
        SELECT COALESCE(SUM(AvailableQuantity), 0) INTO v_AvailableStock
        FROM Inventory
        WHERE ProductID = v_ProductID;
        
        IF v_AvailableStock < v_RequestedQuantity THEN
            SET p_ResultMessage = CONCAT('موجودی ناکافی برای محصول: ', CAST(v_ProductID AS CHAR));
            SET p_IssueID = -1;
            ROLLBACK;
        ELSE
            -- ایجاد آیتم درخواست
            INSERT INTO IssueRequestItems (
                IssueID, ProductID, RequestedQuantity, RequiredDate
            ) VALUES (
                p_IssueID, v_ProductID, v_RequestedQuantity, p_RequiredDate
            );
            
            SET v_TotalQuantity = v_TotalQuantity + v_RequestedQuantity;
            SET i = i + 1;
        END IF;
    END WHILE;
    
    -- به‌روزرسانی مقادیر کل
    UPDATE IssueRequests 
    SET TotalQuantity = v_TotalQuantity
    WHERE IssueID = p_IssueID;
    
    SET p_ResultMessage = 'درخواست خروج با موفقیت ایجاد شد';
    COMMIT;
END$$

DELIMITER ;

4.2. ایجاد حواله خروج

sql
DELIMITER $$

CREATE PROCEDURE sp_CreateIssueVoucher(
    IN p_IssueID INT,
    IN p_VoucherDate DATE,
    IN p_IssuedBy INT,
    IN p_WarehouseID INT,
    IN p_IssuedTo NVARCHAR(200),
    IN p_DepartmentID INT,
    IN p_VoucherType ENUM('Production', 'Sales', 'Maintenance', 'Sample', 'InternalUse'),
    IN p_Items JSON,
    OUT p_VoucherID INT,
    OUT p_VoucherNumber VARCHAR(50),
    OUT p_ResultMessage VARCHAR(500)
)
BEGIN
    DECLARE v_TotalQuantity DECIMAL(12,3) DEFAULT 0;
    DECLARE v_TotalValue DECIMAL(15,2) DEFAULT 0;
    DECLARE v_ItemCount INT DEFAULT 0;
    DECLARE i INT DEFAULT 0;
    DECLARE v_IssueItemID INT;
    DECLARE v_ProductID INT;
    DECLARE v_IssuedQuantity DECIMAL(12,3);
    DECLARE v_UnitCost DECIMAL(12,2);
    DECLARE v_BatchNumber VARCHAR(100);
    DECLARE v_SerialNumber VARCHAR(100);
    DECLARE v_ZoneID INT;
    
    DECLARE EXIT HANDLER FOR SQLEXCEPTION
    BEGIN
        ROLLBACK;
        SET p_ResultMessage = 'خطا در ایجاد حواله خروج';
        SET p_VoucherID = -1;
        SET p_VoucherNumber = '';
    END;
    
    START TRANSACTION;
    
    -- تولید شماره حواله
    SET p_VoucherNumber = fn_GenerateVoucherNumber(p_WarehouseID);
    
    -- ایجاد حواله
    INSERT INTO IssueVouchers (
        VoucherNumber, IssueID, VoucherDate, IssuedBy, WarehouseID,
        IssuedTo, DepartmentID, VoucherType
    ) VALUES (
        p_VoucherNumber, p_IssueID, p_VoucherDate, p_IssuedBy, p_WarehouseID,
        p_IssuedTo, p_DepartmentID, p_VoucherType
    );
    
    SET p_VoucherID = LAST_INSERT_ID();
    
    -- پردازش اقلام
    SET v_ItemCount = JSON_LENGTH(p_Items);
    SET i = 0;
    
    WHILE i < v_ItemCount DO
        SET v_IssueItemID = JSON_EXTRACT(p_Items, CONCAT('$[', i, '].IssueItemID'));
        SET v_ProductID = JSON_EXTRACT(p_Items, CONCAT('$[', i, '].ProductID'));
        SET v_IssuedQuantity = JSON_EXTRACT(p_Items, CONCAT('$[', i, '].IssuedQuantity'));
        SET v_ZoneID = JSON_EXTRACT(p_Items, CONCAT('$[', i, '].ZoneID'));
        
        -- کسر از موجودی (FIFO)
        CALL sp_IssueStockFIFO(
            v_ProductID, p_WarehouseID, v_ZoneID, v_IssuedQuantity,
            p_VoucherID, v_IssueItemID,
            @v_TotalCost, @v_Success
        );
        
        IF @v_Success = 0 THEN
            SET p_ResultMessage = 'موجودی ناکافی برای صدور حواله';
            SET p_VoucherID = -1;
            ROLLBACK;
        ELSE
            SET v_TotalQuantity = v_TotalQuantity + v_IssuedQuantity;
            SET v_TotalValue = v_TotalValue + @v_TotalCost;
            SET i = i + 1;
        END IF;
    END WHILE;
    
    -- به‌روزرسانی مقادیر کل حواله
    UPDATE IssueVouchers 
    SET TotalQuantity = v_TotalQuantity, TotalValue = v_TotalValue, Status = 'Issued'
    WHERE VoucherID = p_VoucherID;
    
    -- به‌روزرسانی وضعیت درخواست
    UPDATE IssueRequests 
    SET Status = 'Completed'
    WHERE IssueID = p_IssueID;
    
    SET p_ResultMessage = 'حواله خروج با موفقیت ایجاد شد';
    COMMIT;
END$$

DELIMITER ;

5. Stored Procedure برای کسر موجودی (FIFO)

sql
DELIMITER $$

CREATE PROCEDURE sp_IssueStockFIFO(
    IN p_ProductID INT,
    IN p_WarehouseID INT,
    IN p_ZoneID INT,
    IN p_Quantity DECIMAL(12,3),
    IN p_VoucherID INT,
    IN p_IssueItemID INT,
    OUT p_TotalCost DECIMAL(15,2),
    OUT p_Success BOOLEAN
)
BEGIN
    DECLARE v_RemainingQty DECIMAL(12,3) DEFAULT p_Quantity;
    DECLARE v_CurrentQty DECIMAL(12,3);
    DECLARE v_IssueQty DECIMAL(12,3);
    DECLARE v_UnitCost DECIMAL(12,2);
    DECLARE v_InventoryID INT;
    DECLARE v_BatchNumber VARCHAR(100);
    DECLARE v_SerialNumber VARCHAR(100);
    DECLARE v_CurrentZoneID INT;
    DECLARE done INT DEFAULT FALSE;
    
    DECLARE cur CURSOR FOR 
        SELECT InventoryID, AvailableQuantity, UnitCost, BatchNumber, SerialNumber, ZoneID
        FROM Inventory 
        WHERE ProductID = p_ProductID 
        AND WarehouseID = p_WarehouseID
        AND (p_ZoneID IS NULL OR ZoneID = p_ZoneID)
        AND AvailableQuantity > 0
        ORDER BY 
            ExpiryDate ASC,
            ProductionDate ASC,
            CreatedAt ASC;
    
    DECLARE CONTINUE HANDLER FOR NOT FOUND SET done = TRUE;
    
    SET p_TotalCost = 0;
    SET p_Success = FALSE;
    
    OPEN cur;
    
    read_loop: LOOP
        FETCH cur INTO v_InventoryID, v_CurrentQty, v_UnitCost, v_BatchNumber, v_SerialNumber, v_CurrentZoneID;
        
        IF done OR v_RemainingQty <= 0 THEN
            LEAVE read_loop;
        END IF;
        
        SET v_IssueQty = LEAST(v_CurrentQty, v_RemainingQty);
        
        -- کسر از موجودی
        UPDATE Inventory 
        SET Quantity = Quantity - v_IssueQty
        WHERE InventoryID = v_InventoryID;
        
        -- ایجاد آیتم حواله
        INSERT INTO IssueVoucherItems (
            VoucherID, IssueItemID, ProductID, IssuedQuantity, UnitCost,
            BatchNumber, SerialNumber, ZoneID
        ) VALUES (
            p_VoucherID, p_IssueItemID, p_ProductID, v_IssueQty, v_UnitCost,
            v_BatchNumber, v_SerialNumber, v_CurrentZoneID
        );
        
        -- ثبت تراکنش
        INSERT INTO InventoryTransactions (
            ProductID, WarehouseID, ZoneID, TransactionType, ReferenceID, ReferenceNumber,
            QuantityOut, UnitCost, TotalCost, NewQuantity, Remarks
        ) VALUES (
            p_ProductID, p_WarehouseID, v_CurrentZoneID, 'Issue', p_VoucherID, CONCAT('IV-', p_VoucherID),
            v_IssueQty, v_UnitCost, v_IssueQty * v_UnitCost, v_CurrentQty - v_IssueQty,
            'Stock issued via voucher'
        );
        
        SET p_TotalCost = p_TotalCost + (v_IssueQty * v_UnitCost);
        SET v_RemainingQty = v_RemainingQty - v_IssueQty;
    END LOOP;
    
    CLOSE cur;
    
    IF v_RemainingQty <= 0 THEN
        SET p_Success = TRUE;
    ELSE
        SET p_Success = FALSE;
    END IF;
END$$

DELIMITER ;

6. توابع کمکی برای تولید شماره

sql
DELIMITER $$

-- تابع تولید شماره رسید
CREATE FUNCTION fn_GenerateReceiptNumber(
    p_CompanyID INT,
    p_ReceiptType VARCHAR(20)
) RETURNS VARCHAR(50)
READS SQL DATA
DETERMINISTIC
BEGIN
    DECLARE v_Prefix VARCHAR(5);
    DECLARE v_Year VARCHAR(4);
    DECLARE v_Month VARCHAR(2);
    DECLARE v_Sequence INT;
    
    SET v_Prefix = 'GR';
    SET v_Year = YEAR(CURDATE());
    SET v_Month = LPAD(MONTH(CURDATE()), 2, '0');
    
    SELECT COALESCE(MAX(CAST(SUBSTRING(ReceiptNumber, -4) AS UNSIGNED)), 0) + 1 INTO v_Sequence
    FROM GoodsReceipts gr
    INNER JOIN Warehouses w ON gr.WarehouseID = w.WarehouseID
    WHERE w.CompanyID = p_CompanyID
    AND YEAR(gr.ReceiptDate) = v_Year
    AND MONTH(gr.ReceiptDate) = v_Month;
    
    RETURN CONCAT(v_Prefix, v_Year, v_Month, LPAD(v_Sequence, 4, '0'));
END$$

-- تابع تولید شماره درخواست خروج
CREATE FUNCTION fn_GenerateIssueNumber(
    p_CompanyID INT
) RETURNS VARCHAR(50)
READS SQL DATA
DETERMINISTIC
BEGIN
    DECLARE v_Year VARCHAR(4);
    DECLARE v_Month VARCHAR(2);
    DECLARE v_Sequence INT;
    
    SET v_Year = YEAR(CURDATE());
    SET v_Month = LPAD(MONTH(CURDATE()), 2, '0');
    
    SELECT COALESCE(MAX(CAST(SUBSTRING(IssueNumber, -4) AS UNSIGNED)), 0) + 1 INTO v_Sequence
    FROM IssueRequests ir
    INNER JOIN Departments d ON ir.DepartmentID = d.DepartmentID
    WHERE d.CompanyID = p_CompanyID
    AND YEAR(ir.IssueDate) = v_Year
    AND MONTH(ir.IssueDate) = v_Month;
    
    RETURN CONCAT('IR', v_Year, v_Month, LPAD(v_Sequence, 4, '0'));
END$$

-- تابع تولید شماره حواله
CREATE FUNCTION fn_GenerateVoucherNumber(
    p_WarehouseID INT
) RETURNS VARCHAR(50)
READS SQL DATA
DETERMINISTIC
BEGIN
    DECLARE v_Year VARCHAR(4);
    DECLARE v_Month VARCHAR(2);
    DECLARE v_Sequence INT;
    
    SET v_Year = YEAR(CURDATE());
    SET v_Month = LPAD(MONTH(CURDATE()), 2, '0');
    
    SELECT COALESCE(MAX(CAST(SUBSTRING(VoucherNumber, -4) AS UNSIGNED)), 0) + 1 INTO v_Sequence
    FROM IssueVouchers
    WHERE WarehouseID = p_WarehouseID
    AND YEAR(VoucherDate) = v_Year
    AND MONTH(VoucherDate) = v_Month;
    
    RETURN CONCAT('IV', v_Year, v_Month, LPAD(v_Sequence, 4, '0'));
END$$

DELIMITER ;

7. نحوه فراخوانی در PHP

php
<?php
// فراخوانی Stored Procedure برای گرفتن لیست محصولات
$companyId = 1;
$categoryId = null;
$page = 1;
$pageSize = 20;

$query = "CALL sp_GetProducts(?, ?, 1, NULL, ?, ?, @totalRecords, @totalPages)";
$stmt = $conn->prepare($query);
$stmt->bind_param("iiii", $companyId, $categoryId, $page, $pageSize);
$stmt->execute();
$result = $stmt->get_result();

$products = [];
while ($row = $result->fetch_assoc()) {
    $products[] = $row;
}

// گرفتن خروجی‌ها
$output = $conn->query("SELECT @totalRecords as total_records, @totalPages as total_pages");
$pagination = $output->fetch_assoc();

// فراخوانی برای ایجاد محصول جدید
$productData = [
    'CompanyID' => 1,
    'ProductCode' => 'PROD001',
    'ProductName' => 'محصول آزمایشی',
    'CategoryID' => 1,
    'UnitID' => 1,
    'MinStockLevel' => 10,
    'MaxStockLevel' => 100
];

$query = "CALL sp_CreateProduct(?, ?, NULL, ?, NULL, ?, ?, NULL, NULL, 0, 0, ?, ?, ?, ?, 0, 0, 0, NULL, NULL, NULL, @productId, @resultMessage)";
$stmt = $conn->prepare($query);
$stmt->bind_param(
    "isssidddd",
    $productData['CompanyID'],
    $productData['ProductCode'],
    $productData['ProductName'],
    $productData['CategoryID'],
    $productData['UnitID'],
    $productData['MinStockLevel'],
    $productData['MaxStockLevel'],
    $productData['ReorderPoint'] ?? 0,
    $productData['SafetyStock'] ?? 0
);
$stmt->execute();

// گرفتن نتیجه
$result = $conn->query("SELECT @productId as product_id, @resultMessage as message");
$output = $result->fetch_assoc();

if ($output['product_id'] > 0) {
    echo "محصول با شناسه " . $output['product_id'] . " ایجاد شد";
} else {
    echo "خطا: " . $output['message'];
}
?>

این Stored Procedure های کامل تمام نیازهای اصلی برای مدیریت رکوردهای سیستم انبارداری را پوشش می‌دهند و می‌توانند بر اساس نیازهای خاص توسعه داده شوند.

 
 
 
 
 
 
 
کانال نوحه و مداحی ناب

نظرات (۰)
هیچ نظری هنوز ثبت نشده است

ارسال نظر

ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
شما میتوانید از این تگهای html استفاده کنید:
<b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
تجدید کد امنیتی