private void Calculate(
            CalculationRange range,
            Order order,
            bool deliveryInDeminimisBase,
            IEnumerable <ChargeName> deminimisBaseCharges)
        {
            // If no delivery reversal is required, add to charges
            if (!deliveryInDeminimisBase)
            {
                var inputDeliveryPrice = order.GetChargeAmount(ChargeNames.InputDelivery, order.Currency);
                order.AddCharge(new OrderCharge(ChargeNames.Delivery, inputDeliveryPrice, ChargeNames.Delivery));
            }

            // Create reverse calc using range
            var reverseCalculator = ReverseCalculatorFactory.Create(range, deminimisBaseCharges);

            // Run reverse calc
            reverseCalculator?.Invoke(order);

            // Create forward calc
            var forwardCalculator = ForwardCalculatorFactory.Create(range, includeFixedCalculators: false);

            // Run forward calc
            forwardCalculator?.Invoke(order);
        }
Пример #2
0
        public Task <OrderResponse> Handle(ForwardCalculatorRequest request, CancellationToken cancellationToken)
        {
            // Add initial charges
            foreach (var item in request.Order.OrderItems)
            {
                var inputItemPrice     = item.GetChargeAmount(ChargeNames.InputItem, request.Order.Currency);
                var inputDeliveryPrice = item.GetChargeAmount(ChargeNames.InputDelivery, request.Order.Currency);

                item.AddCharge(new OrderCharge(ChargeNames.Item, inputItemPrice, ChargeNames.Item));
                item.AddCharge(new OrderCharge(ChargeNames.Delivery, inputDeliveryPrice, ChargeNames.Delivery));
            }
            ;

            // Determine deminimis base
            var deminimisBase = request.Order.Charges.Where(chargeName => request.CalculatorConfiguration.DeminimisBaseCharges.Contains(chargeName.ChargeName))
                                .Select(x => x.ChargeAmount.Value).Sum();

            // Get the correct range for the base price
            var range = request.CalculatorConfiguration.GetRangeForBasePrice(new Price(request.Order.Currency, deminimisBase));

            // Create a forward calculator for the selected range
            var calculator = ForwardCalculatorFactory.Create(range);

            // Run calculator
            calculator?.Invoke(request.Order);

            foreach (var item in request.Order.OrderItems)
            {
                var totalItemPrice = item.Charges
                                     .Where(x => !x.InputCharge)
                                     .Where(x => x.ChargeName.Value.EndsWith("Item"))
                                     .Select(x => x.ChargeAmount.Value)
                                     .Sum();

                var totalDeliveryPrice = item.Charges
                                         .Where(x => !x.InputCharge)
                                         .Where(x => x.ChargeName.Value.EndsWith("Delivery"))
                                         .Select(x => x.ChargeAmount.Value)
                                         .Sum();

                item.AddCharge(new OrderCharge("TotalItem", new Price(request.Order.Currency, totalItemPrice), "TotalItem"));
                item.AddCharge(new OrderCharge("TotalDelivery", new Price(request.Order.Currency, totalDeliveryPrice), "TotalDelivery"));
            }

            return(Task.FromResult(new OrderResponse(request.Order)));
        }
        // TODO: Create abstraction
        private void DetectGreyZones(CalculatorConfiguration configuration, Order order)
        {
            var boundaries = new List <Tuple <Price, CalculationRange> >();

            foreach (var range in configuration.CalculationRanges.Where(x => x.DeminimisThreshold.Value != 0))
            {
                boundaries.Add(new Tuple <Price, CalculationRange>(new Price(order.Currency, range.DeminimisThreshold.Value - .01m), range));
                boundaries.Add(new Tuple <Price, CalculationRange>(new Price(order.Currency, range.DeminimisThreshold.Value), range));
            }

            var greyZonePrices = new List <Tuple <Price, CalculationRange> >();

            foreach (var boundary in boundaries)
            {
                // Create forward calc
                var range             = configuration.GetRangeForBasePrice(boundary.Item1);
                var forwardCalculator = ForwardCalculatorFactory.Create(range);

                var orderItems = order.OrderItems.Select(oi => new OrderItem(oi.Quantity, oi.Weight, oi.VatRate, oi.DutyRate, new Price(order.Currency, order.RelativeOrderItemValue(oi) * boundary.Item1.Value))).ToList();
                orderItems.ForEach(oi => { var c = oi.GetChargeAmount(ChargeNames.InputItem, order.Currency); oi.AddCharge(new OrderCharge(ChargeNames.Item, c, ChargeNames.Item)); });
                var detectionOrder = new Order(order.Country, order.Currency, orderItems, new Price(order.Currency, 0));

                forwardCalculator?.Invoke(detectionOrder);

                var greyZonePrice = detectionOrder.Charges
                                    .Where(x => !x.InputCharge)
                                    .Where(x => x.ChargeName.Value.EndsWith(ChargeNames.Item))
                                    .Select(x => x.ChargeAmount.Value)
                                    .Sum();

                greyZonePrices.Add(new Tuple <Price, CalculationRange>(new Price(order.Currency, greyZonePrice), boundary.Item2));
            }

            if (greyZonePrices.Count % 2 != 0)
            {
                throw new ArgumentException("Invalid grey zones, should be in pairs!");
            }

            for (int x = 0; x < greyZonePrices.Count; x++)
            {
                configuration.AddGreyZone(new GreyZone(greyZonePrices[x].Item1, greyZonePrices[x + 1].Item1, greyZonePrices[x].Item2));
                x++;
            }
        }
        public void Create_MultipleCalculators_ShouldCorrectlyCreateCalculator()
        {
            // Arrange
            var chargeConfiguration1 = ChargeConfigurationFactory.CreateFromOptions(new ChargeConfigurationOptions
            {
                CalculationType    = CalculationType.Fixed,
                DeminimisThreshold = "EUR0",
                FixedChargeAmount  = "EUR10",
                ChargeName         = ChargeNames.Duty,
                Rate = 5
            });
            var chargeConfiguration2 = ChargeConfigurationFactory.CreateFromOptions(new ChargeConfigurationOptions
            {
                CalculationType    = CalculationType.RateBased,
                DeminimisThreshold = "EUR0",
                BaseCharges        = { "Item", ChargeNames.Duty },
                ChargeName         = ChargeNames.Vat,
                Rate = 5
            });
            var chargeConfiguration3 = ChargeConfigurationFactory.CreateFromOptions(new ChargeConfigurationOptions
            {
                CalculationType    = CalculationType.WeightBased,
                DeminimisThreshold = "EUR0",
                ChargeName         = "Fee",
                Rate = 5
            });

            var calculationRange = new CalculationRange(new Price("EUR100"), new List <ChargeConfiguration> {
                chargeConfiguration1, chargeConfiguration2, chargeConfiguration3
            });

            // Act
            var calculator = ForwardCalculatorFactory.Create(calculationRange).GetInvocationList();

            // Assert
            calculator.Should().HaveCount(3);
            calculator[0].Method.DeclaringType.Should().Be(typeof(FixedChargeCalculator));
            calculator[1].Method.DeclaringType.Should().Be(typeof(RateBasedChargeCalculator));
            calculator[2].Method.DeclaringType.Should().Be(typeof(WeightBasedChargeCalculator));
        }