Monday, November 28, 2016

Adding New MetaDataPlus Dictionary Field and Assigning it to Catalog Entry Class

We will be adding new catalog entry class and will be assigning new meta dictionary field with multiple values.

Adding new Catalog Entry (ProductBase) Class



    [CatalogContentType(GUID = "C39413F3-310C-49C6-A4C4-6B86D6788401", MetaClassName = "ProductBase")]
    public class ProductBase  : ProductContent
    {
       [Searchable]
        [IncludeInDefaultSearch]
        [BackingType(typeof(PropertyDictionarySingle))]
        [Display(Order = 60)]
        [Required]
        public virtual string ProductType { get; set; }
    }



Now we will be defining initializing class to add new meta field and assign it to Catalog Entry(ProductBase) class



[ModuleDependency(typeof(ServiceContainerInitialization))]
 [InitializableModule]
 public class CatalogInitialization : IConfigurableModule
 {
  private bool _isInitialized;

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

   SyncMetaClassAndField();

   _isInitialized = true;
  }

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

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

  private static void SyncMetaClassAndField()
  {
    var metaProClass = Mediachase.MetaDataPlus.Configurator.MetaClass.Load(MetaDataContext.Instance, "ProductBase");
                  MetaField metaProp = MetaField.Load(MetaDataContext.Instance, "ProductType");

      if (metaProp == null)
      {
                      metaProp = Mediachase.MetaDataPlus.Configurator.MetaField.Create(MetaDataContext.Instance, "Mediachase.Commerce.Catalog", "ProductType", "Product Type", "Product Type Description", MetaDataType.DictionarySingleValue, 1000, false, false, false, false);
                    }
                  metaProp.Dictionary.Add("Baby", "Baby");
                  metaProp.Dictionary.Add("Toddler", "Toddler");
                  metaProp.Dictionary.Add("Junior", "Junior");
                  metaProp.Dictionary.Add("Walker", "Walker");
                  metaProClass.AddField(metaProp);
        }
 }

Friday, November 4, 2016

Using EPiServer IMigratoinSteps To Automatically Add EPi Commerce Market, Warehouse, Payment and Shipping Method

In one of my application i had to setup Initial Market, Mulitple Warehouses, Payment Method and Shipping Method in order to make thing available for editors.

I got some of the help from the QuickSilver ImportSiteContent Class. However, it was missing how to add new market and warehouses.

I will strongly recommend to look at above class to understand more about it.

We will now go through how to add Market, Warehouses, Shipping Method and Payment Method.

IMigrationStep

In one of my question that i asked on EPiServer Forum, the guy replied that IMigrationStep is an internal class and should not be used because it will void the support contract and can have break change impact in future release. So please guys use it with caution.

We will need to decorate our class

[ServiceConfiguration(typeof(IMigrationStep))]



[ServiceConfiguration(typeof(IMigrationStep))]
    public class ImportNZSiteContent : IMigrationStep
    {   
 public int Order { get { return 2010; } }
        public string Name { get { return "Adding Market, Warehouse and Shipping"; } }
        public string Description { get { return "Adding NZ/AU Market, Multiple Warehouses for NZ and Free Shipping method"; } }
  
 public bool Execute(IProgressMessenger processMessenger)
        {
            _progressMessenger =  processMessenger;
            _progressMessenger.AddProgressMessageText("Adding/Updating New Zealand Market and Warehouse", false, 0);
            try
            {
   
     }   
            catch (Exception exception)
            {
                _processMessenger.AddProgressMessageText("ImportTreasureSiteContent failed: " + exception.Message + "Stack trace:" + exception.StackTrace, true, 0);                
                return false;
            }
            return true;
        }
     }

Re-Testing 

The Migration will only run once per database therefore, if you want to re-test then change the name or namespace of the class and re run the site.

Verify Migration Step
Once you will brows the CMS site it and login with Admin credential, System will provide you the migration step summary. 

Another way to check if migration was run successfully is to look at the view [dbo].[VW_EPiServer.Commerce.Internal.Migration.MigrationStepInfo]) in CMS Site.



Market

By Default EPi Commerce provides IMarket Implementaion MarketImpl but this class does not have setter function to set new values therefore, we will need to add our own class for IMarket interface. We will inherit from the MarketImp in order keep thing simple and easy.


internal class SiteMarketImp : MarketImpl, IMarket
    {
        public new MarketId MarketId { get; set; }
        public new bool IsEnabled { get; set; }
        public new string MarketName { get; set; }
        public new string MarketDescription { get; set; }
        public new CultureInfo DefaultLanguage { get; set; }
        public new IEnumerable<CultureInfo> Languages { get; set; }
        public new Currency DefaultCurrency { get; set; }
        public new IEnumerable<Currency> Currencies { get; set; }
        public new IEnumerable<string> Countries { get; set; }

        /// <summary>
        /// Creates an empty IMarket instance with the specified market identifier.
        /// </summary>
        /// <param name="marketId"></param>
        public SiteMarketImp (MarketId marketId) : base(marketId)
        {
            this.MarketId = marketId;
        }

        /// <summary>Creates a copy of the passed IMarket instance.</summary>
        /// <param name="market">The IMarket instance to copy.</param>
        public SiteMarketImp (IMarket market) : base(market)
        {
            this.MarketId = market.MarketId;
            this.IsEnabled = market.IsEnabled;
            this.MarketName = market.MarketName;
            this.MarketDescription = market.MarketDescription;
            this.DefaultLanguage = market.DefaultLanguage;
            this.DefaultCurrency = market.DefaultCurrency;
        }
    }


Function to Add/Update Market


    private IMarket AddNzMarket()
        {
            MarketId NZ_Market_ID = new MarketId("NZL"); 
            var nzCulture = CultureInfo.GetCultureInfo("en-NZ");
            var nzCurrency = new Currency("NZD");
            var allMarkets = _marketService.GetAllMarkets();
            var nzMarket = allMarkets.FirstOrDefault(m => m.MarketId == NZ_Market_ID);
            bool isCreating = nzMarket == null;
            var updatedNzMarket = isCreating ? new SiteMarketImp (new MarketId("NZL")) : new SiteMarketImp (nzMarket);

            /* Get the country just to make sure it exists */
            var countryDto = CountryManager.GetCountry("NZL", false);

            /* countryDto Table 0 is for State/Province and 1 is for Country 
             * countryDto Row is to get the country code
             */
            if (countryDto == null || countryDto.Tables.Count < 2 || countryDto.Tables[1].Rows.Count < 0)
                throw new ContentNotFoundException("New Zealand Country Could not be found in EPi Commerce");

            /* All the requried fields need to be populated */
            updatedNzMarket.MarketName = "New Zealand";
            updatedNzMarket.MarketDescription = "New Zealand";
            updatedNzMarket.Countries = new List<string> { "NZL" };
            updatedNzMarket.Currencies = new List<Currency> { nzCurrency };
            updatedNzMarket.DefaultCurrency = nzCurrency;
            updatedNzMarket.Languages = new List<CultureInfo> { nzCulture };
            updatedNzMarket.DefaultLanguage = nzCulture;
            updatedNzMarket.IsEnabled = true;

           /* Save NZ Market */
            if (isCreating)
                _marketService.CreateMarket(updatedNzMarket);
            else
                _marketService.UpdateMarket(updatedNzMarket);

            return updatedNzMarket;
        }


Add/Update Warehouses



    private void AddNzWarehouses()
        {
            /* Adding/Updating two ware houses */
            var nzSouth = "NZSouth";
            var nzNorth = "NZNorth";
            var warehouses =_warehouseRepository.List().ToList();

            /* Add/Update South Island Warehouse */
            var nzWarehouse = warehouses.FirstOrDefault(w => w.Code == nzSouth);
            var nzSouthWarehouse= nzWarehouse == null ? new Warehouse() : new Warehouse(nzWarehouse);
            SaveWarehouse(nzSouthWarehouse, nzSouth, "New Zealand South Island Warehouse", true);

            /* Add/Update North Island Warehouse */
            nzWarehouse = warehouses.FirstOrDefault(w => w.Code == nzNorth);
            var nzNorthWarehouse = nzWarehouse == null ? new Warehouse() : new Warehouse(nzWarehouse);
            SaveWarehouse(nzNorthWarehouse, nzNorth, "New Zealand North Island Warehouse", false);
        }   
     private void SaveWarehouse(Warehouse warehouse, string code, string name, bool isPrimary)
        {
            warehouse.ApplicationId = AppContext.Current.ApplicationId;
            warehouse.Code = code;
            warehouse.Name = name;
            warehouse.IsActive = true;
            warehouse.IsPrimary = isPrimary;
            warehouse.IsFulfillmentCenter = true;
            warehouse.IsPickupLocation = false;
            warehouse.IsDeliveryLocation = true;
            warehouse.ContactInformation = new WarehouseContactInformation(warehouse.ContactInformation);
            _warehouseRepository.Save(warehouse);
        }


Add/Update ShippingMethod



  private void AddNzShippingMethod(IMarket market)
        {
            var markets = new List<IMarket> {market};
            foreach (var language in market.Languages.Distinct())
            {
                var languageId = language.TwoLetterISOLanguageName;
                var dto = ShippingManager.GetShippingMethods(languageId);
                DeleteShippingMethods(dto);
                ShippingManager.SaveShipping(dto);

                var shippingSet = CreateShippingMethodsForLanguageAndCurrencies(dto, markets, languageId);
                ShippingManager.SaveShipping(dto);

                AssociateShippingMethodWithMarkets(dto, markets, shippingSet);
                ShippingManager.SaveShipping(dto);
            }
        }

        private void DeleteShippingMethods(ShippingMethodDto dto)
        {
            foreach (var method in dto.ShippingMethod)
            {
                method.Delete();
            }
        }

        private IEnumerable<ShippingMethodDto.ShippingMethodRow> CreateShippingMethodsForLanguageAndCurrencies(ShippingMethodDto dto, IEnumerable<IMarket> markets, string languageId)
        {
            var shippingOption = dto.ShippingOption.First(x => x.Name == "Generic Gateway");
            var shippingMethods = new List<ShippingMethodDto.ShippingMethodRow>();
            var sortOrder = 1;

            var nzdCostRegular = new Money(0, Currency.NZD);

            foreach (var currency in markets.SelectMany(m => m.Currencies).Distinct())
            {
                shippingMethods.Add(CreateShippingMethod(dto, shippingOption, languageId, sortOrder, "Regular-" + currency, string.Format("Regular {0} (4 - 5 days) ({1})", currency, languageId), nzdCostRegular, currency));
            }

            return shippingMethods;
        }

        private ShippingMethodDto.ShippingMethodRow CreateShippingMethod(ShippingMethodDto dto, ShippingMethodDto.ShippingOptionRow shippingOption, string languageId, int sortOrder, string name, string description, Money costInNzd, Currency currency)
        {
            return dto.ShippingMethod.AddShippingMethodRow(
                Guid.NewGuid(),
                shippingOption,
                AppContext.Current.ApplicationId,
                languageId,
                true,
                name,
                "Free Delivery",
                costInNzd.Amount,
                currency,
                description,
                false,
                sortOrder,
                DateTime.Now,
                DateTime.Now);
        }

        private void AssociateShippingMethodWithMarkets(ShippingMethodDto dto, IEnumerable<IMarket> markets, IEnumerable<ShippingMethodDto.ShippingMethodRow> shippingSet)
        {
            foreach (var shippingMethod in shippingSet)
            {
                foreach (var market in markets.Where(m => m.Currencies.Contains<Currency>(shippingMethod.Currency)))
                {
                    dto.MarketShippingMethods.AddMarketShippingMethodsRow(market.MarketId.Value, shippingMethod);
                }
            }
        }