コード例 #1
0
        /// <summary>
        /// Gets the final price for a child product by running a nested calculation pipeline.
        /// </summary>
        /// <param name="childProduct">
        /// The child product (e.g. associated product of a grouped product or a bundle item part) to calculate price for.
        /// </param>
        /// <param name="context">The calculator context of the root pipeline.</param>
        /// <param name="childContextConfigurer">An optional configurer action for the child context.</param>
        /// <returns>The nested calculator context.</returns>
        protected async Task <CalculatorContext> CalculateChildPriceAsync(Product childProduct, CalculatorContext context, Action <CalculatorContext> childContextConfigurer = null)
        {
            if (context.Product == childProduct)
            {
                // TODO: (core) Decent error message for price calculation deadlocking.
                throw new InvalidOperationException("Deadlock");
            }

            var childCalculatorContext = new CalculatorContext(context, childProduct.Price)
            {
                Product = childProduct
            };

            childContextConfigurer?.Invoke(childCalculatorContext);

            // INFO: we know that options have been cloned.
            if (context.Options.ChildProductsBatchContext != null)
            {
                childCalculatorContext.Options.BatchContext = context.Options.ChildProductsBatchContext;
            }

            // Get calculators for child product context
            var calculators = _calculatorFactory.GetCalculators(childCalculatorContext);

            // Run calculators
            await _calculatorFactory.RunCalculators(calculators, childCalculatorContext);

            return(childCalculatorContext);
        }
コード例 #2
0
        /// <summary>
        /// Gets the final price for a child product by running a nested calculation pipeline.
        /// </summary>
        /// <param name="childProduct">
        /// The child product (e.g. associated product of a grouped product or a bundle item part) to calculate price for.
        /// Must not be the same product as <see cref="PriceCalculationContext.Product"/>.
        /// </param>
        /// <param name="context">The calculator context of the root pipeline.</param>
        /// <param name="childContextConfigurer">An optional configurer action for the child context.</param>
        /// <returns>The nested calculator context.</returns>
        protected async Task <CalculatorContext> CalculateChildPriceAsync(Product childProduct, CalculatorContext context, Action <CalculatorContext> childContextConfigurer = null)
        {
            if (context.Product == childProduct)
            {
                throw new InvalidOperationException("The product of a nested calculation pipeline cannot be the same as that of the root pipeline. It would result in a deadlock.");
            }

            var childCalculatorContext = new CalculatorContext(context, childProduct.Price)
            {
                Product = childProduct
            };

            childContextConfigurer?.Invoke(childCalculatorContext);

            // INFO: we know that options have been cloned.
            if (context.Options.ChildProductsBatchContext != null)
            {
                childCalculatorContext.Options.BatchContext = context.Options.ChildProductsBatchContext;
            }

            // Get calculators for child product context
            var calculators = _calculatorFactory.GetCalculators(childCalculatorContext);

            // Run calculators
            await _calculatorFactory.RunCalculators(calculators, childCalculatorContext);

            return(childCalculatorContext);
        }
コード例 #3
0
        public async Task RunCalculators(IPriceCalculator[] calculators, CalculatorContext context)
        {
            Guard.NotNull(calculators, nameof(calculators));
            Guard.NotNull(context, nameof(context));

            var numCalculators = calculators.Length;

            if (numCalculators == 0)
            {
                return;
            }

            // Start the pipeline with first calculator middleware
            int i = 0;

            await Next(context);

            async Task Next(CalculatorContext ctx)
            {
                if (i >= numCalculators)
                {
                    return;
                }

                i++;
                await calculators[i - 1].CalculateAsync(ctx, Next);
            }
        }
コード例 #4
0
        /// <summary>
        /// Gets the final price for a child product by running a nested calculation pipeline.
        /// </summary>
        /// <returns>The nested calculator context.</returns>
        protected async Task <CalculatorContext> CalculateChildPriceAsync(Product childProduct, CalculatorContext context)
        {
            if (context.Product == childProduct)
            {
                // TODO: (core) Decent error message for price calculation deadlocking.
                throw new InvalidOperationException("Deadlock");
            }

            var childCalculatorContext = new CalculatorContext(context, childProduct.Price)
            {
                Product  = childProduct,
                Quantity = int.MaxValue
            };

            // INFO: we know that options have been cloned.
            childCalculatorContext.Options.BatchContext          = context.Options.ChildProductsBatchContext;
            childCalculatorContext.Options.IgnoreGroupedProducts = true;
            childCalculatorContext.Options.IgnoreBundles         = true;

            // Get calculators for child product context
            var calculators = _calculatorFactory.GetCalculators(childCalculatorContext);

            // Run calculators
            await _calculatorFactory.RunCalculators(calculators, childCalculatorContext);

            return(childCalculatorContext);
        }
コード例 #5
0
        public CalculatedPrice(CalculatorContext context)
        {
            Guard.NotNull(context, nameof(context));

            Product                   = context.Product;
            AppliedDiscounts          = context.AppliedDiscounts;
            HasPriceRange             = context.HasPriceRange;
            AttributePriceAdjustments = context.AttributePriceAdjustments;
        }
コード例 #6
0
        public CalculatedPrice(CalculatorContext context)
        {
            Guard.NotNull(context, nameof(context));

            Product          = context.Product;
            AppliedDiscounts = context.AppliedDiscounts;
            //AppliedTierPrice = context.AppliedTierPrice;
            //context.AppliedAttributeCombination = context.AppliedAttributeCombination;
            HasPriceRange = context.HasPriceRange;
        }
コード例 #7
0
        public void CopyTo(CalculatorContext target)
        {
            Guard.NotNull(target, nameof(target));

            target.Product          = Product;
            target.RegularPrice     = RegularPrice;
            target.OfferPrice       = OfferPrice;
            target.PreselectedPrice = FinalPrice;
            target.FinalPrice       = FinalPrice;
            target.HasPriceRange    = HasPriceRange;

            target.AppliedDiscounts.Clear();
            target.AppliedDiscounts.AddRange(AppliedDiscounts);
        }
コード例 #8
0
        private Money?ConvertAmount(decimal?amount, CalculatorContext context, TaxRate taxRate, bool isFinalPrice, out Tax?tax)
        {
            tax = null;

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

            var options = context.Options;

            // A product price cannot be less than zero.
            if (amount < 0)
            {
                amount = 0;
            }

            if (amount != 0)
            {
                tax = options.IsGrossPrice
                     ? _taxCalculator.CalculateTaxFromGross(amount.Value, taxRate, options.TaxInclusive, options.RoundingCurrency)
                     : _taxCalculator.CalculateTaxFromNet(amount.Value, taxRate, options.TaxInclusive, options.RoundingCurrency);

                amount = tax.Value.Price;
            }

            var money = _currencyService.ConvertFromPrimaryCurrency(amount.Value, options.TargetCurrency);

            if (amount != 0 && options.TaxFormat != null)
            {
                money = money.WithPostFormat(options.TaxFormat);
            }

            if (isFinalPrice && context.HasPriceRange)
            {
                var finalPricePostFormat = money.PostFormat;
                finalPricePostFormat = finalPricePostFormat == null
                    ? options.PriceRangeFormat
                    : string.Format(options.PriceRangeFormat, finalPricePostFormat);

                if (money.PostFormat != finalPricePostFormat)
                {
                    money = money.WithPostFormat(finalPricePostFormat);
                }
            }

            return(money);
        }
コード例 #9
0
        private async Task <CalculatorContext> RunCalculators(PriceCalculationContext context)
        {
            Guard.NotNull(context, nameof(context));

            // Remember source product.
            var product = context.Product;

            // Collect calculators
            var calculators       = _calculatorFactory.GetCalculators(context);
            var calculatorContext = new CalculatorContext(context, product.Price);

            // Run all collected calculators
            await _calculatorFactory.RunCalculators(calculators, calculatorContext);

            return(calculatorContext);
        }
コード例 #10
0
        private async Task <CalculatedPrice> CreateCalculatedPrice(CalculatorContext context, Product product = null, int subtotalQuantity = 1)
        {
            product ??= context.Product;

            var options = context.Options;

            // Calculate the subtotal price instead of the unit price.
            if (subtotalQuantity > 1 && context.FinalPrice > 0)
            {
                context.FinalPrice     = options.RoundingCurrency.RoundIfEnabledFor(context.FinalPrice) * subtotalQuantity;
                context.DiscountAmount = options.RoundingCurrency.RoundIfEnabledFor(context.DiscountAmount) * subtotalQuantity;
            }

            // Determine tax rate for product.
            var taxRate = await _taxService.GetTaxRateAsync(product, null, options.Customer);

            // Prepare result by converting price amounts.
            var result = new CalculatedPrice(context)
            {
                Product          = product,
                RegularPrice     = ConvertAmount(context.RegularPrice, context, taxRate, false, out _).Value,
                OfferPrice       = ConvertAmount(context.OfferPrice, context, taxRate, false, out _),
                PreselectedPrice = ConvertAmount(context.PreselectedPrice, context, taxRate, false, out _),
                LowestPrice      = ConvertAmount(context.LowestPrice, context, taxRate, false, out _),
                DiscountAmount   = ConvertAmount(context.DiscountAmount, context, taxRate, false, out _).Value,
                FinalPrice       = ConvertAmount(context.FinalPrice, context, taxRate, true, out var tax).Value,
                Tax = tax
            };

            if (tax.HasValue && _primaryCurrency != options.TargetCurrency)
            {
                // Exchange tax amounts.
                // TODO: (mg) (core) Check for rounding issues thoroughly!
                result.Tax = new Tax(
                    tax.Value.Rate,
                    // Amount
                    _currencyService.ConvertFromPrimaryCurrency(tax.Value.Amount, options.TargetCurrency).Amount,
                    // Price
                    result.FinalPrice.Amount,
                    tax.Value.IsGrossPrice,
                    tax.Value.Inclusive);
            }

            // Convert attribute price adjustments.
            context.AttributePriceAdjustments.Each(x => x.Price = ConvertAmount(x.RawPriceAdjustment, context, taxRate, false, out _).Value);

            // Calculate price saving.
            // The final price without discounts has priority over the old price.
            // This avoids differing percentage discount in product lists and detail page.
            var priceWithoutDiscount = result.FinalPrice + result.DiscountAmount;

            var savingPrice = result.FinalPrice < priceWithoutDiscount
                ? priceWithoutDiscount
                : ConvertAmount(product.OldPrice, context, taxRate, false, out _).Value;

            var hasSaving = savingPrice > 0 && result.FinalPrice < savingPrice;

            result.PriceSaving = new PriceSaving
            {
                HasSaving     = hasSaving,
                SavingPrice   = savingPrice,
                SavingPercent = hasSaving ? (float)((savingPrice - result.FinalPrice) / savingPrice) * 100 : 0f,
                SavingAmount  = hasSaving ? (savingPrice - result.FinalPrice).WithPostFormat(null) : null
            };

            return(result);
        }
コード例 #11
0
 protected virtual void Calculate(CalculatorContext context)
 {
 }
コード例 #12
0
 public virtual async Task CalculateAsync(CalculatorContext context, CalculatorDelegate next)
 {
     Calculate(context);
     await next(context);
 }