Copied to clipboard

Flag this post as spam?

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


  • Warren Harding 132 posts 275 karma points
    Aug 10, 2021 @ 09:12
    Warren Harding
    0

    Displaying Variant options and adjusting the price

    Hi there - new to this and trying to figure it out, was wondering if you could guide me on how to present the variant option on the front end?

    If I have an attribute called 'colour' which consists of an sku and priceAdjustment (does that type need to be a Vendr Price?) and then on the product I have created the variants 'black' and 'white' and provided the sku and price there... how should I then pull in this data in the front-end to create the 'select a colour' option as a dropdown, and then add that adjustment to the product price in order to add it to the cart?

    Further to this, how could I also add a custom property value related to this variant when adding to the order? is there a way to pull in the selected variant alias - in this case 'colourblack' - and then assign something like:

    order.SetProperty("colourBlackWidth", "120mm");

    Thanks for your help!

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Aug 10, 2021 @ 09:29
    Matt Brailsford
    1

    Hey Warren,

    You can see an example of one way to do it within our demo store code at https://github.com/vendrhub/vendr-demo-store

    If you take a look at the ProductPage.cshtml there are a few bits in there.

    If you look here you can see how we render our dropdowns for each attribute

    https://github.com/vendrhub/vendr-demo-store/blob/main/src/Vendr.DemoStore.Web/Views/ProductPage.cshtml#L133-L149

    Toggling a dropdown triggers a JS method which fetches product variant details which is here

    https://github.com/vendrhub/vendr-demo-store/blob/main/src/Vendr.DemoStore.Web/Views/ProductPage.cshtml#L37-L74

    Then the corresponding API controller which fetches and returns a given variants info can be found here

    https://github.com/vendrhub/vendr-demo-store/blob/main/src/Vendr.DemoStore/Web/Controllers/ProductApiController.cs#L25-L57

    Essentially what this is doing is, when you toggle a variant dropdown it fetches variant info for that combination, then JS reads that data and updates the UI accordingly. A few important bits are:

    1. Data attributes rendered on the UI elements we want to update from JS
    2. In the JS where we update the UI, make sure to update the productVariantReference input field.

    Beyond this, you can tweak it to how you need. If you don't have a lot of variants, you could render all the variant combinations to a JS variable to prevent the need of the API call. Of you can update the API controller to return more or less data to update the UI.

    RE a priceAdjustment property on your vairant, yes, this needs to be a Vendr: Price input field. When you add the parent product productReference and the variant productVariantReference to the order, Vendr will automatically work out and apply the price based upon the parent product price and the price adjustment on the variant.

    RE the property, you might be better storing that as an order line property, rather than an order property. That way the width is explicitly for that given order line, which is a given attribute combination and you wouldn't need to prefix it.

    Hope this helps

    Matt

  • Warren Harding 132 posts 275 karma points
    Aug 10, 2021 @ 09:48
    Warren Harding
    0

    Awesome thanks for that, will have a play around and see how that goes.

  • Warren Harding 132 posts 275 karma points
    Aug 10, 2021 @ 09:59
    Warren Harding
    0

    One more thing - is there any potential issue having two Vendr: Price data types in a document type? As in having one for a 'minimum price' and the other for a 'price per square metre'.

    If the width is not specificed then the 'minimum price' would apply (the variable for which it is just named 'price' for the default), however if the width is specified then I will calculate the price, that will involve instantiating a custom IPriceAdjuster correct?

    Thank again!

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Aug 10, 2021 @ 10:02
    Matt Brailsford
    0

    Hi Warren,

    Shouldn't be a problem of having multiple price fields. Vendr is only interested in one with an alias of price (see here for required property aliases https://vendr.net/docs/core/1.8.0/key-concepts/umbraco-properties/).

    RE calculating the price, yea, you could either use a custom adjuster, or replace the OrderLineCalculator ( https://vendr.net/docs/core/1.8.0/key-concepts/calculators/ )

    Matt

  • Warren Harding 132 posts 275 karma points
    Aug 13, 2021 @ 05:55
    Warren Harding
    0

    Thank you. I nearly jumped on another closed thread again... so will ask here as well if I may...

    https://our.umbraco.com/packages/website-utilities/vendr/vendr-support/105253-dynamic-price-from-api-for-products#comment-328311

    Should I be adding the property to the orderline in the AddToCart method - seems to be the logical place, but from the examples there doesn't appear to be OrderLines yet. Any chance you could point me to an example of this?

    Thanks again!

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Aug 13, 2021 @ 08:49
    Matt Brailsford
    100

    Hi Warren

    Ok, I'm trying to piece this all together as it's not really one succinct question right now, but I'm assuming you have a product that uses the variants prop editor and for each variant you can provider a price and a pricePerSqMetre for. Then, when you add a variant to your order, you want to be able to provide a sqMetre property stored against the order line. Then, when calculating the price for the order line, if it has a sqMetre property defined, then use that and multiply by the variants pricePerSqMetre to define it's price, otherwise, if a sqMetre property isn't defined, then just use the default price.

    So based on this assumption, and assuming your properties are named as per the above, you'd probably need to do the following in your add to cart

    // DTO to hold posted values so you should be posting these values back
    // from your HTML
    public class AddToCartDto
    {
        public string ProductReference { get; set; }
        public string ProductVariantReference { get; set; }
        public string SqMetre { get; set; }
    }
    
    [HttpPost]
    public ActionResult AddToCart(AddToCartDto postModel)
    {
        try
        {
            using (var uow = _uowProvider.Create())
            {
                var store = CurrentPage.GetStore();
                var order = _sessionManager.GetOrCreateCurrentOrder(store.Id)
                    .AsWritable(uow)
                    .AddProduct(postModel.ProductReference, postModel.ProductVariantReference, 1, new Dictionary<string, string> {
                        { "sqMetre": postModel.SqMetre }
                    });
    
                _orderService.SaveOrder(order);
    
                uow.Complete();
            }
        }
        catch (ValidationException ex)
        {
            ModelState.AddModelError("productReference", "Failed to add product to cart");
    
            return CurrentUmbracoPage();
        }
    
        return RedirectToCurrentUmbracoPage();
    }
    

    You'll then need to implement a custom IOrderLineCalculator inheriting from the default order line calculator

    public class SqMetreOrderLineCalculator : OrderLineCalculator
    {
        private readonly IPublishedContentQuery _publishedContentQuery;
    
        public OrderLineCalculator(ITaxService taxService,
                IStoreService storeService,
                IProductPriceFreezerService productPriceFreezerService,
                IPublishedContentQuery publishedContentQuery)
            : base(taxService, storeService, productPriceFreezerService)
        { 
            _publishedContentQuery = publishedContentQuery;
        }
    
        public override Price CalculateOrderLineUnitPrice(OrderReadOnly order, OrderLineReadOnly orderLine, Guid currencyId, TaxRate taxRate)
        {
            // See if the order line has a sqMetre property
            var sqMetre = int.Parse("0"+ orderLine.Properties["sqMetre"]);
            if (sqMetre != 0) {
    
                // Get the product + variant ID's from the order line
                var productKey = Guid.Parse(orderLine.ProductReference);
                var variantKey = Guid.Parse(orderLine.ProductVariantReference);
    
                // Get the product + variant Umbraco nodes, typed to their Models Builder types
                var productNode = _publishedContentQuery.Content(productKey) as ProductPage; // Assumes you have a models builder model called ProductPage
                var variantNode = productNode.Variants[variantKey].Content as ProductVariant;  // Assumes you have a models builder model called ProductVariant
    
                // Fetch a per sq metre price for the given currency
                var price = variantNode.PricePerSqMetre.GetPriceFor(currencyId);
                if (price != null && price.Value > 0) 
                {
                    // Calculate the unit price as the price per sq metre * the defined sq metre
                    return Price.Calculate(price.Value * sqMetre, taxRate, currencyId);
                }
            }
    
            // No sq metre property found on the order line so use normal calculation process
            // which will ultimately fallback to using the variants `price` property
            return base.CalculateOrderLineUnitPrice(order, orderLine, currencyId, taxRate);
        }
    }
    

    Which you'll need to override the default implementation in a composer

    [ComposeAfter(typeof(VendrWebComposer))]
    public class Composer : IUserComposer
    {
        public void Compose(Composition composition)
        {
            composition.RegisterUnique<IOrderLineCalculator, SqMetreOrderLineCalculator>();
        }
    }
    

    I've just written this stuff of the top of my head so I can't guarantee it'll work, but it should be the basic outline so you'll need to try it and debug anything that isn't quite right.

    Matt

  • Warren Harding 132 posts 275 karma points
    Aug 13, 2021 @ 09:14
    Warren Harding
    0

    Amazing man! Thanks so much - will have a play and see

  • Warren Harding 132 posts 275 karma points
    Aug 14, 2021 @ 07:13
    Warren Harding
    0

    This works initially, but then when I navigate through the checkout from 'Information' to 'Shipping Method' I get a YSOD from the following line:

    var productNode = _publishedContentQuery.Content(productKey) as Product; 
    

    The error I'm seeing is:

    System.ObjectDisposedException: Cannot access a disposed object.
    Object name: 'snapshot'.
    at Umbraco.Web.PublishedCache.NuCache.ContentStore.Snapshot.Get(Guid id) in D:\a\1\s\src\Umbraco.Web\PublishedCache\NuCache\ContentStore.cs:line 1599
    at Umbraco.Web.PublishedCache.NuCache.ContentCache.GetById(Boolean preview, Guid contentId) in D:\a\1\s\src\Umbraco.Web\PublishedCache\NuCache\ContentCache.cs:line 232
    at Umbraco.Web.PublishedCache.PublishedCacheBase.GetById(Guid contentId) in D:\a\1\s\src\Umbraco.Web\PublishedCache\PublishedCacheBase.cs:line 28
    at Umbraco.Web.PublishedContentQuery.Content(Guid id) in D:\a\1\s\src\Umbraco.Web\PublishedContentQuery.cs:line 50
    at AtlasLogic.Extensions.SqMetreOrderLineCalculator.CalculateOrderLineUnitPrice(OrderReadOnly order, OrderLineReadOnly orderLine, Guid currencyId, TaxRate taxRate) in C:\Projects\proto\ProtoWeb\AtlasLogic\Extensions\SqMetreOrderLineCalculator.cs:line 38
    

    I'm a bit lost here - can you assist further please? Thank you

  • Matt Brailsford 4124 posts 22215 karma points MVP 9x c-trib
    Aug 14, 2021 @ 10:22
    Matt Brailsford
    100

    Ok, you could probably try swapping the IPublishedContentQuery to a IUmbracoContextFactory instead, and then wrap the inside of the if statement of the CalculateOrderLineUnitPrice with a

    using(var ctx = _umbracoContextFactory.EnsureUmbracoContext()) 
    {
        ...
    }
    

    then swap the call to _publishedContentQuery.Content(productKey) for ctx.ContentCache.GetById(false, productKey). I think this should work.

    Matt

  • Warren Harding 132 posts 275 karma points
    Aug 16, 2021 @ 10:30
    Warren Harding
    0

    Perfect! Thank you

Please Sign in or register to post replies

Write your reply to:

Draft