Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
using Grand.Business.Core.Interfaces.Catalog.Prices;
using Grand.Business.Core.Interfaces.Catalog.Products;
using Grand.Business.Core.Interfaces.Common.Directory;
using Grand.Business.Core.Queries.Catalog;
using Grand.Mediator;

namespace Grand.Business.Catalog.Queries.Handlers;

public class GetShoppingCartItemsSubTotalQueryHandler : IRequestHandler<GetShoppingCartItemsSubTotalQuery, double>
{
private readonly ICurrencyService _currencyService;
private readonly IPricingService _pricingService;
private readonly IProductService _productService;

public GetShoppingCartItemsSubTotalQueryHandler(
IPricingService pricingService,
IProductService productService,
ICurrencyService currencyService)
{
_pricingService = pricingService;
_productService = productService;
_currencyService = currencyService;
}

public async Task<double> Handle(GetShoppingCartItemsSubTotalQuery request, CancellationToken cancellationToken)
{
ArgumentNullException.ThrowIfNull(request.Customer);
ArgumentNullException.ThrowIfNull(request.Store);

//priced for the customer of the request, not the ambient one, in the currency the requirement is entered in
var currency = await _currencyService.GetPrimaryStoreCurrency();

double subTotal = 0;
foreach (var item in request.Items)
{
var product = await _productService.GetProductById(item.ProductId);
if (product == null)
continue;

//without discounts the subtotal is the unit price times the quantity, as in IPricingService.GetSubTotal
var unitPrice = await _pricingService.GetUnitPrice(product, request.Customer, request.Store, currency,
item.ShoppingCartTypeId, item.Quantity, item.Attributes, item.EnteredPrice,
item.RentalStartDateUtc, item.RentalEndDateUtc, false);
subTotal += unitPrice.unitprice * item.Quantity;
}

return subTotal;
}
}
Original file line number Diff line number Diff line change
@@ -1,9 +1,6 @@
using Grand.Business.Core.Extensions;
using Grand.Business.Core.Interfaces.Catalog.Discounts;
using Grand.Business.Core.Interfaces.Catalog.Prices;
using Grand.Business.Core.Interfaces.Catalog.Products;
using Grand.Business.Core.Interfaces.Catalog.Tax;
using Grand.Business.Core.Interfaces.Checkout.CheckoutAttributes;
using Grand.Business.Core.Interfaces.Checkout.GiftVouchers;
using Grand.Business.Core.Interfaces.Checkout.Orders;
using Grand.Business.Core.Interfaces.Checkout.Payments;
Expand Down Expand Up @@ -36,15 +33,13 @@ public class OrderCalculationService : IOrderCalculationService
/// Ctor
/// </summary>
/// <param name="workContext">Work context</param>
/// <param name="priceCalculationService">Price calculation service</param>
/// <param name="shoppingCartSubTotalService">Shopping cart subtotal service</param>
/// <param name="taxService">Tax service</param>
/// <param name="shippingService">Shipping service</param>
/// <param name="paymentService">Payment service</param>
/// <param name="checkoutAttributeParser">Checkout attribute parser</param>
/// <param name="discountService">Discount service</param>
/// <param name="giftVoucherService">Gift voucher service</param>
/// <param name="loyaltyPointsService">Loyalty points service</param>
/// <param name="productService">Product service</param>
/// <param name="currencyService">Currency service</param>
/// <param name="groupService">Group</param>
/// <param name="discountValidationService">Discount validation service</param>
Expand All @@ -54,15 +49,13 @@ public class OrderCalculationService : IOrderCalculationService
/// <param name="shoppingCartSettings">Shopping cart settings</param>
/// <param name="catalogSettings">Catalog settings</param>
public OrderCalculationService(IContextAccessor contextAccessor,
IPricingService priceCalculationService,
IShoppingCartSubTotalService shoppingCartSubTotalService,
ITaxService taxService,
IShippingService shippingService,
IPaymentService paymentService,
ICheckoutAttributeParser checkoutAttributeParser,
IDiscountService discountService,
IGiftVoucherService giftVoucherService,
ILoyaltyPointsService loyaltyPointsService,
IProductService productService,
ICurrencyService currencyService,
IGroupService groupService,
IDiscountValidationService discountValidationService,
Expand All @@ -73,15 +66,13 @@ public OrderCalculationService(IContextAccessor contextAccessor,
CatalogSettings catalogSettings)
{
_contextAccessor = contextAccessor;
_pricingService = priceCalculationService;
_shoppingCartSubTotalService = shoppingCartSubTotalService;
_taxService = taxService;
_shippingService = shippingService;
_paymentService = paymentService;
_checkoutAttributeParser = checkoutAttributeParser;
_discountService = discountService;
_giftVoucherService = giftVoucherService;
_loyaltyPointsService = loyaltyPointsService;
_productService = productService;
_currencyService = currencyService;
_groupService = groupService;
_discountValidationService = discountValidationService;
Expand All @@ -97,15 +88,13 @@ public OrderCalculationService(IContextAccessor contextAccessor,
#region Fields

private readonly IContextAccessor _contextAccessor;
private readonly IPricingService _pricingService;
private readonly IShoppingCartSubTotalService _shoppingCartSubTotalService;
private readonly ITaxService _taxService;
private readonly IShippingService _shippingService;
private readonly IPaymentService _paymentService;
private readonly ICheckoutAttributeParser _checkoutAttributeParser;
private readonly IDiscountService _discountService;
private readonly IGiftVoucherService _giftVoucherService;
private readonly ILoyaltyPointsService _loyaltyPointsService;
private readonly IProductService _productService;
private readonly ICurrencyService _currencyService;
private readonly IGroupService _groupService;
private readonly IDiscountValidationService _discountValidationService;
Expand All @@ -119,52 +108,6 @@ public OrderCalculationService(IContextAccessor contextAccessor,

#region Utilities

/// <summary>
/// Gets an order discount (applied to order subtotal)
/// </summary>
/// <param name="customer">Customer</param>
/// <param name="store">Store</param>
/// <param name="currency">Currency</param>
/// <param name="orderSubTotal">Order subtotal</param>
/// <returns>Order discount</returns>
protected virtual async Task<(double ordersubtotaldiscount, List<ApplyDiscount> appliedDiscounts)>
GetOrderSubtotalDiscount(Customer customer, Store store, Currency currency, double orderSubTotal)
{
var appliedDiscounts = new List<ApplyDiscount>();
double discountAmount = 0;
if (_catalogSettings.IgnoreDiscounts)
return (discountAmount, appliedDiscounts);

var allDiscounts = await _discountService.GetActiveDiscountsByContext(DiscountType.AssignedToOrderSubTotal,
_contextAccessor.StoreContext.CurrentStore.Id, currency.CurrencyCode);
var allowedDiscounts = new List<ApplyDiscount>();
if (allDiscounts != null)
foreach (var discount in allDiscounts)
{
var validDiscount =
await _discountValidationService.ValidateDiscount(discount, customer, store, currency);
if (validDiscount.IsValid &&
discount.DiscountTypeId == DiscountType.AssignedToOrderSubTotal &&
allowedDiscounts.All(x => x.DiscountId != discount.Id))
allowedDiscounts.Add(new ApplyDiscount {
DiscountId = discount.Id,
IsCumulative = discount.IsCumulative,
CouponCode = validDiscount.CouponCode,
MaximumDiscountedQuantity = discount.MaximumDiscountedQuantity
});
}

var preferredDiscounts = await _discountService.GetPreferredDiscount(allowedDiscounts, customer,
_contextAccessor.WorkContext.WorkingCurrency, orderSubTotal);
appliedDiscounts = preferredDiscounts.appliedDiscount;
discountAmount = preferredDiscounts.discountAmount;

if (discountAmount < 0)
discountAmount = 0;

return (discountAmount, appliedDiscounts);
}

/// <summary>
/// Gets a shipping discount
/// </summary>
Expand Down Expand Up @@ -297,148 +240,16 @@ private async Task<IList<GiftVoucher>> GetActiveGiftVouchers(Customer customer,
/// </summary>
/// <param name="cart">Cart</param>
/// <param name="includingTax">A value indicating whether calculated price should include tax</param>
public virtual async Task<(double discountAmount, List<ApplyDiscount> appliedDiscounts,
/// <remarks>
/// Not virtual: payment providers read the subtotal from <see cref="IShoppingCartSubTotalService" />,
/// so an override here would make the order total and the payment fee disagree.
/// Customize the subtotal (and its discount) in <see cref="ShoppingCartSubTotalService" /> instead.
/// </remarks>
public Task<(double discountAmount, List<ApplyDiscount> appliedDiscounts,
double subTotalWithoutDiscount, double subTotalWithDiscount, SortedDictionary<double, double> taxRates)>
GetShoppingCartSubTotal(IList<ShoppingCartItem> cart, bool includingTax)
{
double discountAmount = 0;
var appliedDiscounts = new List<ApplyDiscount>();
double subTotalWithoutDiscount = 0;
double subTotalWithDiscount = 0;
var taxRates = new SortedDictionary<double, double>();

if (!cart.Any())
return (discountAmount, appliedDiscounts, subTotalWithoutDiscount, subTotalWithDiscount, taxRates);

//get the customer
var customer = _contextAccessor.WorkContext.CurrentCustomer;
var store = _contextAccessor.StoreContext.CurrentStore;

//sub totals
double subTotalExclTaxWithoutDiscount = 0;
double subTotalInclTaxWithoutDiscount = 0;
foreach (var shoppingCartItem in cart)
{
var product = await _productService.GetProductById(shoppingCartItem.ProductId);
if (product == null)
continue;

var subtotal = await _pricingService.GetSubTotal(shoppingCartItem, product);
var sciSubTotal = subtotal.subTotal;

var pricesExcl = await _taxService.GetProductPrice(product, sciSubTotal, false, customer, store);
var sciExclTax = pricesExcl.productprice;

var (sciInclTax, taxRate) = await _taxService.GetProductPrice(product, sciSubTotal, true, customer, store);

subTotalExclTaxWithoutDiscount += sciExclTax;
subTotalInclTaxWithoutDiscount += sciInclTax;

//tax rates
var sciTax = sciInclTax - sciExclTax;
if (!(taxRate > 0) || !(sciTax > 0)) continue;
if (!taxRates.ContainsKey(taxRate))
taxRates.Add(taxRate, sciTax);
else
taxRates[taxRate] += sciTax;
}

//checkout attributes
if (customer != null)
{
var checkoutAttributes = customer.GetUserFieldFromEntity<List<CustomAttribute>>(SystemCustomerFieldNames.CheckoutAttributes, store.Id);
var attributeValues = await _checkoutAttributeParser.ParseCheckoutAttributeValue(checkoutAttributes);
foreach (var attributeValue in attributeValues)
{
var checkoutAttributePriceExclTax = await _taxService.GetCheckoutAttributePrice(attributeValue.ca, attributeValue.cav, false, customer, store);

var caExclTax = await _currencyService.ConvertFromPrimaryStoreCurrency(checkoutAttributePriceExclTax.checkoutPrice, _contextAccessor.WorkContext.WorkingCurrency);
var (checkoutPrice, taxRate) = await _taxService.GetCheckoutAttributePrice(attributeValue.ca, attributeValue.cav, true, customer, store);
var caInclTax = await _currencyService.ConvertFromPrimaryStoreCurrency(checkoutPrice, _contextAccessor.WorkContext.WorkingCurrency);

subTotalExclTaxWithoutDiscount += caExclTax;
subTotalInclTaxWithoutDiscount += caInclTax;

//tax rates
var caTax = caInclTax - caExclTax;
if (!(taxRate > 0) || !(caTax > 0)) continue;
if (!taxRates.ContainsKey(taxRate))
taxRates.Add(taxRate, caTax);
else
taxRates[taxRate] += caTax;
}
}

//subtotal without discount
subTotalWithoutDiscount = includingTax ? subTotalInclTaxWithoutDiscount : subTotalExclTaxWithoutDiscount;

if (subTotalWithoutDiscount < 0)
subTotalWithoutDiscount = 0;

if (_shoppingCartSettings.RoundPrices)
subTotalWithoutDiscount = RoundingHelper.RoundPrice(subTotalWithoutDiscount, _contextAccessor.WorkContext.WorkingCurrency);

//We calculate discount amount on order subtotal excl tax (discount first)
//calculate discount amount ('Applied to order subtotal' discount)
var orderSubtotalDiscount = await GetOrderSubtotalDiscount(customer, _contextAccessor.StoreContext.CurrentStore,
_contextAccessor.WorkContext.WorkingCurrency, subTotalExclTaxWithoutDiscount);
var discountAmountExclTax = orderSubtotalDiscount.ordersubtotaldiscount;
appliedDiscounts = orderSubtotalDiscount.appliedDiscounts;

if (subTotalExclTaxWithoutDiscount < discountAmountExclTax)
discountAmountExclTax = subTotalExclTaxWithoutDiscount;
var discountAmountInclTax = discountAmountExclTax;
//subtotal with discount (excl tax)
var subTotalExclTaxWithDiscount = subTotalExclTaxWithoutDiscount - discountAmountExclTax;
var subTotalInclTaxWithDiscount = subTotalExclTaxWithDiscount;

//add tax for shopping items & checkout attributes
var tempTaxRates = new Dictionary<double, double>(taxRates);
foreach (var kvp in tempTaxRates)
{
var taxRate = kvp.Key;
var taxValue = kvp.Value;

if (taxValue == 0) continue;
//discount the tax amount that applies to subtotal items
if (subTotalExclTaxWithoutDiscount > 0)
{
var discountTax = taxRates[taxRate] * (discountAmountExclTax / subTotalExclTaxWithoutDiscount);
discountAmountInclTax += discountTax;
taxValue = taxRates[taxRate] - discountTax;
if (_shoppingCartSettings.RoundPrices)
taxValue = RoundingHelper.RoundPrice(taxValue, _contextAccessor.WorkContext.WorkingCurrency);
taxRates[taxRate] = taxValue;
}

//subtotal with discount (incl tax)
subTotalInclTaxWithDiscount += taxValue;
}

if (_shoppingCartSettings.RoundPrices)
{
discountAmountInclTax = RoundingHelper.RoundPrice(discountAmountInclTax, _contextAccessor.WorkContext.WorkingCurrency);
discountAmountExclTax = RoundingHelper.RoundPrice(discountAmountExclTax, _contextAccessor.WorkContext.WorkingCurrency);
}

if (includingTax)
{
subTotalWithDiscount = subTotalInclTaxWithDiscount;
discountAmount = discountAmountInclTax;
}
else
{
subTotalWithDiscount = subTotalExclTaxWithDiscount;
discountAmount = discountAmountExclTax;
}

if (subTotalWithDiscount < 0)
subTotalWithDiscount = 0;

if (_shoppingCartSettings.RoundPrices)
subTotalWithDiscount = RoundingHelper.RoundPrice(subTotalWithDiscount, _contextAccessor.WorkContext.WorkingCurrency);

return (discountAmount, appliedDiscounts, subTotalWithoutDiscount, subTotalWithDiscount, taxRates);
return _shoppingCartSubTotalService.GetShoppingCartSubTotal(cart, includingTax);
}


Expand Down
Loading
Loading