Пример #1
0
        public async Task CalculateAsync(CalculatorContext context, CalculatorDelegate next)
        {
            var product = context.Product;
            var options = context.Options;
            // Ignore tier prices of bundle items (BundlePerItemPricing).
            var processTierPrices = !options.IgnoreTierPrices && !options.IgnoreDiscounts && product.HasTierPrices && context.BundleItem?.Item == null;

            if (processTierPrices)
            {
                var tierPrices = await context.GetTierPricesAsync();

                // Put minimum tier price to context because it's required for discount calculation.
                context.MinTierPrice = GetMinimumTierPrice(product, tierPrices, context.Quantity);

                if (context.Options.DetermineLowestPrice && !context.HasPriceRange)
                {
                    context.HasPriceRange = tierPrices.Any() && !(tierPrices.Count == 1 && tierPrices.First().Quantity <= 1);
                }
            }

            // Process the whole pipeline. We need the result of discount calculation.
            await next(context);

            if (processTierPrices && context.MinTierPrice.HasValue)
            {
                // Apply the minimum tier price if it achieves a lower price than the discounted FinalPrice.
                context.FinalPrice = Math.Min(context.FinalPrice, context.MinTierPrice.Value);
            }
        }
Пример #2
0
        public async Task CalculateAsync(CalculatorContext context, CalculatorDelegate next)
        {
            var product           = context.Product;
            var options           = context.Options;
            var processTierPrices = !options.IgnoreTierPrices && !options.IgnoreDiscounts && product.HasTierPrices && context.BundleItem == null;

            if (processTierPrices)
            {
                var tierPrices = await context.GetTierPricesAsync();

                // Put minimum tier price to context because it's required for discount calculation.
                context.MinTierPrice = GetMinimumTierPrice(product, tierPrices, context.Quantity);

                if (context.Options.DetermineLowestPrice && !context.HasPriceRange)
                {
                    context.HasPriceRange = tierPrices.Any() && !(tierPrices.Count == 1 && tierPrices.First().Quantity <= 1);
                }
            }

            // Process the whole pipeline. We need the result of discount calculation.
            await next(context);

            if (processTierPrices && context.MinTierPrice.HasValue)
            {
                // Wrong result:
                //context.FinalPrice = Math.Min(context.FinalPrice, context.MinTierPrice.Value);

                // Apply the minimum tier price if it achieves a lower price than the discounted FinalPrice
                // but exclude additional charge from comparing.
                context.FinalPrice -= context.AdditionalCharge;

                if (context.MinTierPrice.Value < context.FinalPrice)
                {
                    context.DiscountAmount += context.FinalPrice - context.MinTierPrice.Value;
                    context.FinalPrice      = context.MinTierPrice.Value;
                }

                context.FinalPrice += context.AdditionalCharge;
            }
        }
        public override async Task CalculateAsync(CalculatorContext context, CalculatorDelegate next)
        {
            var options = context.Options;
            var product = context.Product;

            if (options.IgnoreAttributes || !(context.Attributes?.Any() ?? false))
            {
                // Proceed with pipeline and omit this calculator, it is made for attributes price calculation only.
                await next(context);

                return;
            }

            var processTierPrices =
                !options.IgnoreTierPrices &&
                product.HasTierPrices &&
                context.BundleItem?.Item == null &&
                context.Quantity > 1 &&
                _catalogSettings.ApplyTierPricePercentageToAttributePriceAdjustments;

            var attributes = await context.Options.BatchContext.Attributes.GetOrLoadAsync(product.Id);

            var attributeValues = GetSelectedAttributeValues(context, attributes);

            var linkedProductIds = attributeValues
                                   .Where(x => x.ValueType == ProductVariantAttributeValueType.ProductLinkage && x.LinkedProductId != 0)
                                   .Select(x => x.LinkedProductId)
                                   .Distinct()
                                   .ToArray();

            var linkedProducts = linkedProductIds.Any()
                ? await _db.Products.AsNoTracking().Where(x => linkedProductIds.Contains(x.Id)).ToDictionaryAsync(x => x.Id)
                : new Dictionary <int, Product>();

            // Add attribute price adjustment to final price.
            foreach (var value in attributeValues)
            {
                if (value.LinkedProductId == 0)
                {
                    if (processTierPrices && value.PriceAdjustment > decimal.Zero)
                    {
                        var tierPrices = await context.GetTierPricesAsync();

                        var priceAdjustment = GetTierPriceAttributeAdjustment(product, tierPrices, context.Quantity, value.PriceAdjustment);
                        context.FinalPrice += priceAdjustment;
                    }
                    else
                    {
                        context.FinalPrice += value.PriceAdjustment;
                    }
                }
                else if (linkedProducts.TryGetValue(value.LinkedProductId, out var linkedProduct))
                {
                    var childCalculation = await CalculateChildPriceAsync(linkedProduct, context, c =>
                    {
                        c.Options.IgnoreDiscounts = false;
                        c.Quantity           = 1;
                        c.AssociatedProducts = null;
                        c.BundleItems        = null;
                        c.BundleItem         = null;
                        c.AdditionalCharge   = decimal.Zero;
                        c.MinTierPrice       = null;
                    });

                    // Add price of linked product to root final price (unit price * linked product quantity).
                    context.FinalPrice += decimal.Multiply(childCalculation.FinalPrice, value.Quantity);
                }
            }

            await next(context);
        }
Пример #4
0
        public override async Task CalculateAsync(CalculatorContext context, CalculatorDelegate next)
        {
            var options = context.Options;
            var product = context.Product;

            if (!context.SelectedAttributes.Any() && !options.ApplyPreselectedAttributes && !options.DeterminePriceAdjustments)
            {
                // No selected attributes provided and no preselected attributes should be applied and no price adjustments should be determined,
                // then proceed with pipeline and omit this calculator.
                await next(context);

                return;
            }

            var includeTierPriceAttributePriceAdjustment =
                !options.IgnoreTierPrices &&
                !options.IgnorePercentageTierPricesOnAttributePriceAdjustments &&
                product.HasTierPrices &&
                context.BundleItem == null &&
                context.Quantity > 1;

            var attributes = await options.BatchContext.Attributes.GetOrLoadAsync(product.Id);

            var attributeValues = await GetSelectedAttributeValuesAsync(context, attributes);

            var hasSelectedValues = attributeValues.Any();

            if (!hasSelectedValues && options.DeterminePriceAdjustments)
            {
                // Get price adjustments of ALL attribute values. Do not apply anything to FinalPrice, just return them via context.AttributePriceAdjustments.
                attributeValues = attributes.SelectMany(x => x.ProductVariantAttributeValues).ToList();
            }

            // Ignore attributes that have no relevance for pricing.
            attributeValues = attributeValues
                              .Where(x => x.PriceAdjustment != decimal.Zero || x.ValueType == ProductVariantAttributeValueType.ProductLinkage)
                              .ToList();

            var linkedProductIds = attributeValues
                                   .Where(x => x.ValueType == ProductVariantAttributeValueType.ProductLinkage && x.LinkedProductId != 0)
                                   .Select(x => x.LinkedProductId)
                                   .Distinct()
                                   .ToArray();

            var linkedProducts = linkedProductIds.Any()
                ? await _db.Products.AsNoTracking().Where(x => linkedProductIds.Contains(x.Id)).ToDictionaryAsync(x => x.Id)
                : new Dictionary <int, Product>();

            foreach (var value in attributeValues)
            {
                // Calculate price adjustment.
                var adjustment = decimal.Zero;

                if (value.ValueType == ProductVariantAttributeValueType.Simple)
                {
                    if (includeTierPriceAttributePriceAdjustment && value.PriceAdjustment > 0m)
                    {
                        var tierPrices = await context.GetTierPricesAsync();

                        adjustment = GetTierPriceAttributeAdjustment(product, tierPrices, context.Quantity, value.PriceAdjustment);
                    }

                    if (adjustment == 0m)
                    {
                        adjustment = value.PriceAdjustment;
                    }
                }
                else if (value.ValueType == ProductVariantAttributeValueType.ProductLinkage && linkedProducts.TryGetValue(value.LinkedProductId, out var linkedProduct))
                {
                    var childCalculation = await CalculateChildPriceAsync(linkedProduct, context, c =>
                    {
                        c.Quantity = 1;
                        c.Options.IgnoreDiscounts = true;
                    });

                    // Add price of linked product to root final price (unit price * linked product quantity).
                    adjustment = decimal.Multiply(childCalculation.FinalPrice, value.Quantity);
                }

                if (adjustment != 0m)
                {
                    // Apply the adjustment only if selected attributes have been provided.
                    if (hasSelectedValues)
                    {
                        context.FinalPrice       += adjustment;
                        context.AdditionalCharge += adjustment;
                    }

                    if (options.DeterminePriceAdjustments)
                    {
                        context.AttributePriceAdjustments.Add(new CalculatedPriceAdjustment
                        {
                            RawPriceAdjustment = adjustment,
                            AttributeValue     = value,
                            ProductId          = product.Id,
                            BundleItemId       = context?.BundleItem?.Id
                        });
                    }
                }
            }

            await next(context);
        }