Webiant Logo Webiant Logo
  1. No results found.

    Try your search with a different keyword or use * as a wildcard.

RfqService.cs

using Microsoft.AspNetCore.Http;
using Nop.Core;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Orders;
using Nop.Core.Http.Extensions;
using Nop.Data;
using Nop.Plugin.Misc.RFQ.Domains;
using Nop.Services.Catalog;
using Nop.Services.Customers;
using Nop.Services.Localization;
using Nop.Services.Orders;
using Nop.Services.Security;
using Nop.Services.Tax;

namespace Nop.Plugin.Misc.RFQ.Services;

/// <summary>
/// Represent the RFQ service
/// </summary>
public class RfqService
{
    #region Fields

    private readonly ICustomerService _customerService;
    private readonly IHttpContextAccessor _httpContextAccessor;
    private readonly ILocalizationService _localizationService;
    private readonly IPermissionService _permissionService;
    private readonly IProductService _productService;
    private readonly IRepository<Customer> _customerRepository;
    private readonly IRepository<Quote> _quoteRepository;
    private readonly IRepository<QuoteItem> _quoteItemRepository;
    private readonly IRepository<RequestQuote> _requestQuoteRepository;
    private readonly IRepository<RequestQuoteItem> _requestQuoteItemRepository;
    private readonly IRepository<ShoppingCartItem> _sciRepository;
    private readonly IShoppingCartService _shoppingCartService;
    private readonly IStoreContext _storeContext;
    private readonly ITaxService _taxService;
    private readonly IWorkContext _workContext;
    private readonly RfqMessageService _rfqMessageService;

    #endregion

    #region Ctor

    public RfqService(ICustomerService customerService,
        IHttpContextAccessor httpContextAccessor,
        ILocalizationService localizationService,
        IPermissionService permissionService,
        IProductService productService,
        IRepository<Customer> customerRepository,
        IRepository<Quote> quoteRepository,
        IRepository<QuoteItem> quoteItemRepository,
        IRepository<RequestQuote> requestQuoteRepository,
        IRepository<RequestQuoteItem> requestQuoteItemRepository,
        IRepository<ShoppingCartItem> sciRepository,
        IShoppingCartService shoppingCartService,
        IStoreContext storeContext,
        ITaxService taxService,
        IWorkContext workContext,
        RfqMessageService rfqMessageService)
    {
        _customerService = customerService;
        _httpContextAccessor = httpContextAccessor;
        _localizationService = localizationService;
        _permissionService = permissionService;
        _productService = productService;
        _customerRepository = customerRepository;
        _quoteRepository = quoteRepository;
        _quoteItemRepository = quoteItemRepository;
        _requestQuoteRepository = requestQuoteRepository;
        _requestQuoteItemRepository = requestQuoteItemRepository;
        _sciRepository = sciRepository;
        _shoppingCartService = shoppingCartService;
        _storeContext = storeContext;
        _taxService = taxService;
        _workContext = workContext;
        _rfqMessageService = rfqMessageService;
    }

    #endregion

    #region Utilities

    private void LogNote(IAdminNote noteItem, string note)
    {
        if (string.IsNullOrEmpty(note))
            return;

        note = DateTime.UtcNow.ToString(RfqDefaults.DateTimeStringFormat) + $": {note}";

        if (!string.IsNullOrEmpty(noteItem.AdminNotes))
            note += "\r\n";
        else
            noteItem.AdminNotes = string.Empty;

        noteItem.AdminNotes = noteItem.AdminNotes.Insert(0, note);
    }

    private async Task UpdateQuantityWithLogAsync(RequestQuoteItem requestQuoteItem, int newQuantity)
    {
        var note = string.Format(
            await _localizationService.GetResourceAsync("Plugins.Misc.RFQ.QuantityChanged"), requestQuoteItem.RequestedQty,
            newQuantity, await GetCurrentCustomerEmailAsync());

        LogNote(requestQuoteItem, note);

        requestQuoteItem.RequestedQty = newQuantity;
    }

    private async Task UpdateUnitPriceWithLogAsync(RequestQuoteItem requestQuoteItem, decimal newUnitPrice)
    {
        var note = string.Format(
            await _localizationService.GetResourceAsync("Plugins.Misc.RFQ.UnitPriceChanged"), requestQuoteItem.RequestedUnitPrice,
            newUnitPrice, await GetCurrentCustomerEmailAsync());

        LogNote(requestQuoteItem, note);

        requestQuoteItem.RequestedUnitPrice = newUnitPrice;
    }

    private async Task UpdateCustomerNotesWithLogAsync(RequestQuote requestQuote, string newCustomerNotes)
    {
        var note = await _localizationService.GetResourceAsync("Plugins.Misc.RFQ.CustomerNoteChanged");

        LogNote(requestQuote, note);

        requestQuote.CustomerNotes = newCustomerNotes;
    }

    private async Task SetIfExistsAsync(RequestQuoteItem requestQuoteItem, IFormCollection form, string formKey)
    {
        var key = $"{formKey}{requestQuoteItem.Id}";

        if (!form.ContainsKey(key))
            return;

        var formValue = form[key];

        switch (formKey)
        {
            case RfqDefaults.QUANTITY_FORM_KEY:
                if (int.TryParse(formValue, out var quantity) && requestQuoteItem.RequestedQty != quantity)
                    await UpdateQuantityWithLogAsync(requestQuoteItem, quantity);
                break;
            case RfqDefaults.UNIT_PRICE_FORM_KEY:
                if (decimal.TryParse(formValue, out var price) && requestQuoteItem.RequestedUnitPrice != price)
                    await UpdateUnitPriceWithLogAsync(requestQuoteItem, price);

                break;
        }
    }

    private async Task<string> GetCurrentCustomerEmailAsync()
    {
        var customer = await _workContext.GetCurrentCustomerAsync();

        return customer.Email;
    }

    private async Task<Quote> InsertQuoteAsync(Quote quote)
    {
        LogNote(quote, string.Format(await _localizationService.GetResourceAsync(quote.RequestQuoteId.HasValue ? "Plugins.Misc.RFQ.AdminQuote.QuoteCreatedByRequest" : "Plugins.Misc.RFQ.AdminQuote.QuoteCreatedManuallyByStoreOwner"), await GetCurrentCustomerEmailAsync()));
        await _quoteRepository.InsertAsync(quote);

        return quote;
    }

    private async Task UpdateRequestQuoteItemAsync(RequestQuoteItem requestQuoteItem)
    {
        await _requestQuoteItemRepository.UpdateAsync(requestQuoteItem);
    }

    private async Task UpdateQuoteItemAsync(QuoteItem quoteItem)
    {
        await _quoteItemRepository.UpdateAsync(quoteItem);
    }

    private async Task InsertQuoteItemsAsync(IList<QuoteItem> quoteItems)
    {
        if (!quoteItems.Any())
            return;

        await _quoteItemRepository.InsertAsync(quoteItems);
    }

    private async Task<Quote> CheckIsQuoteExpiredAsync(Quote quote)
    {
        if (quote == null)
            return null;

        if (!quote.ExpirationDateUtc.HasValue)
            return quote;

        if (quote.Status is QuoteStatus.Expired or QuoteStatus.OrderCreated)
            return quote;

        if (quote.ExpirationDateUtc.Value > DateTime.UtcNow)
            return quote;

        quote.Status = QuoteStatus.Expired;
        var note = await _localizationService.GetResourceAsync("Plugins.Misc.RFQ.QuoteExpired");

        LogNote(quote, note);
        await UpdateQuoteAsync(quote);

        return quote;
    }

    #endregion

    #region Methods

    #region Request a quote

    /// <summary>
    /// Changes the request quote status and add the note to the admin note field
    /// </summary>
    /// <param name="requestQuote">Request quote to change status</param>
    /// <param name="newStatus">New status to change</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public async Task ChangeAndLogRequestQuoteStatusAsync(RequestQuote requestQuote, RequestQuoteStatus newStatus)
    {
        if (requestQuote.Status == newStatus)
            return;

        var note = string.Format(
            await _localizationService.GetResourceAsync("Plugins.Misc.RFQ.RequestQuoteStatusChanged"),
            await _localizationService.GetLocalizedEnumAsync(requestQuote.Status), await _localizationService.GetLocalizedEnumAsync(newStatus),
            await GetCurrentCustomerEmailAsync());

        LogNote(requestQuote, note);
        requestQuote.Status = newStatus;
    }

    /// <summary>
    /// Creates request a quote by shopping cart items
    /// </summary>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the request a quote and its items
    /// </returns>
    public async Task<(RequestQuote requestQuote, List<RequestQuoteItem> requestQuoteItem)> CreateRequestQuoteByShoppingCartAsync()
    {
        var customer = await _workContext.GetCurrentCustomerAsync();
        var store = await _storeContext.GetCurrentStoreAsync();
        var cart = await _shoppingCartService.GetShoppingCartAsync(customer, ShoppingCartType.ShoppingCart, store.Id);

        if (!cart.Any())
            return (null, null);

        var requestQuote = new RequestQuote
        {
            CreatedOnUtc = DateTime.UtcNow,
            CustomerId = customer.Id
        };

        var items = await cart.SelectAwait(async item =>
        {
            var (shoppingCartUnitPriceWithDiscountBase, _) = await _taxService.GetProductPriceAsync(await _productService.GetProductByIdAsync(item.ProductId),
                (await _shoppingCartService.GetUnitPriceAsync(item, true)).unitPrice);

            var requestQuoteItem = new RequestQuoteItem
            {
                Id = item.Id,
                ProductAttributesXml = item.AttributesXml,
                OriginalProductPrice = shoppingCartUnitPriceWithDiscountBase,
                ProductId = item.ProductId,
                RequestedQty = item.Quantity,
                RequestedUnitPrice = shoppingCartUnitPriceWithDiscountBase,
                RequestQuoteId = requestQuote.Id
            };

            return requestQuoteItem;
        }).ToListAsync();

        return (requestQuote, items);
    }

    /// <summary>
    /// Gets the request quote item by identifier
    /// </summary>
    /// <param name="requestId">Request quote identifier</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the request quote item
    /// </returns>
    public async Task<RequestQuote> GetRequestQuoteByIdAsync(int requestId)
    {
        return await _requestQuoteRepository.GetByIdAsync(requestId);
    }

    /// <summary>
    /// Update request quote
    /// </summary>
    /// <param name="requestQuote">Request a quote</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public async Task UpdateRequestQuoteAsync(RequestQuote requestQuote)
    {
        await _requestQuoteRepository.UpdateAsync(requestQuote);
    }

    /// <summary>
    /// Send new request a quote
    /// </summary>
    /// <param name="customerNotes">Customer notes</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the request a quote identifier
    /// </returns>
    public async Task<int> SendNewRequestAsync(string customerNotes)
    {
        var (request, items) = await CreateRequestQuoteByShoppingCartAsync();

        LogNote(request, string.Format(await _localizationService.GetResourceAsync("Plugins.Misc.RFQ.RequestCreated"), await GetCurrentCustomerEmailAsync()));
        request.Status = RequestQuoteStatus.Submitted;
        await _requestQuoteRepository.InsertAsync(request);

        var httpRequest = _httpContextAccessor.HttpContext?.Request;

        if (httpRequest == null)
            return 0;

        var needSave = false;

        if (!string.IsNullOrEmpty(customerNotes) && (!request.CustomerNotes?.Equals(customerNotes) ?? true))
        {
            needSave = true;
            await UpdateCustomerNotesWithLogAsync(request, customerNotes);
        }

        if (needSave)
            await UpdateRequestQuoteAsync(request);

        items ??= await GetRequestQuoteItemsAsync(request.Id);

        if (items.Any() && httpRequest.IsPostRequest() && httpRequest.HasFormContentType)
        {
            var form = await httpRequest.ReadFormAsync();

            foreach (var requestQuoteItem in items)
            {
                await SetIfExistsAsync(requestQuoteItem, form, RfqDefaults.QUANTITY_FORM_KEY);
                await SetIfExistsAsync(requestQuoteItem, form, RfqDefaults.UNIT_PRICE_FORM_KEY);

                requestQuoteItem.Id = 0;
                requestQuoteItem.RequestQuoteId = request.Id;

                await InsertRequestQuoteItemAsync(requestQuoteItem);
            }
        }

        await _rfqMessageService.CustomerSentNewRequestQuoteAsync(request);

        return request.Id;
    }

    /// <summary>
    /// Gets customer requests a quote
    /// </summary>
    /// <param name="customerId">Customer identifier</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the request a quote item
    /// </returns>
    public async Task<List<RequestQuote>> GetCustomerRequestsAsync(int customerId)
    {
        return await _requestQuoteRepository.Table.Where(p => p.CustomerId == customerId).OrderByDescending(p => p.Id)
            .ToListAsync();
    }

    /// <summary>
    /// Gets customer quotes
    /// </summary>
    /// <param name="customerId">Customer identifier</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the request a quote item
    /// </returns>
    public async Task<List<Quote>> GetCustomerQuotesAsync(int customerId)
    {
        var statuses = new[] { (int)QuoteStatus.Submitted, (int)QuoteStatus.OrderCreated };

        return await _quoteRepository.Table.Where(p => p.CustomerId == customerId && statuses.Contains(p.StatusId))
            .OrderByDescending(p => p.Id).ToListAsync();
    }

    /// <summary>
    /// Search requests a quote
    /// </summary>
    /// <param name="requestQuoteStatusId">Request a quote status identifier</param>
    /// <param name="createdOnFrom">Created on from</param>
    /// <param name="createdOnTo">Created on to</param>
    /// <param name="customerEmail">The customer email</param>
    /// <param name="pageIndex">Page index</param>
    /// <param name="pageSize">Page size</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the request a quote item
    /// </returns>
    public async Task<IPagedList<RequestQuote>> SearchRequestsQuoteAsync(int requestQuoteStatusId, DateTime? createdOnFrom, DateTime? createdOnTo, string customerEmail, int pageIndex = 0, int pageSize = int.MaxValue)
    {
        var requests = await _requestQuoteRepository.GetAllPagedAsync(query =>
        {
            if (createdOnFrom.HasValue)
                query = query.Where(l => createdOnFrom.Value <= l.CreatedOnUtc);

            if (createdOnTo.HasValue)
                query = query.Where(l => createdOnTo.Value >= l.CreatedOnUtc);

            if (requestQuoteStatusId > 0)
                query = query.Where(l => requestQuoteStatusId == l.StatusId);

            if (!string.IsNullOrEmpty(customerEmail))
            {
                query = from rq in query
                        join customer in _customerRepository.Table on rq.CustomerId equals customer.Id
                        where customer.Email.Contains(customerEmail)
                        select rq;
            }

            query = query.OrderByDescending(l => l.CreatedOnUtc);

            return query;
        }, pageIndex, pageSize);

        return requests;
    }

    /// <summary>
    /// Deletes the request quote
    /// </summary>
    /// <param name="requestQuoteId">Request quote identifier</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public async Task DeleteRequestQuoteAsync(int requestQuoteId)
    {
        var requestQuote = await GetRequestQuoteByIdAsync(requestQuoteId);

        await DeleteRequestQuoteAsync(requestQuote);
    }

    /// <summary>
    /// Deletes the request quote
    /// </summary>
    /// <param name="requestQuote">Request a quote</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public async Task DeleteRequestQuoteAsync(RequestQuote requestQuote)
    {
        if (requestQuote == null)
            return;

        if (requestQuote.QuoteId.HasValue)
        {
            var quote = await GetQuoteByIdAsync(requestQuote.QuoteId.Value);
            quote.RequestQuoteId = null;
            await UpdateQuoteAsync(quote);
        }

        await _requestQuoteRepository.DeleteAsync(requestQuote);
    }

    /// <summary>
    /// Deletes the requests a quote by identifiers
    /// </summary>
    /// <param name="ids">Identifiers of request a quote to delete</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public async Task DeleteRequestsQuoteByIdsAsync(ICollection<int> ids)
    {
        await _requestQuoteRepository.DeleteAsync(await _requestQuoteRepository.GetByIdsAsync(ids.ToArray()));
    }

    #endregion

    #region Request quote item

    /// <summary>
    /// Get request quote items
    /// </summary>
    /// <param name="requestId">Request quote identifier to load items</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the request a quote items
    /// </returns>
    public async Task<List<RequestQuoteItem>> GetRequestQuoteItemsAsync(int requestId)
    {
        return await _requestQuoteItemRepository.Table
            .Where(p => p.RequestQuoteId == requestId)
            .ToListAsync();
    }

    /// <summary>
    /// Gets request a quote item
    /// </summary>
    /// <param name="requestQuoteItemId">Request an quote item identifier</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the request a quote item
    /// </returns>
    public async Task<RequestQuoteItem> GetRequestQuoteItemByIdAsync(int requestQuoteItemId)
    {
        return await _requestQuoteItemRepository.GetByIdAsync(requestQuoteItemId, _ => default);
    }

    /// <summary>
    /// Deletes request a quote item
    /// </summary>
    /// <param name="requestQuoteItemId">Request a quote item identifier</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public async Task DeleteRequestQuoteItemAsync(int requestQuoteItemId)
    {
        var item = await GetRequestQuoteItemByIdAsync(requestQuoteItemId);

        if (item == null)
            return;

        await _requestQuoteItemRepository.DeleteAsync(item);

        var requestQuote = await GetRequestQuoteByIdAsync(item.RequestQuoteId);
        var note = string.Format(await _localizationService.GetResourceAsync("Plugins.Misc.RFQ.RequestQuoteItemDeleted"), requestQuoteItemId, await GetCurrentCustomerEmailAsync());

        LogNote(requestQuote, note);
        await UpdateRequestQuoteAsync(requestQuote);
    }

    /// <summary>
    /// Updates request a quote item
    /// </summary>
    /// <param name="requestQuoteItemId">Request a quote item identifier</param>
    /// <param name="quantity">Quantity</param>
    /// <param name="unitPrice">Unit price</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public async Task UpdateRequestQuoteItemAsync(int requestQuoteItemId, int quantity, decimal unitPrice)
    {
        var item = await GetRequestQuoteItemByIdAsync(requestQuoteItemId);

        if (item == null)
            return;

        if (item.RequestedQty != quantity)
            await UpdateQuantityWithLogAsync(item, quantity);

        if (item.RequestedUnitPrice != unitPrice)
            await UpdateUnitPriceWithLogAsync(item, unitPrice);

        await UpdateRequestQuoteItemAsync(item);
    }

    /// <summary>
    /// Inserts request a quote item
    /// </summary>
    /// <param name="requestQuoteItem">Request a quote item</param>
    /// <returns>A task that represents the asynchronous operation
    /// The task result contains the request a quote item</returns>
    public async Task<RequestQuoteItem> InsertRequestQuoteItemAsync(RequestQuoteItem requestQuoteItem)
    {
        LogNote(requestQuoteItem, string.Format(await _localizationService.GetResourceAsync("Plugins.Misc.RFQ.RequestItemCreated"), await GetCurrentCustomerEmailAsync()));

        await _requestQuoteItemRepository.InsertAsync(requestQuoteItem);

        return requestQuoteItem;
    }

    #endregion

    #region Quote

    /// <summary>
    /// Creates the empty quote for specified customer
    /// </summary>
    /// <param name="customerId">The customer identifier</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the new quote 
    /// </returns>
    public async Task<Quote> CreateQuoteAsync(int customerId)
    {
        if (customerId <= 0)
            return null;

        var requestQuote = new Quote
        {
            Status = QuoteStatus.CreatedManuallyByStoreOwner,
            CreatedOnUtc = DateTime.UtcNow,
            CustomerId = customerId
        };

        return await InsertQuoteAsync(requestQuote);
    }

    /// <summary>
    /// Deletes the quotes by identifiers
    /// </summary>
    /// <param name="ids">Identifiers of the quote to delete</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public async Task DeleteQuotesByIdsAsync(ICollection<int> ids)
    {
        await _quoteRepository.DeleteAsync(await _quoteRepository.GetByIdsAsync(ids.ToArray()));
    }

    /// <summary>
    /// Search the quotes
    /// </summary>
    /// <param name="quoteStatusId">Request a quote status identifier</param>
    /// <param name="createdOnFrom">Created on from</param>
    /// <param name="createdOnTo">Created on to</param>
    /// <param name="customerEmail">The customer email</param>
    /// <param name="pageIndex">Page index</param>
    /// <param name="pageSize">Page size</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the request a quote item
    /// </returns>
    public async Task<IPagedList<Quote>> SearchQuotesAsync(int quoteStatusId, DateTime? createdOnFrom, DateTime? createdOnTo, string customerEmail, int pageIndex = 0, int pageSize = int.MaxValue)
    {
        var quotes = await _quoteRepository.GetAllPagedAsync(query =>
        {
            if (createdOnFrom.HasValue)
                query = query.Where(l => createdOnFrom.Value <= l.CreatedOnUtc);

            if (createdOnTo.HasValue)
                query = query.Where(l => createdOnTo.Value >= l.CreatedOnUtc);

            if (quoteStatusId > 0)
                query = query.Where(l => quoteStatusId == l.StatusId);

            if (!string.IsNullOrEmpty(customerEmail))
            {
                query = from rq in query
                        join customer in _customerRepository.Table on rq.CustomerId equals customer.Id
                        where customer.Email.Contains(customerEmail)
                        select rq;
            }
            query = query.OrderByDescending(l => l.CreatedOnUtc);

            return query;
        }, pageIndex, pageSize);

        foreach (var quote in quotes)
            await CheckIsQuoteExpiredAsync(quote);

        return quotes;
    }

    /// <summary>
    /// Creates the quote by request a quote
    /// </summary>
    /// <param name="requestQuoteId">Request a quote identifier</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the new quote identifier
    /// </returns>
    public async Task<int> CreateQuoteByRequestAsync(int requestQuoteId)
    {
        var requestQuote = await GetRequestQuoteByIdAsync(requestQuoteId);

        if (requestQuote == null)
            return 0;

        var quote = new Quote
        {
            CreatedOnUtc = DateTime.UtcNow,
            AdminNotes = string.Empty,
            RequestQuoteId = requestQuoteId,
            Status = QuoteStatus.CreatedFromRequestQuote,
            CustomerId = requestQuote.CustomerId,
        };

        await InsertQuoteAsync(quote);

        var items = await GetRequestQuoteItemsAsync(requestQuoteId);

        await InsertQuoteItemsAsync(items.Select(item => new QuoteItem
        {
            AttributesXml = item.ProductAttributesXml,
            ProductId = item.ProductId,
            OfferedQty = item.RequestedQty,
            QuoteId = quote.Id,
            RequestedQty = item.RequestedQty,
            RequestedUnitPrice = item.RequestedUnitPrice,
            OfferedUnitPrice = item.RequestedUnitPrice,
            RequestQuoteId = item.Id
        }).ToList());

        await ChangeAndLogRequestQuoteStatusAsync(requestQuote, RequestQuoteStatus.QuoteIsCreated);
        requestQuote.QuoteId = quote.Id;
        await UpdateRequestQuoteAsync(requestQuote);

        return quote.Id;
    }

    /// <summary>
    /// Gets the quote item by identifier
    /// </summary>
    /// <param name="quoteId">The quote identifier</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the quote item
    /// </returns>
    public async Task<Quote> GetQuoteByIdAsync(int quoteId)
    {
        return await CheckIsQuoteExpiredAsync(await _quoteRepository.GetByIdAsync(quoteId));
    }

    /// <summary>
    /// Changes the quote status and add the note to the admin note field
    /// </summary>
    /// <param name="quote">Quote to change status</param>
    /// <param name="newStatus">New status to change</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public async Task ChangeAndLogQuoteStatusAsync(Quote quote, QuoteStatus newStatus)
    {
        if (quote.Status == newStatus)
            return;

        var note = string.Format(await _localizationService.GetResourceAsync("Plugins.Misc.RFQ.QuoteStatusChanged"),
            await _localizationService.GetLocalizedEnumAsync(quote.Status), await _localizationService.GetLocalizedEnumAsync(newStatus),
            await GetCurrentCustomerEmailAsync());

        LogNote(quote, note);
        quote.Status = newStatus;
    }

    /// <summary>
    /// Update quote
    /// </summary>
    /// <param name="quote">The quote</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public async Task UpdateQuoteAsync(Quote quote)
    {
        await _quoteRepository.UpdateAsync(quote);
    }

    /// <summary>
    /// Deletes the quote
    /// </summary>
    /// <param name="quoteId">Quote identifier</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public async Task DeleteQuoteAsync(int quoteId)
    {
        var quote = await GetQuoteByIdAsync(quoteId);

        if (quote == null)
            return;

        await _quoteRepository.DeleteAsync(quote);

        if (quote.RequestQuoteId is null or 0)
            return;

        var requestQuote = await _requestQuoteRepository.GetByIdAsync(quote.RequestQuoteId);

        if (requestQuote == null)
            return;

        requestQuote.QuoteId = null;
        requestQuote.Status = RequestQuoteStatus.Submitted;

        await UpdateRequestQuoteAsync(requestQuote);
    }

    /// <summary>
    /// Creates shopping cart items by quote
    /// </summary>
    /// <param name="quoteId">Quote identifier</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public async Task CreateShoppingCartAsync(int quoteId)
    {
        var customer = await _workContext.GetCurrentCustomerAsync();

        if (!await _permissionService.AuthorizeAsync(StandardPermission.PublicStore.ENABLE_SHOPPING_CART, customer))
            throw new NopException("Shopping cart is disabled");

        var store = await _storeContext.GetCurrentStoreAsync();
        var quoteItems = await GetQuoteItemsAsync(quoteId);

        await _shoppingCartService.ClearShoppingCartAsync(customer, store.Id);

        //reset checkout info
        await _customerService.ResetCheckoutDataAsync(customer, store.Id);

        foreach (var quoteItem in quoteItems)
        {
            var now = DateTime.UtcNow;
            var shoppingCartItem = new ShoppingCartItem
            {
                ShoppingCartType = ShoppingCartType.ShoppingCart,
                StoreId = store.Id,
                ProductId = quoteItem.ProductId,
                AttributesXml = quoteItem.AttributesXml,
                CustomerEnteredPrice = decimal.Zero,
                Quantity = quoteItem.OfferedQty,
                RentalStartDateUtc = null,
                RentalEndDateUtc = null,
                CreatedOnUtc = now,
                UpdatedOnUtc = now,
                CustomerId = customer.Id
            };

            await _sciRepository.InsertAsync(shoppingCartItem, false);
            quoteItem.ShoppingCartItemId = shoppingCartItem.Id;
        }

        await UpdateQuoteItemsAsync(quoteItems);

        //updated "HasShoppingCartItems" property used for performance optimization
        if (!customer.HasShoppingCartItems)
        {
            customer.HasShoppingCartItems = true;
            await _customerService.UpdateCustomerAsync(customer);
        }
    }

    #endregion

    #region Quote item

    /// <summary>
    /// Inserts the quote item
    /// </summary>
    /// <param name="quoteItem">Quote item</param>
    /// <returns>A task that represents the asynchronous operation
    /// The task result contains the quote item</returns>
    public async Task<QuoteItem> InsertQuoteItemAsync(QuoteItem quoteItem)
    {
        await _quoteItemRepository.InsertAsync(quoteItem);

        var quote = await GetQuoteByIdAsync(quoteItem.QuoteId);
        var note = string.Format(await _localizationService.GetResourceAsync("Plugins.Misc.RFQ.QuoteItemAdded"),
            quoteItem.Id, await GetCurrentCustomerEmailAsync());

        LogNote(quote, note);
        await UpdateQuoteAsync(quote);

        return quoteItem;
    }

    /// <summary>
    /// Get quote items
    /// </summary>
    /// <param name="quoteId">Quote identifier to load items</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the quote items
    /// </returns>
    public async Task<List<QuoteItem>> GetQuoteItemsAsync(int quoteId)
    {
        return await _quoteItemRepository.Table
            .Where(p => p.QuoteId == quoteId)
            .ToListAsync();
    }

    /// <summary>
    /// Gets the quote item
    /// </summary>
    /// <param name="quoteItemId">Quote item identifier</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the request a quote item
    /// </returns>
    public async Task<QuoteItem> GetQuoteItemByIdAsync(int quoteItemId)
    {
        return await _quoteItemRepository.GetByIdAsync(quoteItemId, _ => default);
    }

    /// <summary>
    /// Deletes the quote item
    /// </summary>
    /// <param name="quoteItemId">Quote item identifier</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public async Task DeleteQuoteItemAsync(int quoteItemId)
    {
        var item = await GetQuoteItemByIdAsync(quoteItemId);

        if (item == null)
            return;

        await _quoteItemRepository.DeleteAsync(item);

        var quote = await GetQuoteByIdAsync(item.QuoteId);
        var note = string.Format(await _localizationService.GetResourceAsync("Plugins.Misc.RFQ.QuoteItemDeleted"), quoteItemId, await GetCurrentCustomerEmailAsync());

        LogNote(quote, note);
        await UpdateQuoteAsync(quote);
    }

    /// <summary>
    /// Updates a quote item
    /// </summary>
    /// <param name="quoteItemId">A quote item identifier</param>
    /// <param name="quantity">Quantity</param>
    /// <param name="unitPrice">Unit price</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public async Task UpdateQuoteItemAsync(int quoteItemId, int quantity, decimal unitPrice)
    {
        var item = await GetQuoteItemByIdAsync(quoteItemId);

        if (item == null)
            return;

        if (item.OfferedQty == quantity && item.OfferedUnitPrice == unitPrice)
            return;

        item.OfferedQty = quantity;
        item.OfferedUnitPrice = unitPrice;

        await UpdateQuoteItemAsync(item);
    }

    /// <summary>
    /// Gets the quote item by shopping cart item identifier
    /// </summary>
    /// <param name="shoppingCartItemId">Shopping cart item identifier</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the quote item
    /// </returns>
    public async Task<QuoteItem> GetQuoteItemByShoppingCartItemIdAsync(int shoppingCartItemId)
    {
        return await _quoteItemRepository.Table.FirstOrDefaultAsync(qi => qi.ShoppingCartItemId == shoppingCartItemId);
    }

    /// <summary>
    /// Gets the quote items by shopping cart item identifiers
    /// </summary>
    /// <param name="shoppingCartItemIds">Shopping cart item identifiers</param>
    /// <returns>
    /// A task that represents the asynchronous operation
    /// The task result contains the list of quote item
    /// </returns>
    public async Task<List<QuoteItem>> GetQuoteItemsByShoppingCartItemIdsAsync(IList<int> shoppingCartItemIds)
    {
        return await _quoteItemRepository.Table.Where(qi => qi.ShoppingCartItemId.HasValue && shoppingCartItemIds.Contains(qi.ShoppingCartItemId.Value)).ToListAsync();
    }

    /// <summary>
    /// Updates request a quote items
    /// </summary>
    /// <param name="quoteItems">Request a quote items for update</param>
    /// <returns>A task that represents the asynchronous operation</returns>
    public async Task UpdateQuoteItemsAsync(List<QuoteItem> quoteItems)
    {
        await _quoteItemRepository.UpdateAsync(quoteItems);
    }

    #endregion

    #endregion
}