public async void Should_benefit_fulfillment_fee_if_discount_is_higher_than_fee()
        {
            fixture.Factory.ClearAllEntities();

            HttpClient client = fixture.Factory.CreateClient();

            Promotion promotion = await new PromotionBuilder()
                                  .QualifiedBy(new IsCurrentDayConditionBuilder()
                                               .Day(DateTime.Now.Day))
                                  .BenefitBy(new CartAmountOffFulfillmentActionBuilder()
                                             .AmountOff(8)) // Fee = 5, Discount = 8
                                  .Build(fixture.Factory);

            fixture.Factory.AddEntityToList(promotion, CommerceEntity.ListName <Promotion>());
            fixture.Factory.AddEntity(promotion);

            Cart cart = await new CartBuilder()
                        .WithLines(new LineBuilder().Quantity(1).Price(50))
                        .WithStandardFulfillment()       // Default fulfillment fee = 5
                        .Build();

            fixture.Factory.AddEntity(cart);

            Cart resultCart =
                await client.GetJsonAsync <Cart>(
                    "api/Carts('Cart01')?$expand=Lines($expand=CartLineComponents($expand=ChildComponents)),Components");

            AwardedAdjustment adjustment =
                resultCart.Adjustments.Single(x => x.AwardingBlock == nameof(CartAmountOffFulfillmentAction));

            Assert.Equal(-5, adjustment.Adjustment.Amount);

            // Subtotal = 50, Tax is 10% = 5, Fulfillment fee = 5 - 5 = 0
            Assert.Equal(55, resultCart.Totals.GrandTotal.Amount);
        }
        private void PopulateAdjustmentChildView(EntityView entityView, AwardedAdjustment adjustment, CommercePipelineExecutionContext context)
        {
            EntityView entityView1 = new EntityView();

            entityView1.EntityId = entityView.EntityId;
            entityView1.ItemId   = adjustment.Name;
            entityView1.Name     = "Adjustment"; // context.GetPolicy<KnownOrderViewsPolicy>().Adjustment;
            EntityView          entityView2   = entityView1;
            List <ViewProperty> properties1   = entityView2.Properties;
            ViewProperty        viewProperty1 = new ViewProperty();

            viewProperty1.Name       = "ItemId";
            viewProperty1.IsHidden   = true;
            viewProperty1.IsReadOnly = true;
            viewProperty1.RawValue   = (object)adjustment.Name;
            properties1.Add(viewProperty1);

            entityView1.AddProperty("Name", adjustment.DisplayName);

            List <ViewProperty> properties2   = entityView2.Properties;
            ViewProperty        viewProperty2 = new ViewProperty();

            viewProperty2.Name       = "Type";
            viewProperty2.IsReadOnly = true;
            viewProperty2.RawValue   = (object)adjustment.AdjustmentType;
            properties2.Add(viewProperty2);
            List <ViewProperty> properties3   = entityView2.Properties;
            ViewProperty        viewProperty3 = new ViewProperty();

            viewProperty3.Name       = "Adjustment";
            viewProperty3.IsReadOnly = true;
            viewProperty3.RawValue   = (object)adjustment.Adjustment;
            properties3.Add(viewProperty3);
            List <ViewProperty> properties4   = entityView2.Properties;
            ViewProperty        viewProperty4 = new ViewProperty();

            viewProperty4.Name       = "Taxable";
            viewProperty4.IsReadOnly = true;
            viewProperty4.RawValue   = (object)adjustment.IsTaxable;
            properties4.Add(viewProperty4);
            List <ViewProperty> properties5   = entityView2.Properties;
            ViewProperty        viewProperty5 = new ViewProperty();

            viewProperty5.Name       = "IncludeInGrandTotal";
            viewProperty5.IsReadOnly = true;
            viewProperty5.RawValue   = (object)adjustment.IncludeInGrandTotal;
            properties5.Add(viewProperty5);
            List <ViewProperty> properties6   = entityView2.Properties;
            ViewProperty        viewProperty6 = new ViewProperty();

            viewProperty6.Name       = "AwardingBlock";
            viewProperty6.IsReadOnly = true;
            viewProperty6.RawValue   = (object)adjustment.AwardingBlock;
            properties6.Add(viewProperty6);
            entityView.ChildViews.Add((Model)entityView2);
        }
        public async void Should_benefit_multiple_times_when_within_action_limit()
        {
            fixture.Factory.ClearAllEntities();

            HttpClient client = fixture.Factory.CreateClient();

            Promotion promotion = await new PromotionBuilder()
                                  .QualifiedBy(new IsCurrentDayConditionBuilder()
                                               .Day(DateTime.Now.Day))
                                  .BenefitBy(new CartEveryXItemsInCategoryPriceDiscountActionBuilder()
                                             .AmountOff(10)
                                             .ForCategory("Laptops")
                                             .ItemsToAward(1)
                                             .ItemsToPurchase(2)
                                             .ApplyActionTo(ApplicationOrder.Ascending)
                                             .ActionLimit(2))
                                  .Build(fixture.Factory);

            fixture.Factory.AddEntityToList(promotion, CommerceEntity.ListName <Promotion>());
            fixture.Factory.AddEntity(promotion);

            fixture.Factory.AddEntity(new Category
            {
                Id         = "Laptops".ToEntityId <Category>(),
                SitecoreId = "435345345"
            });

            Cart cart = await new CartBuilder()
                        .WithLines(new LineBuilder().IdentifiedBy("001").Quantity(1).InCategory("435345345").Price(40),
                                   new LineBuilder().IdentifiedBy("002").Quantity(1).InCategory("435345345").Price(50),
                                   new LineBuilder().IdentifiedBy("003").Quantity(1).InCategory("435345345").Price(40),
                                   new LineBuilder().IdentifiedBy("004").Quantity(1).InCategory("435345345").Price(50))
                        .Build();

            fixture.Factory.AddEntity(cart);

            Cart resultCart =
                await client.GetJsonAsync <Cart>(
                    "api/Carts('Cart01')?$expand=Lines($expand=CartLineComponents($expand=ChildComponents)),Components");

            CartLineComponent firstLine  = resultCart.Lines.Single(x => x.Id == "001");
            AwardedAdjustment adjustment =
                firstLine.Adjustments.Single(x => x.AwardingBlock == nameof(CartEveryXItemsInCategoryPriceDiscountAction));

            Assert.Equal(-10, adjustment.Adjustment.Amount);

            CartLineComponent secondLine       = resultCart.Lines.Single(x => x.Id == "003");
            AwardedAdjustment secondAdjustment =
                secondLine.Adjustments.Single(x => x.AwardingBlock == nameof(CartEveryXItemsInCategoryPriceDiscountAction));

            Assert.Equal(-10, secondAdjustment.Adjustment.Amount);

            // Subtotal = 160, Tax is 10% = 16, Fulfillment fee = 5
            Assert.Equal(181, resultCart.Totals.GrandTotal.Amount);
        }
Beispiel #4
0
        public async void Should_benefit()
        {
            fixture.Factory.ClearAllEntities();

            HttpClient client = fixture.Factory.CreateClient();

            Promotion promotion = await new PromotionBuilder()
                                  .QualifiedBy(new IsCurrentDayConditionBuilder()
                                               .Day(DateTime.Now.Day))
                                  .BenefitBy(new CartFreeGiftActionBuilder()
                                             .Quantity(1)
                                             .Gift("MyCatalog|999|"))
                                  .Build(fixture.Factory);

            var catalog = new Catalog
            {
                Id         = "Entity-Catalog-MyCatalog",
                FriendlyId = "MyCatalog",
                Name       = "MyCatalog"
            };

            SellableItem sellableItem = new SellableItemBuilder()
                                        .IdentifiedBy("001")
                                        .Priced(40)
                                        .Catalog("MyCatalog")
                                        .Build();

            SellableItem gift = new SellableItemBuilder()
                                .IdentifiedBy("999")
                                .Catalog("MyCatalog")
                                .Named("Free Gift")
                                .Priced(99)
                                .Build();

            Cart cart = await new CartBuilder()
                        .WithLines(new LineBuilder()
                                   .IdentifiedBy("001").WithProductId("MyCatalog|001|").Quantity(1).Price(40))
                        .Build();

            fixture.Factory.AddEntities(promotion, gift, sellableItem, catalog, cart);
            fixture.Factory.AddEntityToList(promotion, CommerceEntity.ListName <Promotion>());

            Cart resultCart =
                await client.GetJsonAsync <Cart>(
                    "api/Carts('Cart01')?$expand=Lines($expand=CartLineComponents($expand=ChildComponents)),Components");

            CartLineComponent giftLine = resultCart.Lines.Single(x => x.ItemId == "MyCatalog|999|");

            AwardedAdjustment adjustment = giftLine.Adjustments.Single(x => x.AwardingBlock == nameof(CartFreeGiftAction));

            Assert.Equal(-99, adjustment.Adjustment.Amount);

            // Subtotal = 40, Tax is 10% = 4, Fulfillment fee = 5
            Assert.Equal(49, resultCart.Totals.GrandTotal.Amount);
        }
Beispiel #5
0
        private static decimal GetFulfillmentFee(Cart cart)
        {
            if (cart.HasComponent <SplitFulfillmentComponent>())
            {
                return(cart.Lines
                       .Select(line => line.Adjustments.FirstOrDefault(a => a.Name.EqualsOrdinalIgnoreCase("FulfillmentFee")))
                       .TakeWhile(lineFulfillment => lineFulfillment != null)
                       .Sum(lineFulfillment => lineFulfillment.Adjustment.Amount));
            }

            AwardedAdjustment awardedAdjustment =
                cart.Adjustments.FirstOrDefault(a => a.Name.EqualsOrdinalIgnoreCase("FulfillmentFee"));

            return(awardedAdjustment?.Adjustment.Amount ?? 0);
        }
Beispiel #6
0
        public async void Should_benefit_in_descending_order_when_category_matches()
        {
            fixture.Factory.ClearAllEntities();

            HttpClient client = fixture.Factory.CreateClient();

            Promotion promotion = await new PromotionBuilder()
                                  .QualifiedBy(new IsCurrentDayConditionBuilder()
                                               .Day(DateTime.Now.Day))
                                  .BenefitBy(new CartItemsMatchingInCategoryPercentageDiscountActionBuilder()
                                             .PercentageOff(50)
                                             .ForCategory("Laptops")
                                             .Operator(Operator.Equal)
                                             .NumberOfProducts(2)
                                             .ApplyActionTo(ApplicationOrder.Descending)
                                             .ActionLimit(1))
                                  .Build(fixture.Factory);

            fixture.Factory.AddEntityToList(promotion, CommerceEntity.ListName <Promotion>());
            fixture.Factory.AddEntity(promotion);

            fixture.Factory.AddEntity(new Category
            {
                Id         = "Laptops".ToEntityId <Category>(),
                SitecoreId = "435345345"
            });

            Cart cart = await new CartBuilder()
                        .WithLines(new LineBuilder().IdentifiedBy("001").Quantity(1).InCategory("435345345").Price(40),
                                   new LineBuilder().IdentifiedBy("002").Quantity(1).InCategory("435345345").Price(50))
                        .Build();

            fixture.Factory.AddEntity(cart);

            Cart resultCart =
                await client.GetJsonAsync <Cart>(
                    "api/Carts('Cart01')?$expand=Lines($expand=CartLineComponents($expand=ChildComponents)),Components");

            CartLineComponent line       = resultCart.Lines.Single(x => x.Id == "002");
            AwardedAdjustment adjustment = line.Adjustments.Single(x =>
                                                                   x.AwardingBlock ==
                                                                   nameof(CartItemsMatchingInCategoryPercentageDiscountAction));

            Assert.Equal(-25, adjustment.Adjustment.Amount);

            // Subtotal = 65, Tax is 10% = 6.5, Fulfillment fee = 5
            Assert.Equal(76.5m, resultCart.Totals.GrandTotal.Amount);
        }
        public async void Should_benefit_split_fulfillments()
        {
            fixture.Factory.ClearAllEntities();

            HttpClient client = fixture.Factory.CreateClient();

            Promotion promotion = await new PromotionBuilder()
                                  .QualifiedBy(new IsCurrentDayConditionBuilder()
                                               .Day(DateTime.Now.Day))
                                  .BenefitBy(new CartAmountOffFulfillmentActionBuilder()
                                             .AmountOff(3)) // Fee = 2 * 2, Discount = 3
                                  .Build(fixture.Factory);

            fixture.Factory.AddEntityToList(promotion, CommerceEntity.ListName <Promotion>());
            fixture.Factory.AddEntity(promotion);

            Cart cart = await new CartBuilder()
                        .WithLines(new LineBuilder()
                                   .IdentifiedBy("001")
                                   .Quantity(1)
                                   .WithStandardFulfillment()       // Default fulfillment fee per line item is 2
                                   .Price(50), new LineBuilder()
                                   .IdentifiedBy("002")
                                   .Quantity(1)
                                   .WithStandardFulfillment()                   // Default fulfillment fee per line item is 2
                                   .Price(50))
                        .WithSplitFulfillment()
                        .Build();

            fixture.Factory.AddEntity(cart);

            Cart resultCart =
                await client.GetJsonAsync <Cart>(
                    "api/Carts('Cart01')?$expand=Lines($expand=CartLineComponents($expand=ChildComponents)),Components");

            AwardedAdjustment adjustment =
                resultCart.Adjustments.Single(x => x.AwardingBlock == nameof(CartAmountOffFulfillmentAction));

            Assert.Equal(-3, adjustment.Adjustment.Amount);

            // Subtotal = 100, Tax is 10% = 10, Fulfillment fee = 2*2 - 3 = 1
            Assert.Equal(111, resultCart.Totals.GrandTotal.Amount);
        }
            public void Execute_WithProperties(
                IRuleValue <string> fulfillmentOptionName,
                IRuleValue <decimal> amountOff,
                Cart cart,
                CommerceContext commerceContext,
                IRuleExecutionContext context,
                string fulfillmentOptionNameValue,
                FulfillmentComponent fulfillmentComponent)
            {
                /**********************************************
                * Arrange
                **********************************************/
                const decimal FULFILLMENT_FEE = MINIMUM_AMOUNT_OFF + 10m;

                cart.Adjustments.Clear();
                var awardedAdjustment = new AwardedAdjustment()
                {
                    Name = "FulfillmentFee"
                };

                awardedAdjustment.Adjustment = new Money(FULFILLMENT_FEE);
                cart.Adjustments.Add(awardedAdjustment);
                cart.Lines.ForEach(l => l.Adjustments.Clear());
                cart.SetComponent(fulfillmentComponent);

                context.Fact <CommerceContext>().ReturnsForAnyArgs(commerceContext);
                commerceContext.AddObject(cart);

                var globalPricingPolicy = commerceContext.GetPolicy <GlobalPricingPolicy>();

                globalPricingPolicy.ShouldRoundPriceCalc = false;
                fulfillmentOptionName.Yield(context).ReturnsForAnyArgs(fulfillmentOptionNameValue);
                amountOff.Yield(context).ReturnsForAnyArgs(MINIMUM_AMOUNT_OFF);
                var propertiesModel = new PropertiesModel();

                propertiesModel.Properties.Add("PromotionId", "id");
                propertiesModel.Properties.Add("PromotionCartText", "carttext");
                propertiesModel.Properties.Add("PromotionText", "text");
                commerceContext.AddObject(propertiesModel);

                var commander = Substitute.For <CommerceCommander>(Substitute.For <IServiceProvider>());
                var command   = Substitute.For <GetFulfillmentMethodsCommand>(
                    Substitute.For <IFindEntityPipeline>(),
                    Substitute.For <IGetCartFulfillmentMethodsPipeline>(),
                    Substitute.For <IGetCartLineFulfillmentMethodsPipeline>(),
                    Substitute.For <IGetFulfillmentMethodsPipeline>(),
                    Substitute.For <IServiceProvider>());

                var fulfillmentMethod = new FulfillmentMethod()
                {
                    FulfillmentType = fulfillmentOptionNameValue
                };

                command.Process(commerceContext).ReturnsForAnyArgs(new List <FulfillmentMethod>()
                {
                    fulfillmentMethod
                }.AsEnumerable());
                commander.When(x => x.Command <GetFulfillmentMethodsCommand>()).DoNotCallBase();
                commander.Command <GetFulfillmentMethodsCommand>().Returns(command);
                var action = new CartShippingOptionAmountOffAction(commander)
                {
                    FulfillmentOptionName = fulfillmentOptionName,
                    AmountOff             = amountOff
                };

                /**********************************************
                * Act
                **********************************************/
                Action executeAction = () => action.Execute(context);

                /**********************************************
                * Assert
                **********************************************/
                executeAction.Should().NotThrow <Exception>();
                cart.Lines.SelectMany(l => l.Adjustments).Should().HaveCount(0);
                cart.Adjustments.Should().NotBeEmpty();
                cart.Adjustments.LastOrDefault().Should().NotBeNull();
                cart.Adjustments.LastOrDefault().Should().BeOfType <CartLevelAwardedAdjustment>();
                cart.Adjustments.LastOrDefault().Name.Should().Be("text");
                cart.Adjustments.LastOrDefault().DisplayName.Should().Be("carttext");
                cart.Adjustments.LastOrDefault().AdjustmentType.Should().Be(commerceContext.GetPolicy <KnownCartAdjustmentTypesPolicy>().Discount);
                cart.Adjustments.LastOrDefault().AwardingBlock.Should().Contain(nameof(CartShippingOptionAmountOffAction));
                cart.Adjustments.LastOrDefault().IsTaxable.Should().BeFalse();
                cart.Adjustments.LastOrDefault().Adjustment.CurrencyCode.Should().Be(commerceContext.CurrentCurrency());
                cart.Adjustments.LastOrDefault().Adjustment.Amount.Should().Be(-MINIMUM_AMOUNT_OFF);
                cart.HasComponent <MessagesComponent>().Should().BeTrue();
            }
            public void Execute_AmountOffLessThanFulfillmentFee_False(
                IRuleValue <string> fulfillmentOptionName,
                IRuleValue <decimal> amountOff,
                Cart cart,
                CommerceContext commerceContext,
                IRuleExecutionContext context,
                string fulfillmentOptionNameValue,
                FulfillmentComponent fulfillmentComponent)
            {
                /**********************************************
                * Arrange
                **********************************************/
                const decimal FULFILLMENT_FEE = 10m;
                const decimal AMOUNT_OFF      = FULFILLMENT_FEE + 1m;

                cart.Adjustments.Clear();
                var awardedAdjustment = new AwardedAdjustment()
                {
                    Name = "FulfillmentFee"
                };

                awardedAdjustment.Adjustment = new Money(FULFILLMENT_FEE);
                cart.Adjustments.Add(awardedAdjustment);
                cart.Lines.ForEach(l => l.Adjustments.Clear());
                cart.SetComponent(fulfillmentComponent);

                context.Fact <CommerceContext>().ReturnsForAnyArgs(commerceContext);
                commerceContext.AddObject(cart);

                fulfillmentOptionName.Yield(context).ReturnsForAnyArgs(fulfillmentOptionNameValue);
                amountOff.Yield(context).ReturnsForAnyArgs(AMOUNT_OFF);

                var commander = Substitute.For <CommerceCommander>(Substitute.For <IServiceProvider>());
                var command   = Substitute.For <GetFulfillmentMethodsCommand>(
                    Substitute.For <IFindEntityPipeline>(),
                    Substitute.For <IGetCartFulfillmentMethodsPipeline>(),
                    Substitute.For <IGetCartLineFulfillmentMethodsPipeline>(),
                    Substitute.For <IGetFulfillmentMethodsPipeline>(),
                    Substitute.For <IServiceProvider>());

                var fulfillmentMethod = new FulfillmentMethod()
                {
                    FulfillmentType = fulfillmentOptionNameValue
                };

                command.Process(commerceContext).ReturnsForAnyArgs(new List <FulfillmentMethod>()
                {
                    fulfillmentMethod
                }.AsEnumerable());
                commander.When(x => x.Command <GetFulfillmentMethodsCommand>()).DoNotCallBase();
                commander.Command <GetFulfillmentMethodsCommand>().Returns(command);
                var action = new CartShippingOptionAmountOffAction(commander)
                {
                    FulfillmentOptionName = fulfillmentOptionName,
                    AmountOff             = amountOff
                };

                /**********************************************
                * Act
                **********************************************/
                Action executeAction = () => action.Execute(context);

                /**********************************************
                * Assert
                **********************************************/
                executeAction.Should().NotThrow <Exception>();
                command.Received().Process(commerceContext);
                cart.Lines.SelectMany(l => l.Adjustments).Should().BeEmpty();
                cart.Adjustments.Should().HaveCount(2, "The cart adjustments should only contain the fulfillment fee and shipping discount adjustments.");
                var adjustment = cart.Adjustments.LastOrDefault();

                adjustment.Adjustment.Amount.Should().Be(-FULFILLMENT_FEE, "Adjustment amount should match the AmountOff rule.");
            }