If you are able to code,it's simple to implement this.
My thought is to overwrite GetFinalPrice in PriceCalculationService in a plugin project.To check if current product has parent product id or not, if it has,then change the quantity to the total of all variants.
As this method has been called after all items have been saved to cart,so it's no problem.
Hope you are able to code,this is something I worked out for you.
Service file like this:
using Nop.Core;
using Nop.Core.Caching;
using Nop.Core.Domain.Catalog;
using Nop.Core.Domain.Customers;
using Nop.Core.Domain.Orders;
using Nop.Services.Catalog;
using Nop.Services.Catalog.Cache;
using Nop.Services.Customers;
using Nop.Services.Discounts;
using Nop.Services.Orders;
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace Nop.Plugin.Product.GroupedRules.Services
{
public partial class GroupedProductPriceCalculationService : PriceCalculationService
{
private readonly IStoreContext _storeContext;
private readonly CatalogSettings _catalogSettings;
private readonly IStaticCacheManager _cacheManager;
private readonly IWorkContext _workContext;
public GroupedProductPriceCalculationService(IWorkContext workContext, IStoreContext storeContext, IDiscountService discountService, ICategoryService categoryService, IManufacturerService manufacturerService, IProductAttributeParser productAttributeParser, IProductService productService, IStaticCacheManager cacheManager, ShoppingCartSettings shoppingCartSettings, CatalogSettings catalogSettings) : base(workContext, storeContext, discountService, categoryService, manufacturerService, productAttributeParser, productService, cacheManager, shoppingCartSettings, catalogSettings)
{
this._storeContext = storeContext;
this._catalogSettings = catalogSettings;
this._cacheManager = cacheManager;
this._workContext = workContext;
}
/// <summary>
/// Gets the final price
/// </summary>
/// <param name="product">Product</param>
/// <param name="customer">The customer</param>
/// <param name="overriddenProductPrice">Overridden product price. If specified, then it'll be used instead of a product price. For example, used with product attribute combinations</param>
/// <param name="additionalCharge">Additional charge</param>
/// <param name="includeDiscounts">A value indicating whether include discounts or not for final price computation</param>
/// <param name="quantity">Shopping cart item quantity</param>
/// <param name="rentalStartDate">Rental period start date (for rental products)</param>
/// <param name="rentalEndDate">Rental period end date (for rental products)</param>
/// <param name="discountAmount">Applied discount amount</param>
/// <param name="appliedDiscounts">Applied discounts</param>
/// <returns>Final price</returns>
public override decimal GetFinalPrice(Nop.Core.Domain.Catalog.Product product,
Customer customer,
decimal? overriddenProductPrice,
decimal additionalCharge,
bool includeDiscounts,
int quantity,
DateTime? rentalStartDate,
DateTime? rentalEndDate,
out decimal discountAmount,
out List<DiscountForCaching> appliedDiscounts)
{
if (product == null)
throw new ArgumentNullException(nameof(product));
discountAmount = decimal.Zero;
appliedDiscounts = new List<DiscountForCaching>();
var cacheKey = string.Format(PriceCacheEventConsumer.PRODUCT_PRICE_MODEL_KEY,
product.Id,
overriddenProductPrice.HasValue ? overriddenProductPrice.Value.ToString(CultureInfo.InvariantCulture) : null,
additionalCharge.ToString(CultureInfo.InvariantCulture),
includeDiscounts,
quantity,
string.Join(",", customer.GetCustomerRoleIds()),
_storeContext.CurrentStore.Id);
var cacheTime = _catalogSettings.CacheProductPrices ? 60 : 0;
//we do not cache price for rental products
//otherwise, it can cause memory leaks (to store all possible date period combinations)
if (product.IsRental)
cacheTime = 0;
//this only create unitprice,as subtotal also call this method to generate amount.
if (product.ParentGroupedProductId > 0)
{
quantity = 0;
var cart = _workContext.CurrentCustomer.ShoppingCartItems
.Where(sci => sci.ShoppingCartType == ShoppingCartType.ShoppingCart)
.LimitPerStore(_storeContext.CurrentStore.Id)
.ToList();
var sampParentProductCart = cart.Where(c => c.Product.ParentGroupedProductId == product.ParentGroupedProductId).ToList();
foreach(var cartSameParent in sampParentProductCart)
{
quantity += cartSameParent.Quantity;
}
}
var cachedPrice = _cacheManager.Get(cacheKey, cacheTime, () =>
{
var result = new ProductPriceForCaching();
//initial price
var price = overriddenProductPrice.HasValue ? overriddenProductPrice.Value : product.Price;
//tier prices
var tierPrice = product.GetPreferredTierPrice(customer, _storeContext.CurrentStore.Id, quantity);
if (tierPrice != null)
price = tierPrice.Price;
//additional charge
price = price + additionalCharge;
//rental products
if (product.IsRental)
if (rentalStartDate.HasValue && rentalEndDate.HasValue)
price = price * product.GetRentalPeriods(rentalStartDate.Value, rentalEndDate.Value);
if (includeDiscounts)
{
//discount
var tmpDiscountAmount = GetDiscountAmount(product, customer, price, out List<DiscountForCaching> tmpAppliedDiscounts);
price = price - tmpDiscountAmount;
if (tmpAppliedDiscounts != null)
{
result.AppliedDiscounts = tmpAppliedDiscounts;
result.AppliedDiscountAmount = tmpDiscountAmount;
}
}
if (price < decimal.Zero)
price = decimal.Zero;
result.Price = price;
return result;
});
if (includeDiscounts)
{
if (cachedPrice.AppliedDiscounts.Any())
{
appliedDiscounts.AddRange(cachedPrice.AppliedDiscounts);
discountAmount = cachedPrice.AppliedDiscountAmount;
}
}
return cachedPrice.Price;
}
}
}
Then set DependencyRegistrar:
using Autofac;
using Nop.Core.Configuration;
using Nop.Core.Infrastructure;
using Nop.Core.Infrastructure.DependencyManagement;
using Nop.Plugin.Product.GroupedRules.Services;
using Nop.Services.Catalog;
namespace Nop.Plugin.Product.GroupedRules.Infrastructure
{
public class DependencyRegistrar : IDependencyRegistrar
{
public int Order
{
get { return int.MaxValue; }
}
public void Register(ContainerBuilder builder, ITypeFinder typeFinder, NopConfig config)
{
builder.RegisterType<GroupedProductPriceCalculationService>().As<IPriceCalculationService>().InstancePerLifetimeScope();
//throw new NotImplementedException();
}
}
}