Copied to clipboard

Flag this post as spam?

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


  • Marc Ferrold 24 posts 105 karma points
    Jul 15, 2015 @ 10:50
    Marc Ferrold
    0

    Fulfilling Orders

    G'day, hello, ni hao

    So, I have some strange requirements for a project (shops within shops within shops and tiered pricing and "oh my God, shoot me"), but through the magic of Merchello, I've met most of them :)

    I have, however, run into issues when manually generating orders - I have trouble actually fulfilling them, so the stock of a product doesn't change. I can capture payments (Cash/Invoice), but I can't fulfill the order.

    In essence, all that is needed is a button to press in the sale overview, when they've shipped the goods, to mark it and to change the product stock.

    Saleoverview

    Capture Funds works just fine, but I'm missing a fulfillment button. It'd also be neat, if the shipping address would show, but it will always be the same as the billing address, so it's not really important.

    I've taken inspiration from one of Rusty's examples on how to manually generate an order, but attempted to make it suit my needs:

     //Get current customer, or if not found, create new
     var customer = customerService.GetByLoginName(member.Username)
                   ?? customerService.CreateCustomerWithKey(member.Username, model.company, "", model.email);
    
      //To be used for both billing and shipping
      var billingAddress = new Address()
        {
          Name = model.company,
          Address1 = model.address,
          Locality = model.country,
          PostalCode = model.postal
        };                
    
      //Attempt at storing a shipping/billing address. Not really a success.
      var customerBasket = customer.Basket();
      customerBasket.SalePreparation().SaveShipToAddress(billingAddress);
      customerBasket.SalePreparation().SaveBillToAddress(billingAddress);
      customerBasket.PackageBasket(billingAddress);
    
     //If the customer doesn't have any registered addresses
     if (customer.Addresses.Count() <= 0)
     {
       customer.CreateCustomerAddress(billingAddress, model.company, AddressType.Billing);
       customer.CreateCustomerAddress(billingAddress, model.company, AddressType.Shipping);
     }
    
     //Adding adjusted products to the customer's basket. Adjusted meaning: Prices altered in accordance with the tiered pricing
     foreach(var item in basketList)
     {
       customerBasket.AddItem(item.name, item.sku, item.qty, item.price);
     }
    
    //Finalize order
     var paymentMethod = customer.Basket().SalePreparation().GetPaymentGatewayMethods().FirstOrDefault();
     customer.Basket().SalePreparation().AuthorizePayment(paymentMethod);
    

    I've tried to modify it back and forth for a while, to accomodate tax pricing and shipping, though these are a secondary concerns. My main issue is that I'm quite unsure how I would go about "enabling" fulfillment of an order in such a scenario.

    Any assistance would be very much appreciated <3

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Jul 15, 2015 @ 12:52
    Rusty Swayne
    0

    Wow - that's a new one =)

    First, I'm not sure why the shipping address is not showing up. In your checkout, how are you saving the shipping information?

    It think once that gets sorted the Fulfill button will get sorted.

    You can also for the Order creation so that it can be fulfilled by overriding the setting in the merchello.config file:

    <!-- 
    Overrides the Payment Method's IPaymentResponse ApproveOrderCreation indicating an order should always be created no matter
    if the payment has been collected or not. 
    -->
    <setting alias="AlwaysApproveOrderCreation" value="false" />
    
  • Marc Ferrold 24 posts 105 karma points
    Jul 15, 2015 @ 13:10
    Marc Ferrold
    0

    Well, I'm not actually storing much information about the shipping itself - figured I only needed the address, since the end user will eventually decide on how to ship the goods on a case-by-case basis, so that boils down to:

    customerBasket.SalePreparation().SaveShipToAddress(billingAddress);
    

    Or at least, that was the idea behind it - it is a all a little patch work-esque at this point (co-workers have been lost and found, and then lost again), so I am... Mending and making features, to the best of my abilities :D

    I suppose I could create a shipping method that is sort of "empty" somehow, and attach it to the order? Would that be feasible?

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Jul 15, 2015 @ 20:05
    Rusty Swayne
    0

    Yes, you should quote a shipment with a Zero amount and save the shipment rate quote. All of that information gets serialized with the invoice (that's how we can do anonymous checkouts when the customer does not have a permanent record in the db for example).

    The SaveShipToAddress is merely a step to temporarily store the address so it can be used at a later stage in the SalePreparation. It gets disposed with the SalePreparation data once the sale is complete.

  • Marc Ferrold 24 posts 105 karma points
    Jul 16, 2015 @ 11:45
    Marc Ferrold
    0

    I'm terribly sorry, I feel like an idiot, but I have trouble wrapping my brain around how shipping and rates are stored and connected to an order, apparently.

    I have tried following the example and generating rates, if none are found, but the quotes always came back empty, regardless.

    I'm currently at this attempt:

                //Preparing shipment
                var warehouse = warehouseService.GetDefaultWarehouse();
                var shipmentStatus = shipmentService.GetShipmentStatusByKey(Constants.DefaultKeys.ShipmentStatus.Ready);
                var shipment = shipmentService.CreateShipment(shipmentStatus, warehouse.AsAddress(), billingAddress, customerBasket.Items);
    
                //Digging up a ShipCountry
                var catalog = warehouseService.GetAllWarehouseCatalogs().FirstOrDefault();
                var shipCountry = gatewayproviderService.GetShipCountry(catalog.Key, "DK");
    
                var key = Constants.ProviderKeys.Shipping.FixedRateShippingProviderKey;
    
                var method = gatewayproviderService.GetShipMethodsByShipCountryKey(key, shipCountry.Key).FirstOrDefault();
    
                var shipmentRateQuote = shipment.ShipmentRateQuoteByShipMethod(method.Key);
    
                //Clear old ShipmentRateQuotes and save new ones to customer's basket
                customerBasket.SalePreparation().ClearShipmentRateQuotes();
                customerBasket.SalePreparation().SaveShipmentRateQuote(shipmentRateQuote);
    

    But I can't get shipmentRateQuote to populate properly - I feel like I'm misunderstanding something. I've dug up the right shipping method, but it doesn't seem to be connected to the shipment itself, and so the basket doesn't have anything to save.

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Jul 16, 2015 @ 15:47
    Rusty Swayne
    0

    That looks like it should be working (is it throwing any errors?)

    My guess is your looking in the wrong place for the saved values.

    As this is just a quote and not an actual shipment. These are NOT saved in the merchShipment table, rather information is actually serialized in the ExtendedDataCollection of the shipment line item that represents the quote for that shipment. That way, if something changes at no fault of the customer (out of stock, need to ship a back order, split into multiple shipments) the information can be retrieved without having to bother the customer and/or change the sale price and can be completely handled by the warehouse staff.

       if (invoice.ShippingLineItems().Any()) 
       {
          var shipmentLineiTem = invoice.ShippingLineItems().FirstOrDefault();
          var shipment = shippingLineItem.ExtendedData.GetShipment<InvoiceLineItem>();
       }
    
  • Marc Ferrold 24 posts 105 karma points
    Jul 17, 2015 @ 10:31
    Marc Ferrold
    0

    It does return a null exception error on

    customerBasket.SalePreparation().SaveShipmentRateQuote(shipmentRateQuote);
    

    Because shopmentRateQuote ends up being null, which I admittedly don't quite understand, as when I inspect "method", it does return a shipping method I created in the backend, with some dummy rates, so ShipmentRateQuoteByShipMethod() should find some?

    I've kept poking at it, and gotten somewhat past it, through a lot of fiddling.

    //Preparing shipment
    var warehouse = warehouseService.GetDefaultWarehouse();
    var shipmentStatus = shipmentService.GetShipmentStatusByKey(Constants.DefaultKeys.ShipmentStatus.Quoted);
    var shipment = shipmentService.CreateShipment(shipmentStatus, warehouse.AsAddress(), billingAddress, customerBasket.Items);
    
    //Get rates and quotes
    var methods = rateTableProvider.ShipMethods.ToArray(); //Gets "GLS", "Post Danmark (Vary by Weigth)", "Ground (Vary by Weight)"
    var method = rateTableProvider.ShipMethods.Where(x => x.ShipCountryKey == shipCountry.Key).FirstOrDefault(); //Gets "Post Danmark (Vary by Weight)"
    shipment.ShipMethodKey = method.Key;                
    var quote = rateTableProvider.QuoteShipMethodForShipment(shipment, rateTableProvider.GetShippingGatewayMethod(method.Key, shipCountry.Key));                      
    
    ////Create invoice
    var paymentMethod = customer.Basket().SalePreparation().GetPaymentGatewayMethods().FirstOrDefault();
    var invoice = invoiceService.CreateInvoice(Constants.DefaultKeys.InvoiceStatus.Unpaid);
    //Add items to invoice
    List<InvoiceLineItem> invoiceItems = new List<InvoiceLineItem>();
    foreach (var item in customerBasket.Items)
    {
        invoiceItems.Add(new InvoiceLineItem(LineItemType.Product, item.Name, item.Sku, item.Quantity, item.Price, item.ExtendedData));
    }
    
    //Add keys to invoice
    ((Invoice)invoice).CustomerKey = customer.Key;
    invoice.VersionKey = Guid.NewGuid();
    
    //Set billing address
    invoice.SetBillingAddress(billingAddress);
    
    //Update invoice items and total
    foreach (var item in invoiceItems)
    {
        invoice.AddItem(item);
        invoice.Total += item.TotalPrice;
    }
    //Add shipping quotes to invoice
    invoice.Items.Add(quote.AsLineItemOf<InvoiceLineItem>());
    //Save invoice
    invoiceService.Save(invoice);
    var shipmentLineItem = invoice.ShippingLineItems().FirstOrDefault();
    
    invoice.AuthorizePayment(paymentMethod);
    
    var order = invoice.PrepareOrder();
    orderService.Save(order);
    

    An order now goes through, and a shipping address is now present on the order overview. I still have no fulfillment button, however :(

    enter image description here

    Also, for some reason, the Sales Listing now doesn't update when funds are captured - it's always listed as "Unpaid", even though everything seems to go off without a hitch, when I press "Capture Funds". The Capture Funds buttons sticks around as well, but I don't recall if that's abnormal, really.

  • Marc Ferrold 24 posts 105 karma points
    Jul 17, 2015 @ 10:42
    Marc Ferrold
    0

    Okay, so immediately after posting, I realized a face-palming-worthy error: the product's extended data, didn't set it for "shippable", which lead to the missing fulfillment button (I have the lovely little button back now).

    The bug with Capture Funds is still there though, and pressing Fulfill just results in an error, unfortunately: enter image description here

  • Marc Ferrold 24 posts 105 karma points
    Jul 21, 2015 @ 07:33
    Marc Ferrold
    0

    After a bit of poking and prodding, the error seems to arise in /App_Code/Merchello/js/merchello.controller.js, in "openFulfillShipmentDialog()". For some reason,

    shipmentResource.getShipMethodAndAlternatives(request);
    

    appears to fail - the object itself looks empty, and it never gets into the "then" function. Now, I am a regular orangulartang, in that I don't really know angular, but I do know how to alert() stuff for derpy testing, and "request" does seem to get populated with a shipMethodKey, an invoiceKey, and a lineItemKey, so I don't quite understand why it fails, when the request is fully populated.

  • Marc Ferrold 24 posts 105 karma points
    Jul 22, 2015 @ 12:12
    Marc Ferrold
    0

    So, this seems to be the closest I can get at the moment: enter image description here The Capture Payment button works without bugging, and "Fulfill" doesn't throw an error. It doesn't do anything either (at all), but there doesn't seem to be any errors, whatsoever. It seems likely that this is related to the missing Shipping Address.

    I have tried adding a shipping address thusly:

    //Digging up a ShipCountry
     var warehouse = warehouseService.GetDefaultWarehouse();
     var catalog = warehouse.WarehouseCatalogs.FirstOrDefault();
     var shipCountry = gatewayproviderService.GetShipCountry(catalog.Key, warehouse.CountryCode);
     //Preparing shipment                
     var shipmentStatus = shipmentService.GetShipmentStatusByKey(Constants.DefaultKeys.ShipmentStatus.Quoted);
     var shipment = shipmentService.CreateShipment(shipmentStatus, warehouse.AsAddress(), billingAddress, customerBasket.Items);
    
     var key = Constants.ProviderKeys.Shipping.FixedRateShippingProviderKey;
    
     var rateTableProvider = (FixedRateShippingGatewayProvider)MerchelloContext.Current.Gateways.Shipping.GetProviderByKey(key);
     rateTableProvider.ExtendedData.SetValue(Constants.ExtendedDataKeys.CurrencyCode, "DKK");
    
     // If no shipping methods are found, add a new one
     if (!rateTableProvider.ShipMethods.Any())
     {
       // creates the rate table for ship rate quotes
       var gwShipMethod = (FixedRateShippingGatewayMethod)rateTableProvider.CreateShipMethod(FixedRateShippingGatewayMethod.QuoteType.VaryByWeight, shipCountry, "Post Danmark (Vægtbaseret)");
       gwShipMethod.RateTable.AddRow(0, 10000, 0);
       rateTableProvider.SaveShippingGatewayMethod(gwShipMethod);
     }
    
     IShipMethod shipmethod = rateTableProvider.ShipMethods.Where(x => x.ShipCountryKey == shipCountry.Key).FirstOrDefault();
     IShipmentRateQuote quote = rateTableProvider.QuoteShipMethodForShipment(shipment, rateTableProvider.GetShippingGatewayMethod(shipmethod.Key, shipCountry.Key));
     InvoiceLineItem quoteItem = quote.AsLineItemOf<InvoiceLineItem>();
     quoteItem.ExtendedData.AddShipment(shipment);
     quoteItem.ExtendedData.SetValue(Constants.ExtendedDataKeys.CurrencyCode, "DKK");
     quoteItem.ExtendedData.AddAddress(billingAddress, AddressType.Billing);
     quoteItem.ExtendedData.AddAddress(billingAddress, AddressType.Shipping);
    
     customerBasket.Items.Add(quoteItem);                
    
     //Finalize order
     var paymentMethod = customerBasket.SalePreparation().GetPaymentGatewayMethods().FirstOrDefault();
     CustomerBasket.SalePreparation().AuthorizePayment(paymentMethod);
    

    However, whenever I do

        customerBasket.Items.Add(quoteItem);
    

    the backend bugs out again, not showing the entire order and loading endlessly, with an error: enter image description here

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Jul 23, 2015 @ 14:01
    Rusty Swayne
    0

    @Marc - the shipping address needs to have at very least a country code so that the ship method can be determined.

    In your error message, I see it is failing to find the currency symbol, which would indicate that the Currency Code is not present in the extended data in the invoice line items.

    In a standard checkout, there is a validation task that runs the following code:

     var unTagged = value.Items.Where(x => !x.ExtendedData.ContainsKey(Constants.ExtendedDataKeys.CurrencyCode)).ToArray();
    
            if (unTagged.Any())
            {
                var defaultCurrency =
                    SalePreparation.MerchelloContext.Services.StoreSettingService.GetByKey(
                        Constants.StoreSettingKeys.CurrencyCodeKey);
    
                foreach (var item in unTagged)
                {
                    item.ExtendedData.SetValue(Constants.ExtendedDataKeys.CurrencyCode, defaultCurrency.Value);
                }
            }
    
            var allCurrencyCodes =
                value.Items.Select(x => x.ExtendedData.GetValue(Constants.ExtendedDataKeys.CurrencyCode)).Distinct();
    
            return 1 == allCurrencyCodes.Count()
                       ? Attempt<IInvoice>.Succeed(value)
                       : Attempt<IInvoice>.Fail(new InvalidDataException("Invoice is being created with line items costed in different currencies."));
    

    This ensures that each line has a currency code set AND that all currency codes within the invoice are consistent. Merchello does not allow for multiple currencies in a single invoice - apply taxes to something like that would be an absolute nightmare =).

  • Marc Ferrold 24 posts 105 karma points
    Jul 24, 2015 @ 06:16
    Marc Ferrold
    0

    Ah, I thought as much, which is why I tried just throwing CurrencyCodes at it wherever I could :P I did try

    foreach (var item in basketList)
    {
        ExtendedDataCollection extendedData = new ExtendedDataCollection();
        JObject JExtendedData = (JObject)JsonConvert.DeserializeObject(item.extendedData);
        foreach (var JItem in JExtendedData)
        {
            extendedData.TryAdd(JItem.Key, JItem.Value.ToString());
        }
        if (!extendedData.Keys.Contains(Constants.ExtendedDataKeys.CurrencyCode.ToString()))
        {
            extendedData.SetValue(Constants.ExtendedDataKeys.CurrencyCode, "DKK");
        }    
        customerBasket.AddItem(item.name, item.sku, item.qty, item.price, extendedData);
    }
    

    To confirm that every product I add (this excludes shipping atm though) has a CurrencyCode - I've added one to the quote (AsLineItemOf

    It seems a little weird though, because it doesn't break until I add my "quoteItem", and that has a mindlessly hard coded

    quoteItem.ExtendedData.SetValue(Constants.ExtendedDataKeys.CurrencyCode, "DKK");
    

    I have kind of started over on the project entirely, so that it will all be my own code and logic, in stead of doing patchwerk fixing. If I can get it done on time, and if I'm allowed to by the end of this, I'm considering uploading it somewhere, just so examples of crazy stuff exists.

    I hear tiered pricing is on its way in Merchello, but in this case, it's achieved by adding an Archetype to the product nodes and updating the prices accordingly as they're added to the basket - serializing and deserializing a Json object to pass along a dictionary of tiers and prices. I have a sneaking suspicion that this might have been where this project broke - juggling "real" and "artificially constructed" products and prices, making them lose ExtendedData at one point or another.

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Jul 24, 2015 @ 14:32
    Rusty Swayne
    0

    @Marc - Your project sounds really interesting and you've had to handle quite a few difficult requirements.

    I think you're correct that it is the shipping line item and, assuming your quoteItem is the shipping line item, it looks like you are adding it correctly.

    I agree that the currency codes are set a bit weird. I just have not had a chance to build out a concept to pretty up that aspect of the API and have the problem that I don't want to break any shipping provider someone has coded as a plugin.

    I'm off for the weekend, but would like to look at your scenario more closely - would you have time to setup a Skype call next week?

  • Marc Ferrold 24 posts 105 karma points
    Jul 27, 2015 @ 07:07
    Marc Ferrold
    0

    Yeah, that'd be cool - after starting over on the project, I think I've gotten a more solid structure up and running than I had before, but completing orders is still quite tricky with my limited experience. I reckon I can take a day off work during the week to fiddle with it at home and take a call for a while. I have to hand over the project on Thursday, but I should be able to manage either Tuesday or Wednesday. But if that doesn't work for you, it'd still be interesting to get it finished afterwards, so Friday or after is also great.

  • Rusty Swayne 1655 posts 4993 karma points c-trib
    Jul 27, 2015 @ 15:26
    Rusty Swayne
    101

    I'm around all week. Ping me on twitter and we can schedule something from there.

  • Marc Ferrold 24 posts 105 karma points
    Jul 30, 2015 @ 13:24
    Marc Ferrold
    2

    So after a Skype session with Rusty, the problem turned out to be some missing extended data in my products, which was lost when they were added to the basket, due to the tiered pricing. This is fixed now, and I have made a post about it here - Tiered Pricing.

    Thanks again to Rusty for helping.

Please Sign in or register to post replies

Write your reply to:

Draft