namespace Hims.Infrastructure.Services
{
    using System;
    using System.Collections.Generic;
    using System.Data;
    using System.Linq;
    using System.Threading.Tasks;

    using Dapper;

    using Domain.Entities;
    using Domain.Repositories.UnitOfWork;
    using Domain.Services;

    using Hims.Shared.UserModels;
    using Hims.Shared.UserModels.Inventory;

    using Shared.DataFilters;
    using Shared.EntityModels;

    /// <inheritdoc />
    public class InventoryServices : IInventoryService
    {
        /// <summary>
        /// The unit of work.
        /// </summary>
        private readonly IUnitOfWork unitOfWork;

        /// <summary>
        /// Initializes a new instance of the <see cref="InventoryServices"/> class.
        /// </summary>
        /// <param name="unitOfWork">
        /// The unit of work.
        /// </param>
        public InventoryServices(IUnitOfWork unitOfWork) => this.unitOfWork = unitOfWork;

        /// <inheritdoc/>
        public async Task<int> CreateUnitAsync(LookupValueModel model)
        {
            if (model.LookupId == 0)
            {
                var lookup = await this.unitOfWork.Lookups.FindAsync(m => m.Name == "InventoryUnit");
                if (lookup == null)
                {
                    var lookupModel = new Lookup { Name = "InventoryUnit", Description = "Inventory Units types." };
                    model.LookupId = await this.unitOfWork.Lookups.InsertAsync(lookupModel);
                }
                else
                {
                    model.LookupId = lookup.LookupId;
                }
            }

            var valueModel = new LookupValue
            {
                LookupId = model.LookupId,
                Name = model.Name,
                CreatedDate = DateTime.Now,
                CreatedBy = model.CreatedBy,
            };
            if (await this.CheckLookupValueName(model.Name, model.LookupId))
            {
                return -1;
            }

            return await this.unitOfWork.LookupValues.InsertAsync(valueModel);
        }

        /// <inheritdoc/>
        public async Task<int> CreateCategoryAsync(LookupValueModel model)
        {
            if (model.LookupId == 0)
            {
                var lookup = await this.unitOfWork.Lookups.FindAsync(m => m.Name == "InventoryCategory");
                if (lookup == null)
                {
                    var lookupModel = new Lookup { Name = "InventoryCategory", Description = "Inventory Category types." };
                    model.LookupId = await this.unitOfWork.Lookups.InsertAsync(lookupModel);
                }
                else
                {
                    model.LookupId = lookup.LookupId;
                }
            }

            var valueModel = new LookupValue
            {
                LookupId = model.LookupId,
                Name = model.Name,
                CreatedDate = DateTime.Now,
                CreatedBy = model.CreatedBy
            };
            if (await this.CheckLookupValueName(model.Name, model.LookupId))
            {
                return -1;
            }

            return await this.unitOfWork.LookupValues.InsertAsync(valueModel);
        }

        /// <inheritdoc/>
        public async Task<int> CreateRackAsync(LookupValueModel model)
        {
            if (model.LookupId == 0)
            {
                var lookup = await this.unitOfWork.Lookups.FindAsync(m => m.Name == "InventoryRack");
                if (lookup == null)
                {
                    var lookupModel = new Lookup { Name = "InventoryRack", Description = "Inventory rack numbers." };
                    model.LookupId = await this.unitOfWork.Lookups.InsertAsync(lookupModel);
                }
                else
                {
                    model.LookupId = lookup.LookupId;
                }
            }

            var valueModel = new LookupValue
            {
                LookupId = model.LookupId,
                Name = model.Name,
                CreatedDate = DateTime.Now,
                CreatedBy = model.CreatedBy
            };
            if (await this.CheckLookupValueName(model.Name, model.LookupId))
            {
                return -1;
            }

            return await this.unitOfWork.LookupValues.InsertAsync(valueModel);
        }

        /// <inheritdoc/>
        public async Task<int> CreateGstAsync(LookupValueModel model)
        {
            if (model.LookupId == 0)
            {
                var lookup = await this.unitOfWork.Lookups.FindAsync(m => m.Name == "InventoryGst");
                if (lookup == null)
                {
                    var lookupModel = new Lookup { Name = "InventoryGst", Description = "Inventory Gst." };
                    model.LookupId = await this.unitOfWork.Lookups.InsertAsync(lookupModel);
                }
                else
                {
                    model.LookupId = lookup.LookupId;
                }
            }

            var valueModel = new LookupValue
            {
                LookupId = model.LookupId,
                Name = model.Name,
                CreatedDate = DateTime.Now,
                CreatedBy = model.CreatedBy,
            };
            if (await this.CheckLookupValueName(model.Name, model.LookupId))
            {
                return -1;
            }

            return await this.unitOfWork.LookupValues.InsertAsync(valueModel);
        }

        /// <inheritdoc/>
        public async Task<IEnumerable<LookupValueModel>> FetchLookupValues(LookupValueModel model)
        {
            var where = $@" WHERE 1 = 1 ";

            // if (!string.IsNullOrEmpty(model.FromDate))
            //if (!string.IsNullOrEmpty(model.FromDate))
            //{
            //    where += $@" AND ""CreatedDate""::DATE >= '{model.FromDate}'::DATE";               
            //}

            //if (!string.IsNullOrEmpty(model.ToDate))
            //{
            //    where += $@" AND ""CreatedDate""::DATE <= '{model.ToDate}'";
            //}

            //if (model.CreatedBy != null)
            //{
            //    where += $@" and ""CreatedBy""={model.CreatedBy}";
            //}

            //if (model.LookupValueId != 0)
            //{
            //    where += $@" and ""LookupValueId""={model.LookupValueId}";
            //}
            if (model.FromDate != null)
            {
                where += $@" AND L.""CreatedDate""::DATE >= '{model.FromDate}'::timestamp without time zone ";
            }
            if (model.ToDate != null)
            {
                where += $@" AND L.""CreatedDate""::DATE <= '{model.ToDate}'::timestamp without time zone ";
            }
            if (model.CreatedBy != null)
            {
                where += $@" and L.""CreatedBy""={model.CreatedBy}";
            }
            if (model.LookupValueId != 0)
            {
                where += $@" and L.""LookupValueId""={model.LookupValueId}";
            }
            var query = $@"SELECT L.*
                        , A.""FullName"" as ""CreatedByName"",CR.""RoleName"" as ""CreatedByRole"",M.""FullName"" as ""ModifiedByName""
	                            FROM ""LookupValue"" L
                                left join ""Account"" A on A.""AccountId"" = L.""CreatedBy""
                                left join ""Role"" CR on CR.""RoleId""=A.""RoleId""
                                left join ""Account"" M on M.""AccountId"" = L.""ModifiedBy""
                                {where} and ""LookupId"" = (Select ""LookupId"" from ""Lookup"" where ""Name"" = '{model.Name}') 
                                 order by L.""CreatedDate"" desc";
            //var query = $@"SELECT ""LookupValueId"", ""LookupId"", ""Name"", ""CreatedDate"", ""CreatedBy"", ""ModifiedBy"", ""ModifiedDate""

            //                    FROM ""LookupValue"" {where} and ""LookupId"" = (Select ""LookupId"" from ""Lookup"" where ""Name"" = '{model.Name}') order by ""CreatedDate"" desc";
            return await this.unitOfWork.Current.QueryAsync<LookupValueModel>(query);
        }

        /// <inheritdoc/>
        public async Task<int> UpdateLookupValueAsync(LookupValueModel model)
        {
            var lookupValueName = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<int>($@"select count(""LookupValueId"") from ""LookupValue"" where LOWER(TRIM(""Name"")) = LOWER(TRIM('{model.Name}')) AND ""LookupId"" = {model.LookupId} AND ""LookupValueId"" <> {model.LookupValueId}");
            if (lookupValueName > 0)
            {
                return -1;
            }
            var lookupValue = await this.unitOfWork.LookupValues.FindAsync(m => m.LookupValueId == model.LookupValueId);
            lookupValue.Name = model.Name;
            lookupValue.ModifiedBy = model.ModifiedBy;
            lookupValue.ModifiedDate = DateTime.UtcNow.AddMinutes(330);
            return await this.unitOfWork.LookupValues.UpdateAsync(lookupValue);
        }

        /// <inheritdoc/>
        public async Task<int> DeleteLookupValueAsync(int lookupValueId)
        {
            var query = $@"Delete from ""LookupValue"" where ""LookupValueId"" = {lookupValueId}";
            return await this.unitOfWork.Current.ExecuteAsync(query);
        }

        /// <inheritdoc/>
        public async Task<int> CreateInventoryProduct(ProductModel model)
        {
            var productName =
                await this.unitOfWork.InventoryProducts.FindAsync(m => m.ProductName == model.ProductName);

            if (productName != null)
            {
                if (model.IsGetProductId)
                {
                    return productName.InventoryProductId * -1;
                }
                else
                {
                    return -1;
                }
            }

            var product = new InventoryProduct
            {
                CompanyId = model.CompanyId,
                CreatedDate = DateTime.Now,
                CreatedBy = model.CreatedBy,
                CategoryId = (int)model.CategoryId,
                IsBatchNumber = model.IsBatchNumber,
                IsExpiry = model.IsExpiry,
                ProductName = model.ProductName,
                PurchaseUnit = model.PurchaseUnit,
                PurchaseUnitQuantity = model.PurchaseUnitQty,
                TaxId = model.TaxId
            };
            return await this.unitOfWork.InventoryProducts.InsertAsync(product);
        }

        /// <inheritdoc/>
        public async Task<int> UpdateInventoryProduct(ProductModel model)
        {
            var record =
                await this.unitOfWork.InventoryProducts.FindAsync(
                    m => m.InventoryProductId == model.InventoryProductId);
            record.CompanyId = model.CompanyId;
            record.CategoryId = (int)model.CategoryId;
            record.IsBatchNumber = model.IsBatchNumber;
            record.IsExpiry = model.IsExpiry;
            record.ProductName = model.ProductName;
            record.PurchaseUnit = model.PurchaseUnit;
            record.PurchaseUnitQuantity = model.PurchaseUnitQty;
            record.TaxId = model.TaxId;
            record.ModifiedBy = model.ModifiedBy;
            record.ModifiedDate = DateTime.Now;
            return await this.unitOfWork.InventoryProducts.UpdateAsync(record);
        }

        /// <inheritdoc/>
        public async Task<IEnumerable<ProductModel>> FetchInventoryProduct(ProductModel model)
        {
            var where = "where 1=1";

            if (!string.IsNullOrEmpty(model.ProductName))
            {
                where += $@" and IP.""ProductName"" ilike '%{model.ProductName}%'";
            }

            if (!string.IsNullOrEmpty(model.CategoryName))
            {
                where += $@" and Cat.""Name"" ilike '%{model.CategoryName}%'";
            }

            if (model.CategoryId > 0)
            {
                where += $@" and IP.""CategoryId"" = {model.CategoryId}";
            }

            if (!string.IsNullOrEmpty(model.BulkProductIds))
            {
                where += $@" and IP.""InventoryProductId"" in ({model.BulkProductIds})";
            }

            var query = $@"SELECT count(IP.*) over() as ""TotalItems"" ,IP.""InventoryProductId"", IP.""ProductName"", IP.""CompanyId"", IP.""CategoryId"",IP.""TaxId"", 
                                     IP.""PurchaseUnit"", IP.""PurchaseUnitQuantity"", 
                                    IP.""IsBatchNumber"", IP.""IsExpiry"", IP.""CreatedBy"", IP.""CreatedDate"", IP.""ModifiedBy"", IP.""ModifiedDate"",C.""Name"" as ""CompanyName"",
                                    Cat.""Name"" as ""CategoryName"",Tax.""Name"" as ""Tax"",PurchaseUnit.""Name"" as ""PurchaseUnitName"",
                                    A.""FullName"" as ""CreatedByName"",M.""FullName"" as ""ModifiedByName""
	                                    FROM ""InventoryProduct"" IP join ""Company"" C on C.""CompanyId"" = IP.""CompanyId""
	                                    join ""LookupValue"" Cat on Cat.""LookupValueId"" = IP.""CategoryId""
	                                    join ""LookupValue"" Tax on Tax.""LookupValueId"" = IP.""TaxId""	                                    
	                                    join ""LookupValue"" PurchaseUnit on PurchaseUnit.""LookupValueId"" = IP.""PurchaseUnit""	                                   
	                                    left join ""Account"" A on A.""AccountId"" = IP.""CreatedBy""
	                                    left join ""Account"" M on M.""AccountId"" = IP.""ModifiedBy"" 
	                                    {where} order by IP.""CreatedDate"" desc";

            if (model.PageIndex != null && model.PageSize != null)
            {
                model.PageIndex = model.PageIndex > 0 ? model.PageIndex - 1 : model.PageIndex;
                query += $@" limit {model.PageSize} offset {model.PageIndex * model.PageSize}";
            }

            return await this.unitOfWork.Current.QueryAsync<ProductModel>(query);
        }

        /// <inheritdoc/>
        public async Task<int> DeleteInventoryProduct(int inventoryProductId)
        {
            var query = $@"Delete from ""InventoryProduct"" where ""InventoryProductId"" = {inventoryProductId}";
            return await this.unitOfWork.Current.ExecuteAsync(query);
        }

        /// <inheritdoc/>
        public async Task<IEnumerable<InventoryStockModel>> FetchInventoryStocks(InventoryStockModel model)
        {
            var where = "where 1=1 ";
            if (!string.IsNullOrEmpty(model.ProductName))
            {
                where += $@" and IP.""ProductName"" ilike '%{model.ProductName}%'";
            }

            if (!string.IsNullOrEmpty(model.CategoryName))
            {
                where += $@" and LV.""Name"" ilike '%{model.CategoryName}%'";
            }

            if (!string.IsNullOrEmpty(model.BatchNumber))
            {
                where += $@" and InS.""BatchNumber"" ilike '%{model.BatchNumber}%'";
            }

            if (model.InventoryProductId != null)
            {
                where += $@" and InS.""InventoryProductId"" = {model.InventoryProductId}";
            }

            if (model.InventoryWareHouseId != null)
            {
                where += $@" and InS.""InventoryWareHouseId"" = {model.InventoryWareHouseId} ";
            }

            if (!string.IsNullOrEmpty(model.BulkProductIds))
            {
                where += $@" and InS.""InventoryProductId"" in ({model.BulkProductIds}) and (InS.""QuantityIn""::int - InS.""QuantityOut""::int) > 0";
            }

            var query =
                $@"SELECT distinct InS.""InventoryStockId"" , count(InS.*) over() as ""TotalItems"",IP.""ProductName"",LV.""Name"" as ""CategoryName"",InS.""InventoryProductId"",InS.""CreatedDate"", InS.""QuantityIn"", InS.""QuantityOut"", InS.""BatchNumber"", InS.""ExpiryDate"", InS.""PurchaseRate"",
	                            (InS.""QuantityIn""::int - InS.""QuantityOut""::int) as ""AvailableQty"",Tax.""Name"" as ""TaxPercentage"",InS.""TaxId"",InS.""InventoryWareHouseId"",
                                IPR.""RackName"",IPD.""InventoryProductDetailId"",IPD.""ROQ"",IPD.""ROL""
	                            FROM ""InventoryStock"" InS join ""InventoryProduct"" IP on IP.""InventoryProductId"" = InS.""InventoryProductId""
	                            join ""LookupValue"" LV on LV.""LookupValueId"" = IP.""CategoryId""
                                join ""LookupValue"" Tax on Tax.""LookupValueId"" = InS.""TaxId""
                                left join ""InventoryProductDetail"" IPD on IPD.""InventoryProductId"" = InS.""InventoryProductId"" and IPD.""InventoryWareHouseId"" = InS.""InventoryWareHouseId""
	                            left join ""InventoryProductRack"" IPR on IPR.""InventoryProductRackId"" = IPD.""InventoryProductRackId""
	                            {where} order by InS.""CreatedDate"" desc";

            if (model.PageIndex != null && model.PageSize != null)
            {
                model.PageIndex = model.PageIndex > 0 ? model.PageIndex - 1 : model.PageIndex;
                query += $@"  limit {model.PageSize} offset {model.PageSize * model.PageIndex}";
            }

            return await this.unitOfWork.Current.QueryAsync<InventoryStockModel>(query);
        }

        /// <inheritdoc/>
        public async Task<int> UpdateInventoryStocks(InventoryStockModel model)
        {
            var record = await this.unitOfWork.InventoryStocks.FindAsync(m => m.InventoryStockId == model.InventoryStockId);
            try
            {
                {
                    record.BatchNumber = model.BatchNumber;
                    record.ExpiryDate = model.ExpiryDate ?? record.ExpiryDate;
                    record.ModifiedBy = model.ModifiedBy;
                    record.ModifiedDate = DateTime.UtcNow.AddMinutes(330);
                }
            }
            catch (Exception ex)
            {

            }
            return await this.unitOfWork.InventoryStocks.UpdateAsync(record);
            //return 1;
        }
        /// <inheritdoc/>
        public async Task<IEnumerable<PurchaseBillModel>> FetchAddedPurchaseBillAsync(int? inventoryPurchaseHeaderId, string billNumber)
        {
            var where = " where 1=1 ";
            if (inventoryPurchaseHeaderId != null)
            {
                where += $@" and IPH.""InventoryPurchaseHeaderId"" = {inventoryPurchaseHeaderId}";
            }

            if (!string.IsNullOrEmpty(billNumber))
            {
                where += $@" and IPH.""BillNumber"" = '{billNumber}'";
            }

            var query =
                $@"SELECT IPH.""InventoryPurchaseHeaderId"",
                                IP.""ProductName"",
                                IPD.""BatchNumber"",
                                IPD.""ExpiryDate"",
                                IPD.""PurchaseRate"",
                                IPD.""Quantity"",
                                IPD.""Free"",
                                IPD.""Total"", 
                                IPD.""DiscountPerItem"",
                                IPD.""Discount"",
                                IPH.""Discount"" as ""OverallDiscount"",
                                IPD.""TaxPerItem"",
                                IPD.""TaxAmount"",
                                IPD.""NetAmount"",
                                IPH.""BillAmount"",
                                IPH.""Taxes"",
                        IPH.""NetAmount"" as ""OverallAmount"",

								S.""Name"" as ""SupplierName"",A.""FullName"" as ""CreatedByName"", R.""RoleName"",
								IPH.""BillNumber"", IPH.""BillDate"", IPH.""BillType"",IPH.""SupplierId""
	                                FROM ""InventoryPurchaseHeader"" IPH 
	                                join ""InventoryPurchaseDetail"" IPD on IPD.""InventoryPurchaseHeaderId"" = IPH.""InventoryPurchaseHeaderId""
	                                join ""InventoryProduct"" IP on IP.""InventoryProductId"" = IPD.""InventoryProductId""
									join ""Supplier"" S on S.""SupplierId"" = IPH.""SupplierId""
									join ""Account"" A on A.""AccountId"" = IPH.""CreatedBy""
									join ""Role"" R on R.""RoleId"" = A.""RoleId""

	                                {where} ";
            return await this.unitOfWork.Current.QueryAsync<PurchaseBillModel>(query);
        }

        /// <inheritdoc/>
        public async Task<int> AddPurchaseBillAsync(InventoryPurchaseBillModel model)
        {
            try
            {
                var inventoryProduct =
                await this.unitOfWork.InventoryProducts.FindAsync(m => m.InventoryProductId == model.ProductId);
                if (inventoryProduct == null)
                {
                    return -1;
                }

                var purchaseBasedOnUnit = model.PurchaseRate
                                          / inventoryProduct.PurchaseUnitQuantity;
                var quantityForStock = (model.Quantity * inventoryProduct.PurchaseUnitQuantity);

                int freeForStock;

                if (model.Free != null)
                {
                    freeForStock = (Convert.ToInt32(model.Free) * inventoryProduct.PurchaseUnitQuantity);
                }
                else
                {
                    freeForStock = 0;
                }

                using (var transaction = this.unitOfWork.BeginTransaction())
                {
                    var inventoryPurchase = new InventoryPurchaseHeader
                    {
                        BillAmount = model.Total,
                        BillDate = model.BillDate.Add(DateTime.Now.TimeOfDay),
                        BillNumber = model.BillNumber,
                        BillType = model.BillType,
                        CreatedBy = model.CreatedBy,
                        CreatedDate = DateTime.Now,
                        Discount = model.DiscountAmount,
                        NetAmount = model.NetAmount,
                        SupplierId = model.SupplierId,
                        Taxes = model.TaxAmount
                    };
                    inventoryPurchase.InventoryPurchaseHeaderId =
                        await this.InsertIntoPurchaseHeader(inventoryPurchase, transaction);
                    if (inventoryPurchase.InventoryPurchaseHeaderId == 0 || inventoryPurchase.InventoryPurchaseHeaderId < 0)
                    {
                        transaction.Rollback();
                        return -1;
                    }

                    var stocks = new InventoryStock
                    {
                        QuantityIn = quantityForStock + freeForStock,
                        QuantityOut = 0,
                        BatchNumber = model.BatchNumber,
                        ExpiryDate = model.ExpiryDate,
                        PurchaseRate = purchaseBasedOnUnit,
                        ModifiedBy = model.ModifiedBy,
                        InventoryProductId = model.ProductId,
                        CreatedBy = model.CreatedBy,
                        TaxId = inventoryProduct.TaxId,
                        CreatedDate = DateTime.Now,
                        DepartmentId = null
                    };

                    stocks.InventoryStockId = await this.InsertIntoInventoryStock(stocks, transaction);

                    if (stocks.InventoryStockId == 0 || stocks.InventoryStockId < 0)
                    {
                        transaction.Rollback();
                        return -2;
                    }

                    var purchaseDetail = new InventoryPurchaseDetail
                    {
                        InventoryPurchaseHeaderId = inventoryPurchase.InventoryPurchaseHeaderId,
                        Free = model.Free != null ? Convert.ToDouble(model.Free.ToString()) : 0,
                        Quantity = model.Quantity,
                        PurchaseRate = model.PurchaseRate,
                        NetAmount = model.NetAmount,
                        BatchNumber = model.BatchNumber,
                        ExpiryDate = model.ExpiryDate,
                        TaxId = inventoryProduct.TaxId,
                        InventoryStockId = stocks.InventoryStockId,
                        Discount = model.Discount,
                        InventoryProductId = model.ProductId,
                        TaxAmount = model.TaxAmount,
                        TaxPerItem = model.TaxPerItem,
                        DiscountPerItem = model.DiscountAmount,
                        Total = model.Total
                    };

                    purchaseDetail.InventoryPurchaseDetailId = await this.unitOfWork.InventoryPurchaseDetails.InsertAsync(purchaseDetail, transaction);

                    if (purchaseDetail.InventoryPurchaseDetailId == 0)
                    {
                        transaction.Rollback();
                        return -3;
                    }

                    transaction.Commit();
                    return inventoryPurchase.InventoryPurchaseHeaderId;
                }
            }
            catch (Exception e)
            {
                if (e.Message.Contains("numeric field overflow"))
                {
                    return -10;
                }
                else
                {
                    throw;
                }
            }

        }

        /// <inheritdoc/>
        public async Task<string> FetchBillNumber()
        {
            var lastBillNumberQuery = $@"SELECT ""BillNumber"" FROM ""InventoryPurchaseReturnHeader""
                                                ORDER BY ""InventoryPurchaseReturnHeaderId"" DESC LIMIT 1";
            var oldBillNumber = await this.unitOfWork.Current.QuerySingleOrDefaultAsync<string>(lastBillNumberQuery);
            if (string.IsNullOrEmpty(oldBillNumber))
            {
                // INAB2021040101
                return "IN" + CoreFilter.Random(2) + DateTime.Now.ToString("yyyyMMdd") + "1";
            }
            else
            {
                var stringLiterals = oldBillNumber.Substring(0, 4);
                var previousBillDateWithCount = oldBillNumber.Substring(4);
                var counter = previousBillDateWithCount.Substring(8);
                var date = previousBillDateWithCount.Substring(0, 8);
                if (date != DateTime.Now.ToString("yyyyMMdd"))
                {
                    return "IN" + CoreFilter.Random(2) + DateTime.Now.ToString("yyyyMMdd") + "1";
                }

                counter = (int.Parse(counter) + 1).ToString();
                return stringLiterals + date + counter;
            }
        }

        /// <inheritdoc/>
        public async Task<IEnumerable<InventoryPurchaseBillReturnModel>> FetchAddedReturnPurchaseBillAsync(
            int inventoryPurchaseReturnHeaderId)
        {
            var query =
                $@"SELECT IPRH.""InventoryPurchaseReturnHeaderId"", IPRD.""InventoryPurchaseReturnDetailId"",IP.""ProductName"" ,IPRH.""BillNumber"", IPRH.""ReturnDate"", 
                                InS.""PurchaseRate"",IPRD.""Quantity"",IPRD.""Total"",IPRD.""DiscountPercentage"",IPRD.""DiscountPerItem"",IPRD.""TaxAmount"",  IPRD.""NetAmount"",
                                IPRH.""ReturnAmount"" as ""OverallReturnAmount"", IPRH.""Discount"" as ""OverallDiscountRs"", IPRH.""Taxes"" as ""OverallTaxesRs"",
                                IPRH.""NetAmount"" as ""OverallNetAmount"",IPRD.""InventoryProductId"", IPRD.""TaxId"", IPRD.""InventoryStockId""	   
	                                FROM ""InventoryPurchaseReturnHeader"" IPRH 
	                                join ""InventoryPurchaseReturnDetail"" IPRD on IPRD.""InventoryPurchaseReturnHeaderId"" = IPRH.""InventoryPurchaseReturnHeaderId""
	                                join ""InventoryProduct"" IP on IP.""InventoryProductId"" = IPRD.""InventoryProductId""
	                                join ""InventoryStock"" InS on InS.""InventoryStockId"" = IPRD.""InventoryStockId""
	                                where IPRH.""InventoryPurchaseReturnHeaderId"" = {inventoryPurchaseReturnHeaderId}";
            return await this.unitOfWork.Current.QueryAsync<InventoryPurchaseBillReturnModel>(query);
        }

        /// <inheritdoc/>
        public async Task<int> AddReturnPurchaseBillAsync(InventoryPurchaseReturnModel model)
        {
            using (var transaction = this.unitOfWork.BeginTransaction())
            {
                var headerRecord = new InventoryPurchaseReturnHeader
                {
                    Discount = model.DiscountInRupee,
                    Taxes = model.GstInRupee,
                    ModifiedBy = model.ModifiedBy,
                    BillNumber = model.BillNumber,
                    SupplierId = model.SupplierId,
                    ModifiedDate = DateTime.Now,
                    ReturnAmount = model.Total,
                    NetAmount = model.NetAmount,
                    CreatedBy = model.CreatedBy,
                    CreatedDate = DateTime.Now,
                    ReturnDate = model.BillDate.Add(DateTime.Now.TimeOfDay)
                };
                headerRecord.InventoryPurchaseReturnHeaderId =
                    await this.InsertIntoPurchaseReturnHeader(headerRecord, transaction);

                if (headerRecord.InventoryPurchaseReturnHeaderId < 0
                    || headerRecord.InventoryPurchaseReturnHeaderId == 0)
                {
                    transaction.Rollback();
                    return -1;
                }

                var stockInfo =
                    await this.unitOfWork.InventoryStocks.FindAsync(m => m.InventoryStockId == model.InventoryStockId);
                if (stockInfo == null)
                {
                    transaction.Rollback();
                    return -2;
                }

                stockInfo.QuantityOut += model.Quantity;
                stockInfo.ModifiedBy = model.ModifiedBy;
                stockInfo.ModifiedDate = DateTime.Now;
                var stockInfoResponse = await this.unitOfWork.InventoryStocks.UpdateAsync(stockInfo, transaction);
                if (stockInfoResponse < 0 || stockInfoResponse == 0)
                {
                    transaction.Rollback();
                    return -3;
                }

                var detail = new InventoryPurchaseReturnDetail
                {
                    InventoryPurchaseReturnHeaderId = headerRecord.InventoryPurchaseReturnHeaderId,
                    SerialNo = 0,
                    InventoryProductId = model.ProductId,
                    NetAmount = model.NetAmount,
                    DiscountPerItem = model.DiscountInRupee,
                    Total = model.Total,
                    DiscountPercentage = model.DiscountPercentage,
                    InventoryStockId = stockInfo.InventoryStockId,
                    Quantity = model.Quantity,
                    TaxAmount = model.GstInRupee,
                    TaxId = model.TaxId
                };
                detail.InventoryPurchaseReturnDetailId = await this.InsertIntoPurchaseReturnDetails(detail, transaction);
                if (detail.InventoryPurchaseReturnDetailId < 0 || detail.InventoryPurchaseReturnDetailId == 0)
                {
                    transaction.Rollback();
                    return -4;
                }

                transaction.Commit();
                return headerRecord.InventoryPurchaseReturnHeaderId;
            }
        }

        /// <inheritdoc/>
        public async Task<IEnumerable<InventoryDashboardModel>> FetchDashboardReorderLevel()
        {
            var query = $@"Select (InS.""QuantityIn"" - InS.""QuantityOut"") as ""AvailableQty"",* from ""InventoryStock"" InS join ""InventoryProduct"" IP on IP.""InventoryProductId"" = InS.""InventoryProductId""
                            where IP.""ReorderLevelQuantity"" > (InS.""QuantityIn"" - InS.""QuantityOut"") ";
            return await this.unitOfWork.Current.QueryAsync<InventoryDashboardModel>(query);
        }

        /// <inheritdoc/>
        public async Task<IEnumerable<InventoryDashboardModel>> FetchDashboardExpiryItem()
        {
            var query = $@"Select (InS.""QuantityIn"" - InS.""QuantityOut"") as ""AvailableQty"",* from ""InventoryStock"" InS join ""InventoryProduct"" IP on IP.""InventoryProductId"" = InS.""InventoryProductId""
                            where InS.""ExpiryDate""::date < now()::date";
            return await this.unitOfWork.Current.QueryAsync<InventoryDashboardModel>(query);
        }

        /// <inheritdoc/>
        public async Task<IEnumerable<InventoryDashboardModel>> FetchDashboardNextMonthExpiryItem()
        {
            var query = $@"Select (InS.""QuantityIn"" - InS.""QuantityOut"") as ""AvailableQty"",* from ""InventoryStock"" InS join ""InventoryProduct"" IP on IP.""InventoryProductId"" = InS.""InventoryProductId""
                            where (Extract(Month from InS.""ExpiryDate"") = (extract(Month from now())+1) and 
								  Extract(Year from InS.""ExpiryDate"") = (extract(Year from now())))";
            return await this.unitOfWork.Current.QueryAsync<InventoryDashboardModel>(query);
        }

        /// <inheritdoc/>
        public async Task<IEnumerable<InventoryDashboardModel>> FetchDashboardCurrentMonthExpireItem()
        {
            var query = $@"Select (InS.""QuantityIn"" - InS.""QuantityOut"") as ""AvailableQty"",* from ""InventoryStock"" InS join ""InventoryProduct"" IP on IP.""InventoryProductId"" = InS.""InventoryProductId""
                            where (Extract(Month from InS.""ExpiryDate"") = (extract(Month from now())) and 
								  Extract(Year from InS.""ExpiryDate"") = (extract(Year from now())))";
            return await this.unitOfWork.Current.QueryAsync<InventoryDashboardModel>(query);
        }

        /// <inheritdoc/>
        public async Task<IEnumerable<ProductModel>> FetchInventoryProducts()
        {
            var query = $@"SELECT count(IP.*) over() as ""TotalItems"" ,IP.""InventoryProductId"", IP.""ProductName"", IP.""CompanyId"", IP.""CategoryId"",IP.""TaxId"",IPD.""ROL"", IPR.""InventoryProductRackId"" as ""RackId"", IP.""PurchaseUnit"", IP.""PurchaseUnitQuantity"",
                                    IP.""IsBatchNumber"", IP.""IsExpiry"", IP.""CreatedBy"", IP.""CreatedDate"", IP.""ModifiedBy"", IP.""ModifiedDate"",C.""Name"" as ""CompanyName"",
                                    Cat.""Name"" as ""CategoryName"",Tax.""Name"" as ""Tax"",IPR.""RackName"",PurchaseUnit.""Name"" as ""PurchaseUnitName"",
                                    A.""FullName"" as ""CreatedByName"",M.""FullName"" as ""ModifiedByName""
                                        FROM ""InventoryProduct"" IP join ""Company"" C on C.""CompanyId"" = IP.""CompanyId""
                                        join ""LookupValue"" Cat on Cat.""LookupValueId"" = IP.""CategoryId""
                                        join ""LookupValue"" Tax on Tax.""LookupValueId"" = IP.""TaxId""
						                left join ""InventoryProductDetail"" IPD on IPD.""InventoryProductId""=IP.""InventoryProductId""
						                left join ""InventoryProductRack"" IPR on IPR.""InventoryProductRackId""	=IPD.""InventoryProductRackId""	
                                        join ""LookupValue"" PurchaseUnit on PurchaseUnit.""LookupValueId"" = IP.""PurchaseUnit""
                                        left join ""Account"" A on A.""AccountId"" = IP.""CreatedBy""
                                        left join ""Account"" M on M.""AccountId"" = IP.""ModifiedBy""
                                        order by IP.""CreatedDate"" desc";
            //var query = $@"SELECT count(IP.*) over() as ""TotalItems"" ,IP.""InventoryProductId"", IP.""ProductName"", IP.""CompanyId"", IP.""CategoryId"", IP.""SupplierId"", IP.""TaxId"", IP.""MaxQuantity"", 
            //                        IP.""MinQuantity"", IP.""ReorderLevelQuantity"", IP.""RackId"", IP.""PurchaseUnit"", IP.""PurchaseUnitQuantity"", IP.""SaleUnit"", IP.""SaleUnitQuantity"", 
            //                        IP.""IsBatchNumber"", IP.""IsExpiry"", IP.""CreatedBy"", IP.""CreatedDate"", IP.""ModifiedBy"", IP.""ModifiedDate"",C.""Name"" as ""CompanyName"",
            //                        Cat.""Name"" as ""CategoryName"",Tax.""Name"" as ""Tax"",Rack.""Name"" as ""RackName"",PurchaseUnit.""Name"" as ""PurchaseUnitName"",
            //                        SaleUnit.""Name"" as ""SaleUnitName"",A.""FullName"" as ""CreatedByName"",M.""FullName"" as ""ModifiedByName""
            //                            FROM ""InventoryProduct"" IP join ""Company"" C on C.""CompanyId"" = IP.""CompanyId""
            //                            join ""LookupValue"" Cat on Cat.""LookupValueId"" = IP.""CategoryId""
            //                            join ""LookupValue"" Tax on Tax.""LookupValueId"" = IP.""TaxId""
            //                            join ""LookupValue"" Rack on Rack.""LookupValueId"" = IP.""RackId""
            //                            join ""LookupValue"" PurchaseUnit on PurchaseUnit.""LookupValueId"" = IP.""PurchaseUnit""
            //                            join ""LookupValue"" SaleUnit on SaleUnit.""LookupValueId"" = IP.""SaleUnit""
            //                            left join ""Account"" A on A.""AccountId"" = IP.""CreatedBy""
            //                            left join ""Account"" M on M.""AccountId"" = IP.""ModifiedBy""
            //                            order by IP.""CreatedDate"" desc";

            return await this.unitOfWork.Current.QueryAsync<ProductModel>(query);
            //return await this.unitOfWork.Current.QueryAsync<ProductModel>(query);
        }


        /// <inheritdoc/>
        public async Task<int> CheckProductAlreadyAlreadyExists(string name)
        {
            var query = $@"select count(*) from  ""InventoryProduct"" where lower(""ProductName"") = '{name.ToLower()}' ";
            return await this.unitOfWork.Current.QuerySingleOrDefaultAsync<int>(query);
        }

        /// <inheritdoc/>
        public async Task<IEnumerable<PurchaseBillModel>> FetchInventoryReturnBill(string billNumber)
        {
            try
            {
                var query = $@"
                             SELECT
	                            IP.""ProductName"",
                                IPRH.""BillNumber"" ,
	                            IPRH.""ReturnDate"" ""BillDate"" ,
	                            --IPH.""Bill-Type"",
	                            IPRH.""ReturnAmount"" ""Total"" ,
	                            IPRH.""Discount"",
	                            IPRH.""Taxes"",
	                            IPRH.""NetAmount"",
	                            IPRH.""CreatedDate"",
	                            CA.""FullName"" ""CreatedByName"", R.""RoleName"",
	                            IPRD.""Quantity"",
	                            --IPD.""Free"",
	                            i.""PurchaseRate"",
	                            s.""Name"" ""SupplierName"",
                                IPRH.""NetAmount"" as ""OverallAmount""
                            FROM
                                ""InventoryPurchaseReturnHeader"" IPRH
                                JOIN ""Supplier"" S ON S.""SupplierId"" = IPRH.""SupplierId""
                                JOIN ""InventoryPurchaseReturnDetail"" IPRD ON IPRD.""InventoryPurchaseReturnHeaderId"" = IPRH.""InventoryPurchaseReturnHeaderId""
                                JOIN ""InventoryProduct"" IP ON IP.""InventoryProductId"" = IPRD.""InventoryProductId""
                                Join ""Account"" CA on CA.""AccountId"" = IPRH.""CreatedBy""
                                join ""Role"" R on R.""RoleId"" = CA.""RoleId""
                                join ""InventoryStock"" i on i.""InventoryStockId"" = IPRD.""InventoryStockId""
                              where IPRH.""BillNumber"" = '{billNumber}'";

                return await this.unitOfWork.Current.QueryAsync<PurchaseBillModel>(query);
            }
            catch (Exception e)
            {
                //   
            }
            return null;
        }

        /// <inheritdoc/>
        public async Task<InventoryBillHeaderModel> FetchPurchaseBill(int? supplierId, int? purchaseHeaderId, string billNumber, int? locationId)
        {
            var where = " where 1=1 ";

            if (supplierId != null)
            {
                where += $@" and IPH.""SupplierId"" = {supplierId}";
            }

            if (purchaseHeaderId != null)
            {
                where += $@" and IPH.""InventoryPurchaseHeaderId"" = {purchaseHeaderId}";
            }

            if (!string.IsNullOrEmpty(billNumber))
            {
                where += $@" and IPH.""BillNumber"" = '{billNumber}'";
            }

            if (locationId != null)
            {
                where += $@" and (IPH.""LocationId"" = {locationId} or IPH.""LocationId"" is null)";
            }

            var headerQuery = $@"SELECT IPH.""InventoryPurchaseHeaderId"", IPH.""BillNumber"", IPH.""BillDate"", IPH.""BillType"", IPH.""SupplierId"", IPH.""BillAmount"", 
		                    IPH.""Discount"", IPH.""Taxes"", IPH.""NetAmount"", IPH.""CreatedBy"", IPH.""CreatedDate"", IPH.""ModifiedBy"", 
		                    IPH.""ModifiedDate"", IPH.""InventoryWareHouseId"", IWH.""Name"" as ""InventoryWareHouseName"",IPH.""LocationId""
	                    FROM ""InventoryPurchaseHeader"" IPH
                        join ""InventoryWareHouse"" IWH on IWH.""InventoryWareHouseId"" = IPH.""InventoryWareHouseId""
	                    {where}  order by IPH.""CreatedDate"" desc limit 1";

            var headerResponse = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<InventoryBillHeaderModel>(headerQuery);

            if (headerResponse == null)
            {
                return headerResponse;
            }

            headerResponse.Products = new List<InventoryBillDetailModel>();

            var detailQuery = $@"SELECT IPD.""InventoryPurchaseDetailId"", IPD.""InventoryPurchaseHeaderId"", IPD.""InventoryProductId"", 
		                        IPD.""Quantity"", IPD.""Free"", IPD.""PurchaseRate"", IPD.""Total"", IPD.""TaxPerItem"", IPD.""TaxAmount"", 
		                        IPD.""DiscountPerItem"", IPD.""Discount"" as ""DiscountAmount"", IPD.""NetAmount"",
		                        IPD.""InventoryStockId"",
		                        IP.""ProductName"",Com.""Name"" as ""CompanyName"",ISS.""BatchNumber"",
		                        Cat.""Name"" as ""CategoryName"",IP.""TaxId"",IPD.""TaxPerItem""::text as ""TaxPercentage"",
		                        IP.""IsExpiry"",IP.""IsBatchNumber"",
                                ISS.""ExpiryDate""
	                        FROM ""InventoryPurchaseDetail"" IPD
	                        join ""InventoryProduct"" IP on IP.""InventoryProductId"" = IPD.""InventoryProductId""
	                        join ""Company"" Com on Com.""CompanyId"" = IP.""CompanyId""
	                        join ""LookupValue"" Cat on Cat.""LookupValueId"" = IP.""CategoryId""
                            join ""InventoryStock"" ISS on ISS.""InventoryStockId"" = IPD.""InventoryStockId""
	                        where IPD.""InventoryPurchaseHeaderId"" = {headerResponse.InventoryPurchaseHeaderId}";

            headerResponse.Products = (await this.unitOfWork.Current.QueryAsync<InventoryBillDetailModel>(detailQuery)).ToList();

            return headerResponse;

        }

        /// <inheritdoc/>
        public async Task<int> AddInventoryPurchaseBillAsync(InventoryBillHeaderModel model)
        {
            var transaction = this.unitOfWork.BeginTransaction();
            int returnFunction(int number)
            {
                transaction.Rollback();
                return number;
            }

            var warehouse = await this.unitOfWork.InventoryWareHouses.FindAsync(w => w.InventoryWareHouseId == model.InventoryWareHouseId);
            if (warehouse != null && warehouse.LocationId == null)
            {
                model.LocationId = (int?)null;
            }

            var inventoryPurchaseBillHeader = new InventoryPurchaseHeader
            {
                BillAmount = model.BillAmount,
                BillDate = model.BillDate,
                BillNumber = model.BillNumber,
                BillType = model.BillType,
                CreatedBy = model.CreatedBy,
                CreatedDate = DateTime.Now,
                Discount = model.Discount,
                InventoryWareHouseId = model.InventoryWareHouseId,
                NetAmount = model.NetAmount,
                SupplierId = model.SupplierId,
                Taxes = model.Taxes,
                LocationId = model.LocationId,
                DueDate=model.DueDate
            };

            if (model.InventoryPurchaseHeaderId > 0)
            {
                var existingHeader = await this.unitOfWork.InventoryPurchaseHeaders.FindAsync(h => h.InventoryPurchaseHeaderId == model.InventoryPurchaseHeaderId);

                if (existingHeader == null)
                {
                    returnFunction(0);
                }
                inventoryPurchaseBillHeader.InventoryPurchaseHeaderId = existingHeader.InventoryPurchaseHeaderId;
                existingHeader.BillAmount = inventoryPurchaseBillHeader.BillAmount;
                existingHeader.Discount = inventoryPurchaseBillHeader.Discount;
                existingHeader.NetAmount = inventoryPurchaseBillHeader.NetAmount;
                existingHeader.Taxes = inventoryPurchaseBillHeader.Taxes;
                existingHeader.ModifiedBy = inventoryPurchaseBillHeader.CreatedBy;
                existingHeader.ModifiedDate = inventoryPurchaseBillHeader.CreatedDate;

                var updateHeaderResponse = await this.unitOfWork.InventoryPurchaseHeaders.UpdateAsync(existingHeader, transaction);
                if (updateHeaderResponse == 0)
                {
                    returnFunction(-1);
                }
            }
            else
            {
                inventoryPurchaseBillHeader.InventoryPurchaseHeaderId = await this.unitOfWork.InventoryPurchaseHeaders.InsertAsync(inventoryPurchaseBillHeader, transaction);
                if (inventoryPurchaseBillHeader.InventoryPurchaseHeaderId == 0)
                {
                    returnFunction(-1);
                }
            }

            foreach (var product in model.Products)
            {
                var inventoryProduct = await this.unitOfWork.InventoryProducts.FindAsync(p => p.InventoryProductId == product.InventoryProductId);

                #region stockCalculation
                var purchaseBasedOnUnit = product.PurchaseRate / inventoryProduct.PurchaseUnitQuantity;
                purchaseBasedOnUnit = Math.Round(purchaseBasedOnUnit, 2);

                var quantityForStock = product.Quantity * inventoryProduct.PurchaseUnitQuantity;
                int freeForStock;
                if (product.Free != null)
                {
                    freeForStock = (int)product.Free * inventoryProduct.PurchaseUnitQuantity;
                }
                else
                {
                    freeForStock = 0;
                }
                #endregion

                var inventoryPurchaseDetail = new InventoryPurchaseDetail
                {
                    Discount = product.DiscountAmount,
                    DiscountPerItem = product.DiscountPerItem,
                    Free = product.Free ?? 0,
                    NetAmount = product.NetAmount,
                    InventoryProductId = product.InventoryProductId,
                    InventoryPurchaseHeaderId = inventoryPurchaseBillHeader.InventoryPurchaseHeaderId,
                    PurchaseRate = product.PurchaseRate,
                    Quantity = product.Quantity,
                    TaxAmount = product.TaxAmount,
                    TaxPerItem = product.TaxPerItem,
                    Total = product.Total,
                    TaxId = product.TaxId
                };

                if (product.InventoryPurchaseDetailId == 0)
                {
                    var newStock = new InventoryStock
                    {
                        BatchNumber = product.BatchNumber,
                        CreatedBy = model.CreatedBy,
                        CreatedDate = DateTime.Now,
                        ExpiryDate = product.ExpiryDate,
                        InventoryProductId = product.InventoryProductId,
                        PurchaseRate = purchaseBasedOnUnit,
                        QuantityIn = (quantityForStock + freeForStock),
                        QuantityOut = 0,
                        TaxId = product.TaxId,
                        InventoryWareHouseId = model.InventoryWareHouseId
                    };

                    var extraCondition = string.Empty;
                    if (!string.IsNullOrEmpty(product.BatchNumber))
                    {
                        extraCondition += $@" and lower(""BatchNumber"") = lower('{product.BatchNumber}') ";
                    }
                    if (product.ExpiryDate != null)
                    {
                        extraCondition += $@" and ""ExpiryDate""::date = '{product.ExpiryDate?.ToString("yyyy-MM-dd")}'::date ";
                    }

                    var getPreviousStockQuery = $@"Select * from ""InventoryStock"" where ""InventoryProductId"" = {product.InventoryProductId} 
                                                    {extraCondition} and ""InventoryWareHouseId"" = {model.InventoryWareHouseId} order by ""CreatedDate"" desc limit 1";

                    var getPreviousStock = await this.unitOfWork.Current.QueryFirstOrDefaultAsync<InventoryStock>(getPreviousStockQuery);

                    if (getPreviousStock != null && (getPreviousStock.QuantityIn - getPreviousStock.QuantityOut) > 0)
                    {
                        if (getPreviousStock.ExpiryDate == product.ExpiryDate && getPreviousStock.PurchaseRate == purchaseBasedOnUnit)
                        {
                            getPreviousStock.QuantityIn += (quantityForStock + freeForStock);
                            getPreviousStock.ModifiedBy = model.CreatedBy;
                            getPreviousStock.ModifiedDate = DateTime.Now;
                            newStock.InventoryStockId = getPreviousStock.InventoryStockId;
                            var stockUpdateResponse = await this.unitOfWork.InventoryStocks.UpdateAsync(getPreviousStock, transaction);
                            if (stockUpdateResponse == 0)
                            {
                                returnFunction(-3);
                            }
                            inventoryPurchaseDetail.InventoryStockId = getPreviousStock.InventoryStockId;
                        }
                        else
                        {
                            newStock.InventoryStockId = await this.unitOfWork.InventoryStocks.InsertAsync(newStock, transaction);
                            if (newStock.InventoryStockId == 0)
                            {
                                returnFunction(-3);
                            }

                            inventoryPurchaseDetail.InventoryStockId = newStock.InventoryStockId;
                        }
                    }
                    else
                    {
                        newStock.InventoryStockId = await this.unitOfWork.InventoryStocks.InsertAsync(newStock, transaction);
                        if (newStock.InventoryStockId == 0)
                        {
                            returnFunction(-3);
                        }

                        inventoryPurchaseDetail.InventoryStockId = newStock.InventoryStockId;
                    }

                    inventoryPurchaseDetail.InventoryPurchaseDetailId = await this.unitOfWork.InventoryPurchaseDetails.InsertAsync(inventoryPurchaseDetail, transaction);
                    if (inventoryPurchaseDetail.InventoryPurchaseDetailId == 0)
                    {
                        returnFunction(-4);
                    }
                }
            }

            transaction.Commit();
            return inventoryPurchaseBillHeader.InventoryPurchaseHeaderId;
        }

        /// <inheritdoc/>
        public async Task<IEnumerable<string>> FetchExistingBatchNumbers(int inventoryProductId, int? locationId)
        {
            var where = $@" where ISS.""InventoryProductId"" = {inventoryProductId}";
            if (locationId != null)
            {
                where += $@" and IWH.""LocationId"" = {locationId}";
            }

            var query = $@"Select distinct ISS.""BatchNumber"" from ""InventoryStock"" ISS
   		                    join ""InventoryWareHouse"" IWH on IWH.""InventoryWareHouseId"" = ISS.""InventoryWareHouseId""
                                 {where}
                                group by ISS.""BatchNumber""";
            return await this.unitOfWork.Current.QueryAsync<string>(query);
        }

        /// <inheritdoc/>
        public async Task<int> DeletePurchaseBillItemAsync(InventoryBillHeaderModel model)
        {
            var purchaseHeader = await this.unitOfWork.InventoryPurchaseHeaders.FindAsync(m => m.InventoryPurchaseHeaderId == model.InventoryPurchaseHeaderId);
            if (purchaseHeader == null)
            {
                return -1;
            }


            var purchaseDetail = await this.unitOfWork.InventoryPurchaseDetails.FindAsync(m => m.InventoryPurchaseDetailId == model.InventoryPurchaseDetailId);
            if (purchaseDetail == null)
            {
                return -2;
            }

            var productDetail = await this.unitOfWork.InventoryProducts.FindAsync(p => p.InventoryProductId == purchaseDetail.InventoryProductId);

            if (productDetail == null)
            {
                return -4;
            }

            var stock = await this.unitOfWork.InventoryStocks.FindAsync(s => s.InventoryStockId == purchaseDetail.InventoryStockId);
            if (stock == null)
            {
                return -3;
            }

            var transaction = this.unitOfWork.BeginTransaction();
            var quantityForStockReduction = Convert.ToInt32((purchaseDetail.Quantity * productDetail.PurchaseUnitQuantity).ToString());

            #region header
            purchaseHeader.NetAmount -= purchaseDetail.NetAmount;
            purchaseHeader.BillAmount -= purchaseDetail.Total;
            purchaseHeader.Discount -= purchaseDetail.Discount;
            purchaseHeader.Taxes -= purchaseDetail.TaxAmount;
            purchaseHeader.ModifiedBy = model.CreatedBy;
            purchaseHeader.ModifiedDate = DateTime.UtcNow.AddMinutes(330);

            var updateHeader = await this.unitOfWork.InventoryPurchaseHeaders.UpdateAsync(purchaseHeader, transaction);
            if (updateHeader <= 0)
            {
                transaction.Rollback();
                return -5;
            }
            #endregion

            #region stock
            stock.QuantityIn -= quantityForStockReduction;
            stock.ModifiedBy = model.CreatedBy;
            stock.ModifiedDate = DateTime.UtcNow.AddMinutes(330);
            var stockResponse = await this.unitOfWork.InventoryStocks.UpdateAsync(stock, transaction);
            if (stockResponse <= 0)
            {
                transaction.Rollback();
                return -6;
            }
            #endregion

            var detailResponse = await this.unitOfWork.InventoryPurchaseDetails.DeleteAsync(purchaseDetail, transaction);
            if (!detailResponse)
            {
                transaction.Rollback();
                return -7;
            }
            transaction.Commit();

            var getDetailCount = (await this.unitOfWork.InventoryPurchaseDetails.FindAllAsync(m => m.InventoryPurchaseHeaderId == purchaseHeader.InventoryPurchaseHeaderId)).ToList();
            if (getDetailCount.Count == 0)
            {
                await this.unitOfWork.InventoryPurchaseHeaders.DeleteAsync(purchaseHeader);
            }

#pragma warning disable S2583 // Conditionally executed code should be reachable
            return detailResponse ? 1 : 0;
#pragma warning restore S2583 // Conditionally executed code should be reachable
        }

        /// <summary>
        /// The insert into purchase header.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <param name="transaction">
        /// The transaction.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        private async Task<int> InsertIntoPurchaseHeader(InventoryPurchaseHeader model, IDbTransaction transaction)
        {
            var checkHeaderRecord = await this.unitOfWork.InventoryPurchaseHeaders.FindAsync(
                                        m => m.BillNumber == model.BillNumber && m.SupplierId == model.SupplierId);
            if (checkHeaderRecord == null)
            {
                return await this.unitOfWork.InventoryPurchaseHeaders.InsertAsync(model, transaction);
            }
            else
            {
                checkHeaderRecord.BillAmount += Math.Round(model.BillAmount, 2);
                if (model.Discount != null)
                {
                    checkHeaderRecord.Discount += Math.Round((double)model.Discount, 2);
                }

                checkHeaderRecord.NetAmount += Math.Round(model.NetAmount, 2);
                checkHeaderRecord.Taxes += Math.Round(model.Taxes, 2);
                var response =
                    await this.unitOfWork.InventoryPurchaseHeaders.UpdateAsync(checkHeaderRecord, transaction);
                if (response > 0)
                {
                    return checkHeaderRecord.InventoryPurchaseHeaderId;
                }
                else
                {
                    return -1;
                }
            }
        }

        /// <summary>
        /// The insert into inventory stock.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <param name="transaction">
        /// The transaction.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        private async Task<int> InsertIntoInventoryStock(InventoryStock model, IDbTransaction transaction)
        {
            var checkStock = await this.unitOfWork.InventoryStocks.FindAsync(
                                 m => m.BatchNumber == model.BatchNumber && m.ExpiryDate == model.ExpiryDate
                                                                         // ReSharper disable once CompareOfFloatsByEqualityOperator
                                                                         && m.PurchaseRate == model.PurchaseRate);
            if (checkStock == null)
            {
                return await this.unitOfWork.InventoryStocks.InsertAsync(model, transaction);
            }
            else
            {
                checkStock.QuantityIn += model.QuantityIn;
                var response = await this.unitOfWork.InventoryStocks.UpdateAsync(checkStock, transaction);
                if (response > 0)
                {
                    return checkStock.InventoryStockId;
                }
                else
                {
                    return -1;
                }
            }
        }

        /// <summary>
        /// The insert into purchase return header.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <param name="transaction">
        /// The transaction.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        private async Task<int> InsertIntoPurchaseReturnHeader(
            InventoryPurchaseReturnHeader model,
            IDbTransaction transaction)
        {
            var findRecord = await this.unitOfWork.InventoryPurchaseReturnHeaders.FindAsync(
                                 m => m.BillNumber == model.BillNumber && m.SupplierId == model.SupplierId);
            if (findRecord == null)
            {
                model.ModifiedBy = null;
                model.ModifiedDate = null;
                return await this.unitOfWork.InventoryPurchaseReturnHeaders.InsertAsync(model, transaction);
            }
            else
            {
                findRecord.ModifiedDate = DateTime.Now;
                findRecord.ModifiedBy = model.ModifiedBy;
                findRecord.ReturnAmount += Math.Round(model.ReturnAmount);
                findRecord.Discount += model.Discount != null ? Math.Round((double)model.Discount) : 0;
                findRecord.NetAmount += Math.Round(model.NetAmount, 2);
                findRecord.Taxes += model.Taxes != null ? Math.Round((double)model.Taxes) : 0;
                var response = await this.unitOfWork.InventoryPurchaseReturnHeaders.UpdateAsync(findRecord, transaction);
                return response > 0 ? findRecord.InventoryPurchaseReturnHeaderId : -1;
            }
        }

        /// <summary>
        /// The insert into purchase return details.
        /// </summary>
        /// <param name="model">
        /// The model.
        /// </param>
        /// <param name="transaction">
        /// The transaction.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        private async Task<int> InsertIntoPurchaseReturnDetails(InventoryPurchaseReturnDetail model, IDbTransaction transaction)
        {
            var oldRecord = await this.unitOfWork.InventoryPurchaseReturnDetails.FindAllAsync(
                                m => m.InventoryPurchaseReturnHeaderId == model.InventoryPurchaseReturnHeaderId);
            var inventoryPurchaseReturnDetails = oldRecord.ToList();
            if (inventoryPurchaseReturnDetails.Count > 0)
            {
                var record = inventoryPurchaseReturnDetails.Max(m => m.InventoryPurchaseReturnDetailId);
                var greatestRecord = inventoryPurchaseReturnDetails.Find(m => m.InventoryPurchaseReturnDetailId == record);
                if (greatestRecord != null)
                {
                    model.SerialNo = greatestRecord.SerialNo + 1;
                }
            }
            else
            {
                model.SerialNo = 1;
            }

            return await this.unitOfWork.InventoryPurchaseReturnDetails.InsertAsync(model, transaction);
        }

        /// <summary>
        /// The check lookup value name.
        /// </summary>
        /// <param name="name">
        /// The name.
        /// </param>
        /// <param name="lookupId">
        /// The lookup id.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        private async Task<bool> CheckLookupValueName(string name, int lookupId)
        {
            var lookup = await this.unitOfWork.LookupValues.FindAllAsync(m => m.LookupId == lookupId);
            var match = lookup?.ToList().Find(
                m => string.Equals(m.Name, name, StringComparison.CurrentCultureIgnoreCase));
            return match != null;
        }
    }
}