Sunday, January 29, 2017

Extending EPiServer Commerce Classes

In EPiServer we can extend the existing Commerce Classes. Such as PurchaseOrder, OrderForm and LineItem.

We can use this extended class to use meta class property as class property as oppose to calling object as indexer with hard coded property name.
for e.g


var orderForm = orderGroup.OrderForms[0]
var stockRoomId = orderForm["StockRoomId"]

/* We can use extended class to get value of stock room id */

var tcmsOrderForm = orderGroup.OrderForms[0] as TcmsOrderForm
var stockRoomId = tcmsOrderForm.StockRoomId;



First all of we need to define create the custom meta class and custom meta property in Commerce Section.

We will assign the associated meta property to meta classes.

We will have following custom classes

  • TcmsOrderForm
  • TcmsLineItem

We will also have following meta properties(Not all are included)

  • StockRoomId
  • PeriodId
  • UnitOfMeasure
  • IsPrescribed

Meta properties  will need to be assigned to associated Meta classes in Commerce section.
Once we have meta property and meta classes relation defined we are ready to create extended commerce classes.

Extended Purchase Order (TcmsPurchaseOrder)

using Mediachase.Commerce.Orders;
using System;
using System.Data;
using System.Linq;
using EPiServer7App.EPiServerCore.Utilities;

namespace EPiServer7App.Commerce.MetaFields.Orders
{
    /// <summary>
    /// Extended purchase order class which stores the date sent and date actioned
    /// </summary>
    [Serializable]
    public class TcmsPurchaseOrder : PurchaseOrder
    {
        public const string MetaClass_Name = "PurchaseOrder";

        public const string MetaField_DateActioned = "DateActioned";
        public const string MetaField_DateSent = "DateSent";
        public const string MetaField_OrderCreateLastStep = "OrderCreateLastStep";
        public const string MetaField_TrackingNumberIsFacilityGenerated = "TrackingNumberIsFacilityGenerated";

        #region Constructors

        /// <summary>
        /// Initializes a new instance of the <see cref="TcmsPurchaseOrder"/> class.
        /// </summary>
        public TcmsPurchaseOrder()
        {
            InitializeCustomFields();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TcmsOrderForm"/> class.
        /// </summary>
        /// <param name="reader">The reader.</param>
        public TcmsPurchaseOrder(IDataReader reader)
            : base(reader)
        {
            InitializeCustomFields();
        }


        #endregion

        /// <summary>
        /// Initializes this instance.
        /// </summary>
        protected void InitializeCustomFields()
        {
            DateActioned = null;
            DateSent = null;
            OrderCreateLastStep = Enums.OrderCreateStep.StockTake;
            TrackingNumberIsFacilityGenerated = false;
        }

        /// <summary>
        /// Gets or sets the date actioned.
        /// </summary>
        /// <value>
        /// The date actioned.
        /// </value>
        public DateTime? DateActioned
        {
            get { return this.GetDateTimeValue(MetaField_DateActioned, null); }
            set { this[MetaField_DateActioned] = value; }
        }


        /// <summary>
        /// Gets or sets the date sent.
        /// </summary>
        /// <value>
        /// The date sent.
        /// </value>
        public DateTime? DateSent
        {
            get { return this.GetDateTimeValue(MetaField_DateSent, null); }
            set { this[MetaField_DateSent] = value; }
        }

        /// <summary>
        /// Gets or sets the order create last step.
        /// </summary>
        /// <value>
        /// The order create last step.
        /// </value>
        public Enums.OrderCreateStep OrderCreateLastStep
        {
            get { return (Enums.OrderCreateStep) this.GetIntWithDefaultValue(MetaField_OrderCreateLastStep, (int) Enums.OrderCreateStep.StockTake); }
            set { this[MetaField_OrderCreateLastStep] = (int) value; }
        }

        public bool IsEditable
        {
            get
            {
                var nonEditableStatus = new[]
                {
                    Enums.TcmsOrderStatus.Completed,
                    Enums.TcmsOrderStatus.SubmittedToDistributor,
                    Enums.TcmsOrderStatus.Cancelled
                };

                return !nonEditableStatus.Contains(EnumHelper.ParseOrDefault<Enums.TcmsOrderStatus>(Status));
            }
        }

        /// <summary>
        /// Gets or sets if the tracking number (purchase order number) has been set by the facility as
        /// opposite of being system generated.
        /// </summary>
        public bool TrackingNumberIsFacilityGenerated
        {
            get { return ((bool?) this[MetaField_TrackingNumberIsFacilityGenerated]).GetValueOrDefault(false);}
            set { this[MetaField_TrackingNumberIsFacilityGenerated] = value; }
        }
    }
}

Extended Order Form (TcmsOrderForm)

using System;
using System.Data;
using System.Linq;
using System.Runtime.Serialization;
using EPiServer7App.EPiServerCore.Utilities;
using Mediachase.Commerce.Orders;
using Mediachase.Commerce.Storage;

namespace EPiServer7App.Commerce.MetaFields.Orders
{
    /// <summary>
    /// Extended version of the OrderForm
    /// </summary>
    [Serializable]
    public class TcmsOrderForm : OrderForm
    {
        public const string MetaClass_Name = "OrderFormEx";

        public const string MetaField_StockRoomId = "StockRoomId";
        public const string MetaField_PeriodId = "PeriodId";
        public const string MetaField_Comments = "Comments";

        #region Constructors
        public TcmsOrderForm()
        {
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TcmsOrderForm"/> class.
        /// </summary>
        /// <param name="reader">The reader.</param>
        public TcmsOrderForm(IDataReader reader)
            : base(reader)
        {
            InitializeCustomFields();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TcmsOrderForm"/> class.
        /// </summary>
        /// <param name="info">The information.</param>
        /// <param name="context">The context.</param>
        protected TcmsOrderForm(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
            InitializeCustomFields();
        } 
        #endregion

        /// <summary>
        /// Initializes this instance.
        /// </summary>
        protected void InitializeCustomFields()
        {
            StockRoomId = Guid.Empty;
            PeriodId = Guid.Empty;
        }

        /// <summary>
        /// Gets or sets the stock room identifier.
        /// </summary>
        /// <value>
        /// The stock room identifier.
        /// </value>
        public Guid StockRoomId
        {
            get { return this.GetGuidValue(MetaField_StockRoomId, Guid.Empty).Value; }
            set { this[MetaField_StockRoomId] = value; }
        }


        /// <summary>
        /// Gets or sets the period identifier.
        /// </summary>
        /// <value>
        /// The period identifier.
        /// </value>
        public Guid PeriodId
        {
            get { return this.GetGuidValue(MetaField_PeriodId, Guid.Empty).Value; }
            set { this[MetaField_PeriodId] = Convert.ToString(value); }
        }

        /// <summary>
        /// Gets or sets the comments.
        /// </summary>
        /// <value>
        /// The comments.
        /// </value>
        public string Comments
        {
            get { return GetString(MetaField_Comments); }
            set { this[MetaField_Comments] = value; }
        }

        protected override void PopulateCollections(DataTableCollection tables, string filter)
        {
            filter = string.Format("OrderFormId = '{0}'", OrderFormId);
            base.PopulateCollections(tables, filter);            
        }

    }
}

Extended Line Item (TcmsLineItem)

using System;
using System.Runtime.Serialization;
using Mediachase.Commerce.Orders;

namespace EPiServer7App.Commerce.MetaFields.Orders
{
    /// <summary>
    /// Extended class for LineItem with TCMS specific values
    /// </summary>
    public class TcmsLineItem : LineItem
    {
        public const string MetaClass_Name = "LineItemEx";

        public const string MetaField_UnitOfMeasure = "UnitOfMeasure";
        public const string MetaField_ActualCatalogEntryId = "ActualCatalogEntryId";
        public const string MetaField_IsPrescribed = "IsPrescribed";
        
        #region Constructors
        /// <summary>
        /// Initializes a new instance of the <see cref="TcmsLineItem"/> class.
        /// </summary>
        public TcmsLineItem()
        {
            Initialize();
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="TcmsLineItem"/> class.
        /// </summary>
        /// <param name="info">The information.</param>
        /// <param name="context">The context.</param>
        protected TcmsLineItem(SerializationInfo info, StreamingContext context)
            : base(info, context)
        {
            Initialize();
        } 
        #endregion

        /// <summary>
        /// Initializes this instance with default values
        /// </summary>
        /// <remarks>The base class doesn't expose this as virtual. Sigh!</remarks>
        protected virtual void Initialize()
        {
            UnitOfMeasure = Enums.UnitOfMeasure.Carton;
            IsPrescribed = false;
        }

        /// <summary>
        /// Gets or sets the unit of measure.
        /// </summary>
        /// <value>
        /// The unit of measure.
        /// </value>
        public Enums.UnitOfMeasure UnitOfMeasure
        {
            get { return (Enums.UnitOfMeasure)GetInt(MetaField_UnitOfMeasure); }
            set { this[MetaField_UnitOfMeasure] = (int)value; }
        }
     
        /// <summary>
        /// Gets or sets a value indicating whether [is prescribed].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [is prescribed]; otherwise, <c>false</c>.
        /// </value>
        public bool IsPrescribed
        {
            get { return GetBool(MetaField_IsPrescribed); }
            set { this[MetaField_IsPrescribed] = value; }
        }      
    }
}



ecf.order.config

We also need to update the ecf.order.config section to include the custom class definition

<?xml version="1.0"?>
<Orders newOrderStatus="Draft" autoConfigure="true" shipmentAutoReleaseTimeout="1:0:0">
  <MappedTypes>
    <ShoppingCartType name="Mediachase.Commerce.Orders.Cart,Mediachase.Commerce" />
    
    <!-- Custom Lines -->
    <PurchaseOrderType name="EPiServer7App.Commerce.MetaFields.Orders.TcmsPurchaseOrder,EPiServer7App.Commerce" />
    <OrderFormType name="EPiServer7App.Commerce.MetaFields.Orders.TcmsOrderForm,EPiServer7App.Commerce" />
    <LineItemType name="EPiServer7App.Commerce.MetaFields.Orders.TcmsLineItem,EPiServer7App.Commerce" />    
    <!-- END Custom Lines-->
    
    <!-- ORIGINAL LINES -->
    
    <!--
    <PurchaseOrderType name="Mediachase.Commerce.Orders.PurchaseOrder,Mediachase.Commerce" />
    <OrderFormType name="Mediachase.Commerce.Orders.OrderForm,Mediachase.Commerce" />
    <LineItemType name="Mediachase.Commerce.Orders.LineItem,Mediachase.Commerce" />
    -->

    <PaymentPlanType name="Mediachase.Commerce.Orders.PaymentPlan,Mediachase.Commerce" />    
    <OrderGroupAddressType name="Mediachase.Commerce.Orders.OrderAddress,Mediachase.Commerce" />
    <ShipmentType name="Mediachase.Commerce.Orders.Shipment,Mediachase.Commerce" />
    <OrderSearchType name=" Mediachase.Commerce.Orders.Search.OrderSearch,Mediachase.Commerce" />
  </MappedTypes>
  <MetaClasses>
    <PurchaseOrderClass name="PurchaseOrder" />
    <PaymentPlanClass name="PaymentPlan" />
    <ShoppingCartClass name="ShoppingCart" />]
    <OrderFormClass name="OrderFormEx" />
    <LineItemClass name="LineItemEx" />    
    <ShipmentClass name="ShipmentEx" />
    <OrderAddressClass name="OrderGroupAddressEx" />
  </MetaClasses>
  <Connections confConnectionStringName="EcfSqlConnection" transConnectionStringName="EcfSqlConnection" />
  <Cache enabled="true" shippingCollectionTimeout="0:0:10" paymentCollectionTimeout="0:0:10" statusCollectionTimeout="0:0:10" countryCollectionTimeout="0:0:10" taxCollectionTimeout="0:0:10" jurisdictionCollectionTimeout="0:0:10" />
  <Roles>
    <add name="OrderSupervisorRole" value="Order Supervisor" />
    <add name="OrderManagerRole" value="Order Manager" />
    <add name="ShippingManagerRole" value="Shipping Manager" />
    <add name="ReceivingManagerRole" value="Receiving Manager" />
  </Roles>
</Orders>

After defining custom classes and configuration we are now ready to use the classes
Below are the utility functions and example to use the above classes.


var purchaseOrder = new TcmsPurchaseOrder { TrackingNumber = "DummyTrackingNumber" };

var orderForm = purchaseOrder.OrderForms.AddNew() as TcmsOrderForm;
    
orderForm.LineItems.Add(new TcmsLineItem()
  {
       Quantity = 1,
       ActualCatalogEntryId = 24,
       ExtendedPrice = (decimal)20.30,
       IsPrescribed = true,
       UnitOfMeasure = Enums.UnitOfMeasure.Carton,
       StockTakeQuantityInPieces = 12,
  });
     
     
  //Assumes that we're only using one form!
  return orderGroup.OrderForms[0] as TcmsOrderForm;

Extension method to convert normal classes to extended classes

using System.Collections.Generic;
using EPiServer7App.Commerce.MetaFields.Orders;
using Mediachase.Commerce.Orders;

namespace EPiServer7App.Commerce.Services.Extensions
{
    /// <summary>
    /// Creates extension methods for order group implementations such as Cart and Purchase Order
    /// </summary>
    public static class OrderGroupExtensions
    {
        /// <summary>
        /// Gets the TCMS order form.
        /// </summary>
        /// <param name="orderGroup">The order group.</param>
        /// <returns></returns>
        public static TcmsOrderForm GetTcmsOrderForm(this OrderGroup orderGroup)
        {
            if (orderGroup == null)
                return null;

            if (orderGroup.OrderForms.Count < 1)
                return null;

            //Assumes that we're only using one form!
            return orderGroup.OrderForms[0] as TcmsOrderForm;
        }

        /// <summary>
        /// To the TCMS order form.
        /// </summary>
        /// <param name="orderForm">The order form.</param>
        /// <returns></returns>
        public static TcmsOrderForm ToTcmsOrderForm(this OrderForm orderForm)
        {
            if (orderForm == null)
                return null;

            return orderForm as TcmsOrderForm;
        }

        /// <summary>
        /// To the TCMS line item.
        /// </summary>
        /// <param name="lineItem">The line item.</param>
        /// <returns></returns>
        public static TcmsLineItem ToTcmsLineItem(this LineItem lineItem)
        {
            if (lineItem == null)
                return null;

            return lineItem as TcmsLineItem;
        }
    }
}



Monday, January 23, 2017

Adding EPiServer Meta Property through Code And Assigning it to MetaClass

In this post, we will go through the feature to add meta property to OrderForm, LineItem and CredtiCardPayment class.

We are going to use MetaClass and MetaField class of Mediachase.MetaDataPlus to add/update property to class

 

var metaLineItemClass = MetaClass.Load(MetaDataContext.Instance, "LineItemEx")


The above function will return the instance of LineItemEx  class. Using this instance you can add meta field to give meta class.

 

var metaProp = MetaField.Load(MetaDataContext.Instance, "GiftVoucherCode")



The above function will return the instance of field "GiftVoucherCode" meta property. Do not forget, MetaField are unique and can only be one created but can be assigned to any meta class.


 
if (metaProp == null)
                metaProp = MetaField.Create(MetaDataContext.Instance, "Mediachase.Commerce.Orders", "Gift Voucher Code", "Gift Voucher Code", "Gift Voucher Code", MetaDataType.ShortString, 100, true, false, false, false);
if (!metaLineItemClass.MetaFields.Contains(metaProp))
                metaLineItemClass.AddField(metaProp);


The above piece of code first checks if the meta field is already created. If not then it creates one.
It also checks if the meta field is already associated with meta class then it ignores it else assigns the meta field to meta class using AddField function in meta class.

Below is the complete code that I have used in my project.

We will have class MetaClassAndFieldsSyncInitializer which will add property to meta class.


 using System;
using System.Collections.Generic;
using System.Linq;
using EPiServer.Reference.Commerce.Shared.Settings;
using Mediachase.MetaDataPlus;
using Mediachase.MetaDataPlus.Configurator;

namespace EPiServer.Reference.Commerce.Shared.Infrastructure.Initialization
{
    public class MetaClassAndFieldsSyncInitializer
    {
        private readonly MetaDataContext _metaDataContext;
        private const string PRODUCT_TYPE = "ProductType";
        private const string LINE_ITEM_EX = "LineItemEx";
        private const string ORDER_FORM_EX = "OrderFormEx";
        private const string ORDER_GROUP_ADDRESS_EX = "OrderGroupAddressEx";
        private const string ORDER_CREDIT_CARD_PAYMENT = "CreditCardPayment";


        private List<string> _productTypes = Enum.GetNames(typeof(Enums.ProductType)).ToList();
            
        public MetaClassAndFieldsSyncInitializer(MetaDataContext metaDataContext)
        {
            if (metaDataContext == null)
                throw new ArgumentNullException("metaDataContext");
            _metaDataContext = metaDataContext;
        }

        public void Sync()
        {
            AddBaseProductProductType();
            AddLineItemFields();
            AddOrderFormFields();
            AddOrderGroupAddressFields();
            AddOrderPaymentFields();
        }

        private void AddBaseProductProductType()
        {
            var metaProp = AddProductTypeField();
        }

        private void AddLineItemFields()
        {
            var metaLineItemClass = MetaClass.Load(_metaDataContext, LINE_ITEM_EX);

            /* Will add them back when nedd them */
            var metaProp = MetaField.Load(_metaDataContext, MetaFieldsConstants.LineItem.GiftVoucherCode);
            if (metaProp == null)
                metaProp = MetaField.Create(_metaDataContext, "Mediachase.Commerce.Orders.System.LineItem", MetaFieldsConstants.LineItem.GiftVoucherCode, "Gift Voucher Code", "Gift Voucher Code", MetaDataType.ShortString, 20, true, false, false, false);

            if (!metaLineItemClass.MetaFields.Contains(metaProp))
                metaLineItemClass.AddField(metaProp);

            metaProp = MetaField.Load(_metaDataContext, MetaFieldsConstants.LineItem.LineItemAbstract);
            if (metaProp == null)
                metaProp = MetaField.Create(_metaDataContext, "Mediachase.Commerce.Orders", MetaFieldsConstants.LineItem.LineItemAbstract, "LineItem Abstract", "LineItem Abstract", MetaDataType.LongHtmlString, 5000, true, false, false, false);

            if (!metaLineItemClass.MetaFields.Contains(metaProp))
                metaLineItemClass.AddField(metaProp);

            metaProp = MetaField.Load(_metaDataContext, MetaFieldsConstants.LineItem.ImageUrl);
            if (metaProp == null)
                metaProp = MetaField.Create(_metaDataContext, "Mediachase.Commerce.Orders", MetaFieldsConstants.LineItem.ImageUrl, "Image Url", "Image Url", MetaDataType.ShortString, 1000, true, false, false, false);

            if (!metaLineItemClass.MetaFields.Contains(metaProp))
                metaLineItemClass.AddField(metaProp);

            metaProp = AddProductTypeField();
            if (!metaLineItemClass.MetaFields.Contains(metaProp))
                metaLineItemClass.AddField(metaProp);
        }

        private void AddOrderGroupAddressFields()
        {
            var metaOrdeGroupAddressClass = MetaClass.Load(_metaDataContext, ORDER_GROUP_ADDRESS_EX);
            var metaProp = MetaField.Load(_metaDataContext, MetaFieldsConstants.OrderGroupAddress.Suburb_Field);
            if (metaProp == null)
                metaProp = MetaField.Create(_metaDataContext, "Mediachase.Commerce.Orders", MetaFieldsConstants.OrderGroupAddress.Suburb_Field, "Suburb", "Suburb", MetaDataType.ShortString, 100, true, true, false, false);
            if (metaProp != null && !metaOrdeGroupAddressClass.MetaFields.Contains(metaProp))
                metaOrdeGroupAddressClass.AddField(metaProp);
        }

        private void AddOrderFormFields()
        {
            var metaOrderFormClass = MetaClass.Load(_metaDataContext, ORDER_FORM_EX);
            var metaProp = MetaField.Load(_metaDataContext, MetaFieldsConstants.OrderForm.SubscribeToNewsletters_Field);
            if (metaProp == null)
                metaProp = MetaField.Create(_metaDataContext, "Mediachase.Commerce.Orders", MetaFieldsConstants.OrderForm.SubscribeToNewsletters_Field, "Subscribe To NewsLetter", "Subscribe To NewsLetter", MetaDataType.Boolean, 10, true, false, false, false);
            if(metaProp != null && !metaOrderFormClass.MetaFields.Contains(metaProp))
                metaOrderFormClass.AddField(metaProp);

            metaProp = MetaField.Load(_metaDataContext, MetaFieldsConstants.OrderForm.ReOrdering_Field);
            if (metaProp == null)
                metaProp = MetaField.Create(_metaDataContext, "Mediachase.Commerce.Orders", MetaFieldsConstants.OrderForm.ReOrdering_Field, "Reordering", "Reordering", MetaDataType.Boolean, 10, true, false, false, false);
            if (metaProp != null && !metaOrderFormClass.MetaFields.Contains(metaProp))
                metaOrderFormClass.AddField(metaProp);
        }

        private void AddOrderPaymentFields()
        {
            var metaOrderFormClass = MetaClass.Load(_metaDataContext, ORDER_CREDIT_CARD_PAYMENT);
            var metaProp = MetaField.Load(_metaDataContext, MetaFieldsConstants.OrderPayment.PaymentProviderResponse_Field);
            if (metaProp == null)
                metaProp = MetaField.Create(_metaDataContext, "Mediachase.Commerce.Orders", MetaFieldsConstants.OrderPayment.PaymentProviderResponse_Field, "Payment Provider Response", "Payment Provider Response", MetaDataType.LongString, 10000, true, false, false, false);
            if (metaProp != null && !metaOrderFormClass.MetaFields.Contains(metaProp))
                metaOrderFormClass.AddField(metaProp);
        }

        private MetaField AddProductTypeField()
        {
            MetaField metaProp = MetaField.Load(_metaDataContext, PRODUCT_TYPE);

            if (metaProp == null)
            {
                metaProp = MetaField.Create(_metaDataContext, "Mediachase.Commerce.Catalog", PRODUCT_TYPE, "Product Type", "Product type such as(Walker, Crawler, Toddler and etc", MetaDataType.EnumSingleValue, 1000, true, false, false, false);
            }

            foreach (var pr in _productTypes)
            {
                var existItem = metaProp.Dictionary.GetItem(pr);
                if (existItem == null)
                    metaProp.Dictionary.Add(pr, pr);

            }

            return metaProp;
        }
    }
}

We will also this SharedDependencyInitialization which will be call once before initialization of site to add/update any meta class or meta field.



using System.Reflection;
using EPiServer.Framework;
using EPiServer.Framework.Initialization;
using EPiServer.Reference.Commerce.Shared.Data.BusinessFoundation.Initialization;
using EPiServer.Reference.Commerce.Shared.Utilities;
using EPiServer.ServiceLocation;
using Mediachase.BusinessFoundation.Data;
using Mediachase.MetaDataPlus;

namespace EPiServer.Reference.Commerce.Shared.Infrastructure.Initialization
{
 [ModuleDependency(typeof(ServiceContainerInitialization))]
 [InitializableModule]
 public class SharedDependencyInitialization : IConfigurableModule
 {
  private bool _isInitialized;

  public void Initialize(InitializationEngine context)
  {
   if (_isInitialized)
    return;

      SyncMetaObjects();

                    _isInitialized = true;
  }

  public void Uninitialize(InitializationEngine context)
  {
   _isInitialized = false;
  }

  public void ConfigureContainer(ServiceConfigurationContext context)
  {
   context.Container.Configure(ctx => ctx.AddRegistry<SharedDependencyRegistry>());
  }

  private static void SyncMetaObjects()
     {
            new MetaClassAndFieldsSyncInitializer(MetaDataContext.Instance).Sync();
        }
 }
}