Copied to clipboard

Flag this post as spam?

This post will be reported to the moderators as potential spam to be looked at


  • Łukasz Koruba 30 posts 151 karma points
    Jan 29, 2021 @ 15:52
    Łukasz Koruba
    0

    Vendr - Disable freeze pricing

    Hey,

    In our solution we want to disable freezing prices or thaw them at some point.

    To calculate correct prices which are based on Member Types we overloaded ProductSnapshotBase and it looks like follows:

    public class MemberTypeProductSnapshot : ProductSnapshotBase
    {
        private readonly VendrServiceContext _vendrServices;
        private readonly IMemberGroupTypeService _memberGroupTypeService;
    
        public IPublishedContent Content { get; set; }
        private string _languageIsoCode;
        private Lazy<Guid> _storeId;
        private Lazy<string> _sku;
        private Lazy<string> _name;
        private Lazy<Guid?> _taxClassId;
        private Lazy<IEnumerable<ProductPrice>> _prices;
        private Lazy<IDictionary<string, string>> _properties;
        private Lazy<bool> _isGiftCard;
        private string _productReference;
    
        public override string ProductReference => _productReference;
    
        public override Guid StoreId => _storeId.Value;
        public override string Sku => _sku.Value;
        public override string Name => _name.Value;
        public override Guid? TaxClassId => _taxClassId.Value;
        public override IEnumerable<ProductPrice> Prices => _prices.Value;
        public override IDictionary<string, string> Properties => _properties.Value;
        public override bool IsGiftCard => _isGiftCard.Value;
    
        public MemberTypeProductSnapshot(VendrServiceContext vendrServiceContext,
              IPublishedContent content,
              string languageIsoCode,
              string productReference)
        {
            _vendrServices = vendrServiceContext;
            _memberGroupTypeService = DependencyResolver.Current.GetService<IMemberGroupTypeService>();
            Content = content;
            _languageIsoCode = languageIsoCode;
            _productReference = productReference.Trim();
    
            Initialize();
        }
    
        private void Initialize()
        {
            _storeId = new Lazy<Guid>(() =>
            {
                var nodePropertyValue = GetNodePropertyValue<StoreReadOnly>(Content, VendrConstants.Properties.Store, _languageIsoCode);
    
                return !(nodePropertyValue == null)
                    ? nodePropertyValue.Id
                    : throw new ArgumentException("The product doesn't have a Store ID associated with it. Remember to add the Vendr store picker to your Umbraco content tree.");
            });
            _sku = new Lazy<string>(() =>
            {
                var nodePropertyValue = GetNodePropertyValue<string>(Content, VendrConstants.Properties.Sku, _languageIsoCode);
                if (!nodePropertyValue.HasValue())
                    nodePropertyValue = Content.Key.ToString();
    
                return nodePropertyValue;
            });
            _name = new Lazy<string>(() => Content.GetTitle());
            _taxClassId = new Lazy<Guid?>(() =>
            {
                var taxClass = _vendrServices.TaxService.GetTaxClass(StoreId, VendrConstants.TaxClass.Standard);
    
                return taxClass.HasValue() && !taxClass.IsDeleted ? taxClass.Id : new Guid?();
            });
            _prices = new Lazy<IEnumerable<ProductPrice>>(()
                => GetProductPrice(_memberGroupTypeService.IsMemberTrader() && !Content.IsGeneralProduct()
                    ? VendrConstants.Properties.TradePrice
                    : VendrConstants.Properties.Price));
            _properties = new Lazy<IDictionary<string, string>>(() =>
            {
                var dictionary = new Dictionary<string, string>();
                foreach (var productPropertyAlias in _vendrServices.StoreService.GetStore(StoreId).ProductPropertyAliases)
                {
                    var nodePropertyValue = GetNodePropertyValue<string>(Content, productPropertyAlias, _languageIsoCode);
                    dictionary.Add(productPropertyAlias, nodePropertyValue);
                }
    
                return dictionary;
            });
            _isGiftCard = new Lazy<bool>(() => GetNodePropertyValue<bool>(Content, VendrConstants.Properties.IsGiftCard, _languageIsoCode));
        }
    
        private IEnumerable<ProductPrice> GetProductPrice(string priceType)
        {
            var productPriceList = new List<ProductPrice>();
    
            foreach (var currency in _vendrServices.CurrencyService.GetCurrencies(StoreId))
            {
                var nodePropertyValue = GetNodePropertyValue(Content,
                    priceType,
                    _languageIsoCode,
                    hasValueCheck: (Func<PricePropertyValue, bool>)(val => val.HasPriceFor(currency.Id)));
    
                if (nodePropertyValue != null && nodePropertyValue.TryGetPriceFor(currency.Id, out var productPrice))
                {
                    productPriceList.Add(Content.IsGeneralProduct()
                        ? productPrice
                        : CalculateDiscount(productPrice));
                }
            }
    
            return productPriceList;
        }
    
        protected static T GetNodePropertyValue<T>(
            IPublishedContent productNode,
            string propertyAlias,
            string languageIsoCode,
            Func<IPublishedContent, bool> ancestorSelector = null,
            bool recursive = true,
            Func<T, bool> hasValueCheck = null)
        {
            if (productNode == null || !propertyAlias.HasValue())
                return default;
    
            var content = ancestorSelector != null ? productNode.AncestorsOrSelf().FirstOrDefault(ancestorSelector) : productNode;
            if (content == null)
                return default;
    
            foreach (var publishedElement in content.AncestorsOrSelf())
            {
                var property = publishedElement.GetProperty(propertyAlias);
                if (property == null)
                    continue;
    
                if (property.PropertyType.VariesByNothing())
                {
                    var obj = property.Value<T>();
                    if (obj.HasValue() && (hasValueCheck == null || hasValueCheck(obj)))
                        return obj;
                }
                else
                {
                    var obj = property.Value<T>(languageIsoCode, fallback: Fallback.ToLanguage);
                    if (obj.HasValue() && (hasValueCheck == null || hasValueCheck(obj)))
                        return obj;
                }
            }
            var productSourceNode = productNode.GetProductSourceNode();
    
            return productSourceNode.HasValue()
                ? GetNodePropertyValue<T>(productSourceNode, propertyAlias, languageIsoCode, ancestorSelector, recursive)
                : default;
        }
    
        private ProductPrice CalculateDiscount(ProductPrice productPrice)
        {
            if (_memberGroupTypeService.IsMemberFriend())
            {
                return new ProductPrice(
                    productPrice.Value * (1m - DiscountsConstants.Friend / 100m),
                    productPrice.CurrencyId);
            }
            if (_memberGroupTypeService.IsMemberFamily())
            {
                return new ProductPrice(
                    productPrice.Value * (1m - DiscountsConstants.Family / 100m),
                    productPrice.CurrencyId);
            }
            return productPrice;
        }
    }
    

    We have the issue when user add product to basket and move to checkout and then get back to site and log in to get membership discounts. Discounts are applied in our basket but when he proceed back to checkout price is the same as originally added by him without discounts.

    I've tried to use ThawPrices(partialKey: productReference) inside custom OrderLineCalculator:

    public class DisableFreezePriceOrderLineCalculator : OrderLineCalculator
    {
        private readonly IStoreService _storeService;
        private readonly IPriceFreezerService _priceFreezerService;
        private readonly IUmbracoContextAccessor _umbracoContextAccessor;
    
        public DisableFreezePriceOrderLineCalculator(ITaxService taxService, IStoreService storeService, IProductPriceFreezerService productPriceFreezerService,
            IPriceFreezerService priceFreezerService, IUmbracoContextAccessor umbracoContextAccessor)
            : base(taxService, storeService, productPriceFreezerService)
        {
            _storeService = storeService;
            _priceFreezerService = priceFreezerService;
            _umbracoContextAccessor = umbracoContextAccessor;
        }
    
        public override Price CalculateOrderLineUnitPrice(OrderReadOnly order, OrderLineReadOnly orderLine, Guid currencyId, TaxRate taxRate)
        {
            _priceFreezerService.ThawPrices(partialKey: orderLine.ProductReference);
    
            return base.CalculateOrderLineUnitPrice(order, orderLine, currencyId, taxRate);
        }
    }
    

    But it gives me errors:

    {
      "message": "An error has occurred.",
      "exceptionMessage": "ValueFactory attempted to access the Value property of this instance.",
      "exceptionType": "System.InvalidOperationException",
      "stackTrace": "   w System.Lazy`1.CreateValue()\r\n   w System.Lazy`1.LazyInitValue()\r\n   w Vendr.Core.Services.OrderService.<>c.<GetOrderStates>b__23_0(Lazy`1 x)\r\n   w System.Linq.Enumerable.WhereSelectEnumerableIterator`2.MoveNext()\r\n   w System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()\r\n   w System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)\r\n   w System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)\r\n   w Vendr.Core.Services.OrderService.GetOrders(Guid[] ids)\r\n   w Vendr.Core.Events.Domain.Handlers.FrozenPrice.RecalculateOrdersAfterPriceThaw.Handle(FrozenPricesThawed evt)\r\n   w Vendr.Core.Events.Domain.DomainEventHandlerBase`1.Vendr.Core.Events.IEventHandler.Handle(IEvent evt)\r\n   w Vendr.Core.Events.InProcEventDispatcher.Dispatch[T](IEnumerable`1 handlers, T evt)\r\n   w Vendr.Core.Events.EventBus.Dispatch[T](T evt)\r\n   w Vendr.Core.Services.ServiceBase.RaiseDomainEvents(IEnumerable`1 events)\r\n   w Vendr.Core.Services.ServiceBase.RaiseDomainEvents(IDomainEvent[] events)\r\n   w Vendr.Core.Services.ServiceBase.RaiseDomainEvent(IDomainEvent events)\r\n   w Vendr.Core.Services.PriceFreezerService.ThawPrices(Nullable`1 orderId, String partialKey, Nullable`1 currencyId, Nullable`1 countryId, Nullable`1 regiondId, Nullable`1 olderThan)\r\n   w DynamicVines.Vendr.Calculators.DisableFreezePriceOrderLineCalculator.CalculateOrderLineUnitPrice(OrderReadOnly order, OrderLineReadOnly orderLine, Guid currencyId, TaxRate taxRate) w D:\\CogWorks\\Projects\\DYN0264-0898\\Source\\DynamicVines.Vendr\\Calculators\\DisableFreezePriceOrderLineCalculator.cs:wiersz 28\r\n   w Vendr.Core.Pipelines.OrderLine.Tasks.CalculateOrderLineUnitPriceWithoutDiscountsTask.Execute(OrderLineCalculationPipelineArgs args)\r\n   w Vendr.Core.Pipelines.PipelineTaskWithTypedArgsBase`2.Execute(PipelineArgs`1 args)\r\n   w Vendr.Core.Pipelines.PipelineTaskBase`2.Execute(PipelineArgs input)\r\n   w Vendr.Core.Pipelines.InProcPipelineInvoker.<Vendr.Core.Pipelines.IPipelineInvoker.Invoke>g__next|2_0(PipelineArgs e, <>c__DisplayClass2_0& )\r\n   w Vendr.Core.Pipelines.InProcPipelineInvoker.<Vendr.Core.Pipelines.IPipelineInvoker.Invoke>g__next|2_0(PipelineArgs e, <>c__DisplayClass2_0& )\r\n   w Vendr.Core.Pipelines.InProcPipelineInvoker.Vendr.Core.Pipelines.IPipelineInvoker.Invoke(IEnumerable`1 pipelineTasks, PipelineArgs args)\r\n   w Vendr.Core.Pipelines.Pipeline.Invoke[TPipeline,TArgs,TEntity](Func`2 argsFactory)\r\n   w Vendr.Core.Pipelines.Order.Tasks.OrderLinesCalculationPipelineTaskBase`1.Execute(OrderCalculationPipelineArgs args)\r\n   w Vendr.Core.Pipelines.PipelineTaskWithTypedArgsBase`2.Execute(PipelineArgs`1 args)\r\n   w Vendr.Core.Pipelines.PipelineTaskBase`2.Execute(PipelineArgs input)\r\n   w Vendr.Core.Pipelines.InProcPipelineInvoker.<Vendr.Core.Pipelines.IPipelineInvoker.Invoke>g__next|2_0(PipelineArgs e, <>c__DisplayClass2_0& )\r\n   w Vendr.Core.Pipelines.InProcPipelineInvoker.Vendr.Core.Pipelines.IPipelineInvoker.Invoke(IEnumerable`1 pipelineTasks, PipelineArgs args)\r\n   w Vendr.Core.Pipelines.Pipeline.Invoke[TPipeline,TArgs,TEntity](Func`2 argsFactory)\r\n   w Vendr.Core.Pipelines.SubPipelineTaskBase`2.Execute(PipelineArgs`1 input)\r\n   w Vendr.Core.Pipelines.PipelineTaskBase`2.Execute(PipelineArgs input)\r\n   w Vendr.Core.Pipelines.InProcPipelineInvoker.<Vendr.Core.Pipelines.IPipelineInvoker.Invoke>g__next|2_0(PipelineArgs e, <>c__DisplayClass2_0& )\r\n   w Vendr.Core.Pipelines.InProcPipelineInvoker.<Vendr.Core.Pipelines.IPipelineInvoker.Invoke>g__next|2_0(PipelineArgs e, <>c__DisplayClass2_0& )\r\n   w Vendr.Core.Pipelines.InProcPipelineInvoker.<Vendr.Core.Pipelines.IPipelineInvoker.Invoke>g__next|2_0(PipelineArgs e, <>c__DisplayClass2_0& )\r\n   w Vendr.Core.Pipelines.InProcPipelineInvoker.Vendr.Core.Pipelines.IPipelineInvoker.Invoke(IEnumerable`1 pipelineTasks, PipelineArgs args)\r\n   w Vendr.Core.Pipelines.Pipeline.Invoke[TPipeline,TArgs,TEntity](Func`2 argsFactory)\r\n   w Vendr.Core.Calculators.OrderCalculator.CalculateOrder(OrderReadOnly order)\r\n   w Vendr.Core.Persistence.Repositories.OrderRepository.DoProcessDtos(IEnumerable`1 dtos)\r\n   w Vendr.Core.Persistence.Repositories.OrderRepository.DoFetchInternal(IDatabaseUnitOfWork uow, String sql, Object[] args)\r\n   w Vendr.Core.Persistence.Repositories.OrderRepository.Get(Guid id)\r\n   w Vendr.Core.Services.OrderService.<>c__DisplayClass24_0.<GetOrderState>b__1()\r\n   w System.Lazy`1.CreateValue()\r\n--- Koniec śladu stosu z poprzedniej lokalizacji, w której wystąpił wyjątek ---\r\n   w System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   w System.Lazy`1.get_Value()\r\n   w Vendr.Core.Collections.LazyConcurrentDictionary`2.GetOrAddIfNotNull(TKey key, Func`2 valueFactory)\r\n   w Vendr.Core.Services.OrderService.GetOrderState(Guid id, Func`2 factory)\r\n   w Vendr.Core.Services.OrderService.GetOrder(Guid id)\r\n   w Vendr.Core.Session.SessionManager.GetCurrentOrder(Guid storeId, Boolean checkAndMove)\r\n   w Vendr.Core.Session.SessionManager.CheckAndMoveOrderIds(Guid storeId)\r\n   w Vendr.Core.Session.SessionManager.GetCurrentOrder(Guid storeId, Boolean checkAndMove)\r\n   w Vendr.Core.Session.SessionManager.GetCurrentOrder(Guid storeId)\r\n   w Vendr.Core.IProductSnapshotExtensions.CalculatePrice(IProductSnapshot product, ISessionManager session, IProductCalculator calculator, ITaxSourceFactory taxSourceFactory)\r\n   w Vendr.Core.IProductSnapshotExtensions.CalculatePrice(IProductSnapshot product)\r\n   w DynamicVines.Core.Extensions.VendrExtensions.GetProductPrice(IProductSnapshot product) w D:\\CogWorks\\Projects\\DYN0264-0898\\Source\\DynamicVines.Core\\Extensions\\VendrExtensions.cs:wiersz 15\r\n   w DynamicVines.Core.Mappers.ProductMapper.Map(IPublishedContent content, ISearchResult indexResult) w D:\\CogWorks\\Projects\\DYN0264-0898\\Source\\DynamicVines.Core\\Mappers\\ProductMapper.cs:wiersz 65\r\n   w DynamicVines.Core.Mappers.ProductMapper.<Map>b__5_1(<>f__AnonymousType3`2 x) w D:\\CogWorks\\Projects\\DYN0264-0898\\Source\\DynamicVines.Core\\Mappers\\ProductMapper.cs:wiersz 44\r\n   w System.Linq.Enumerable.WhereSelectListIterator`2.MoveNext()\r\n   w System.Linq.Enumerable.WhereEnumerableIterator`1.MoveNext()\r\n   w System.Collections.Generic.List`1..ctor(IEnumerable`1 collection)\r\n   w System.Linq.Enumerable.ToList[TSource](IEnumerable`1 source)\r\n   w DynamicVines.Core.Mappers.ProductMapper.Map(IEnumerable`1 searchResults) w D:\\CogWorks\\Projects\\DYN0264-0898\\Source\\DynamicVines.Core\\Mappers\\ProductMapper.cs:wiersz 42\r\n   w DynamicVines.Core.Builders.ProductListingBuilder.Build(SearchResultDto searchResult) w D:\\CogWorks\\Projects\\DYN0264-0898\\Source\\DynamicVines.Core\\Builders\\ProductListingBuilder.cs:wiersz 19\r\n   w DynamicVines.Web.Controllers.Api.ProductsController.GetWines(String featuredProductsIds, FiltersSearchRequest searchRequest) w D:\\CogWorks\\Projects\\DYN0264-0898\\Source\\DynamicVines.Web\\Controllers\\Api\\ProductsController.cs:wiersz 44\r\n   w DynamicVines.Web.Controllers.Api.ProductsController.GetWines(FiltersSearchRequest searchRequest) w D:\\CogWorks\\Projects\\DYN0264-0898\\Source\\DynamicVines.Web\\Controllers\\Api\\ProductsController.cs:wiersz 50\r\n   w lambda_method(Closure , Object , Object[] )\r\n   w System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ActionExecutor.<>c__DisplayClass6_2.<GetExecutor>b__2(Object instance, Object[] methodParameters)\r\n   w System.Web.Http.Controllers.ReflectedHttpActionDescriptor.ExecuteAsync(HttpControllerContext controllerContext, IDictionary`2 arguments, CancellationToken cancellationToken)\r\n--- Koniec śladu stosu z poprzedniej lokalizacji, w której wystąpił wyjątek ---\r\n   w System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   w System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   w System.Web.Http.Controllers.ApiControllerActionInvoker.<InvokeActionAsyncCore>d__1.MoveNext()\r\n--- Koniec śladu stosu z poprzedniej lokalizacji, w której wystąpił wyjątek ---\r\n   w System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   w System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   w System.Web.Http.Controllers.ActionFilterResult.<ExecuteAsync>d__5.MoveNext()\r\n--- Koniec śladu stosu z poprzedniej lokalizacji, w której wystąpił wyjątek ---\r\n   w System.Runtime.ExceptionServices.ExceptionDispatchInfo.Throw()\r\n   w System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task)\r\n   w System.Web.Http.Dispatcher.HttpControllerDispatcher.<SendAsync>d__15.MoveNext()"
    }
    

    In which place I need to invoke ThawPrice() method or maybe update freezed price to have correct price calculated at checkout level?

  • Matt Brailsford 4125 posts 22222 karma points MVP 9x c-trib
    Jan 29, 2021 @ 17:01
    Matt Brailsford
    0

    Hi Łukasz

    If the thing that affects the discounts is a Member logging in / out, I think a simpler solution rather than replacing the product snapshot would be to put some code in your login / logout logic to fetch the current order and thaw the prices at that point. This way, any change in authentication should trigger a thaw.

    Give that a try and let me know if it still causes an error.

    Matt

  • Łukasz Koruba 30 posts 151 karma points
    Jan 29, 2021 @ 17:24
    Łukasz Koruba
    0

    Matt,

    I'm calling _priceFreezerService.ThawPrices() with order ID as a parameter in our method for handling Login but this looks like doesn't affecting price on Checkout. It stoped throwing an error.

    The reason why we had to override the ProductSnaphot is because we have two price fields for products (trade and retail). That's why we handled those inside ProductSnapshot.

        public void HandleLogin(string email, string password)
        {
            var isLoggedIn = _membershipHelper.Login(email, password);
    
            if (!isLoggedIn)
            {
                return;
            }
    
            var member = _membershipHelper.GetByEmail(email);
    
            using (var uow = _uowProvider.Create())
            {
                var order = _shopService.GetCurrentOrder()
                    .AsWritable(uow)
                    .AssignToCustomer(member.Key.ToString());
    
                _shopService.SaveOrder(order);
    
                _priceFreezerService.ThawPrices(order.Id);
    
                uow.Complete();
            }
        }
    

    Price in basket after succesfully login Price in basket after succesfully login

    Price in the checkout still without discounts Price in the checkout still without discounts

    Any thoughts?

  • Matt Brailsford 4125 posts 22222 karma points MVP 9x c-trib
    Jan 29, 2021 @ 18:39
    Matt Brailsford
    0

    Product snapshots are taken just before adding to an order at which point the values are copied to an order line.

    You might be better off implementing an OrderLineCalculator where you can override the unit price of the order line instead.

    Hope this helps

    Matt

  • Łukasz Koruba 30 posts 151 karma points
    Feb 01, 2021 @ 18:16
    Łukasz Koruba
    100

    I've extracted logic for calculating our prices and using it in ProductSnapshot and OrderLineCalculator. Looks like now there is no price freezing at all cause I'm using Price.Calculate() method and not CalculateOrderLineUnitPrice().

    So price is showing propely on FE and on checkout as well.

    The strange thing is that if Product snapshots are taken just before adding to an order our previous logic should also work because we were returning correct prices from product snapshot.

  • Matt Brailsford 4125 posts 22222 karma points MVP 9x c-trib
    Feb 03, 2021 @ 09:17
    Matt Brailsford
    0

    Hi Łukasz

    You mentioned in your first post that the customer added the product to the cart and THEN logged in. So this will be why your snapshot wasn't updated, because it's only taken the first time the product is added to the cart. If the item is already in the cart, then the frozen price for that product will be used until the cart is cleared of all orderlines using the same product, or the prices are thawed.

    Hope this helps

    Matt

  • Łukasz Koruba 30 posts 151 karma points
    Feb 03, 2021 @ 09:45
    Łukasz Koruba
    0

    Hi Matt,

    ok, that would explain why that was happening. Thanks for pointing me to the right direction.

Please Sign in or register to post replies

Write your reply to:

Draft