November 24, 2017
We all know the feeling. Having to setup tax jurisdictions, warehouses, payment methods or shipping methods can be a tedious exercise, which quickly can be interfered by human mistakes if it isn’t applied consistently. Not everyone is aware that these data aspects easily can be scripted, hence automatically moved across databases used by folks within your development team and to the more stable environments such as Integration, Pre-Production and Production.
When would these scripts be executed?
We tend to rely on Optimizely’s Initialization mechanism to apply these data enrichment scripts.
[InitializableModule] [ModuleDependency(typeof(CommerceInitialization))] public class ExampleInitialization: IInitializableModule { public void Initialize(InitializationEngine context) { //Enrichment script goes here } public void Uninitialize(InitializationEngine context) { } }
You though need to remember that these Initialization Modules are re-executed every time instances of your application starts up, hence it will always be your responsibility to ensure the same data record is not added over and over again. Please also note the ModuleDependency annotation, which forces these to be initialized after the Optimizely Commerce engine has been setup.
Let’s have a look at some data enrichment scenarios.
Tax Groups, Tax Jurisdictions, Tax Categories and Taxes
[InitializableModule] [ModuleDependency(typeof(CommerceInitialization))] public class SetupTaxJurisdictionInitialization : IInitializableModule { private const string TaxCategory = "VAT"; public void Initialize(InitializationEngine context) { ILanguageBranchRepository languageBranchRepository = context.Locate.LanguageBranchRepository(); JurisdictionDto jurisdictionDto = JurisdictionManager.GetJurisdictions(JurisdictionManager.JurisdictionType.Tax); //Check if our VAT jurisdiction group already exists to avoid duplicate records. JurisdictionDto.JurisdictionGroupRow jurisdictionGroup = jurisdictionDto.JurisdictionGroup.FirstOrDefault(g => g.Code.Equals("VAT", StringComparison.OrdinalIgnoreCase)); if (jurisdictionGroup == null) { jurisdictionDto = new JurisdictionDto(); //Add new jurisdiction group jurisdictionGroup = jurisdictionDto.JurisdictionGroup.NewJurisdictionGroupRow(); jurisdictionGroup.ApplicationId = Mediachase.Commerce.Core.AppContext.Current.ApplicationId; jurisdictionGroup.Code = "VAT"; jurisdictionGroup.DisplayName = "VAT Group"; jurisdictionGroup.JurisdictionType = (int)JurisdictionManager.JurisdictionType.Tax; jurisdictionDto.JurisdictionGroup.AddJurisdictionGroupRow(jurisdictionGroup); } // Get list of country codes from the website languages being supported // Convert these to regions to apply taxes to IList<RegionInfo> regions = languageBranchRepository.ListEnabled() .Where(l => !l.Culture.IsNeutralCulture) .Select(l => l.Culture.Name).Distinct() .Select(l => new RegionInfo(l)).ToList(); foreach (RegionInfo region in regions) { //Validate if region already exists bool doesExist = jurisdictionDto.Jurisdiction .Any(j => !j.IsCodeNull() && j.Code.Equals(region.TwoLetterISORegionName, StringComparison.OrdinalIgnoreCase)); //Add jurisdiction if it does not exist if (!doesExist) { //Add new jurisdiction JurisdictionDto.JurisdictionRow jurisdiction = jurisdictionDto.Jurisdiction.NewJurisdictionRow(); jurisdiction.ApplicationId = Mediachase.Commerce.Core.AppContext.Current.ApplicationId; jurisdiction.Code = region.TwoLetterISORegionName; jurisdiction.DisplayName = region.DisplayName; jurisdiction.CountryCode = region.TwoLetterISORegionName; jurisdiction.JurisdictionType = (int)JurisdictionManager.JurisdictionType.Tax; jurisdictionDto.Jurisdiction.AddJurisdictionRow(jurisdiction); //Add new jurisdiction relation with group JurisdictionDto.JurisdictionRelationRow jurisdictionRelation = jurisdictionDto.JurisdictionRelation.NewJurisdictionRelationRow(); jurisdictionRelation.JurisdictionId = jurisdiction.JurisdictionId; jurisdictionRelation.JurisdictionRow = jurisdiction; jurisdictionRelation.JurisdictionGroupRow = jurisdictionGroup; jurisdictionDto.JurisdictionRelation.AddJurisdictionRelationRow(jurisdictionRelation); } } if (jurisdictionDto.HasChanges()) JurisdictionManager.SaveJurisdiction(jurisdictionDto); //Create our Tax Category if it doesn't exist this.CreateTaxCategoryIfNotExists(); //Get tax category string taxCategory = this.GetTaxCategory(); //Retrieve the tax records for Sales Tax TaxDto taxDto = TaxManager.GetTaxDto(TaxType.SalesTax); //Validate if we already have a record called "Sales Tax". TaxDto.TaxRow salesTaxRow = taxDto.Tax .FirstOrDefault(t => t.Name.Equals("Sales Tax", StringComparison.OrdinalIgnoreCase)); //Add tax value for Sales Tax with rate 20% if (salesTaxRow == null) { salesTaxRow = taxDto.Tax.NewTaxRow(); salesTaxRow.ApplicationId = Mediachase.Commerce.Core.AppContext.Current.ApplicationId; salesTaxRow.Name = "Sales Tax"; salesTaxRow.SortOrder = 1; salesTaxRow.TaxType = (int)TaxType.SalesTax; taxDto.Tax.AddTaxRow(salesTaxRow); //Add all language labels for the sales tax value foreach (LanguageBranch languageBranch in languageBranchRepository.ListEnabled()) { TaxDto.TaxLanguageRow taxLanguage = taxDto.TaxLanguage.NewTaxLanguageRow(); taxLanguage.LanguageCode = languageBranch.LanguageID; taxLanguage.TaxRow = salesTaxRow; taxLanguage.DisplayName = "VAT 20%"; taxDto.TaxLanguage.AddTaxLanguageRow(taxLanguage); } TaxDto.TaxValueRow salesTaxValue = taxDto.TaxValue.NewTaxValueRow(); salesTaxValue.Percentage = 20; salesTaxValue.AffectiveDate = DateTime.UtcNow; salesTaxValue.TaxCategory = taxCategory; salesTaxValue.JurisdictionGroupId = jurisdictionGroup.JurisdictionGroupId; salesTaxValue.TaxRow = salesTaxRow; taxDto.TaxValue.AddTaxValueRow(salesTaxValue); } if (taxDto.HasChanges()) TaxManager.SaveTax(taxDto); //Retrieve the tax records for Shipping Tax taxDto = TaxManager.GetTaxDto(TaxType.ShippingTax); //Validate if we already have a record called "Shipping Tax". TaxDto.TaxRow shippingTaxRow = taxDto.Tax .FirstOrDefault(t => t.Name.Equals("Shipping Tax", StringComparison.OrdinalIgnoreCase)); if (shippingTaxRow == null) { shippingTaxRow = taxDto.Tax.NewTaxRow(); shippingTaxRow.ApplicationId = Mediachase.Commerce.Core.AppContext.Current.ApplicationId; shippingTaxRow.Name = "Shipping Tax"; shippingTaxRow.SortOrder = 2; shippingTaxRow.TaxType = (int)TaxType.ShippingTax; taxDto.Tax.AddTaxRow(shippingTaxRow); TaxDto.TaxValueRow shippingTaxValue = taxDto.TaxValue.NewTaxValueRow(); shippingTaxValue.Percentage = 20; shippingTaxValue.AffectiveDate = DateTime.UtcNow; shippingTaxValue.TaxCategory = taxCategory; shippingTaxValue.JurisdictionGroupId = jurisdictionGroup.JurisdictionGroupId; shippingTaxValue.TaxRow = shippingTaxRow; taxDto.TaxValue.AddTaxValueRow(shippingTaxValue); } if(taxDto.HasChanges()) TaxManager.SaveTax(taxDto); } private string GetTaxCategory() { //Validate if TaxCategory exists CatalogTaxDto taxCategories = CatalogTaxManager.GetTaxCategories(true); if (taxCategories.TaxCategory.Any(t => t.Name.Equals(TaxCategory, StringComparison.OrdinalIgnoreCase))) return TaxCategory; return String.Empty; } private void CreateTaxCategoryIfNotExists() { if (String.IsNullOrWhiteSpace(this.GetTaxCategory())) { CatalogTaxManager.CreateTaxCategory(TaxCategory, true); } } public void Uninitialize(InitializationEngine context) { } }
Warehouses
[InitializableModule] [ModuleDependency(typeof(Optimizely.Commerce.Initialization.InitializationModule))] public class SetupWarehouseInitialization : IInitializableModule { public void Initialize(InitializationEngine context) { IWarehouseRepository warehouseRepository = context.Locate.Advanced.GetInstance<IWarehouseRepository>(); if (warehouseRepository.Get("NYC") == null) { Warehouse warehouse = new Warehouse(); warehouse.Code = "NYC"; warehouse.IsActive = true; warehouse.Created = DateTime.Now; warehouse.Modified = DateTime.Now; warehouse.SortOrder = 0; warehouse.IsPrimary = true; warehouse.ApplicationId = Mediachase.Commerce.Core.AppContext.Current.ApplicationId; warehouse.ContactInformation = new WarehouseContactInformation() { City = "New York", CountryCode = "US", CountryName = "USA", DaytimePhoneNumber = "1 (123) 123 1234", Email = "newyork@distributor.com" // More information goes here... }, warehouse.FulfillmentPriorityOrder = 0; warehouse.IsDeliveryLocation = false; warehouse.IsFulfillmentCenter = true; warehouse.IsPickupLocation = true; warehouse.Name = "NY Warehouse"; warehouseRepository.Save(warehouse); } } public void Uninitialize(InitializationEngine context) { } }
Payment Methods
[InitializableModule] [ModuleDependency(typeof(Optimizely.Commerce.Initialization.InitializationModule))] public class SetupPaymentMethodsInitialization : IInitializableModule { public void Initialize(InitializationEngine context) { IMarketService marketService = context.Locate.Advanced.GetInstance<IMarketService>(); //Get all enabled markets IEnumerable<IMarket> allMarkets = marketService.GetAllMarkets(); //Get all the languages foreach (CultureInfo language in allMarkets.SelectMany(m => m.Languages).Distinct()) { //Get all payment methods for this language PaymentMethodDto paymentMethodDto = PaymentManager.GetPaymentMethods(language.Name); //Get the market's to enable the method for IEnumerable<MarketId> marketIds = allMarkets.Where(m => m.Languages.Contains(language)) .Select(m => m.MarketId); //Get the payment methods we want to create for this language IList<String[]> listOfPayments = this.GetPaymentMethodList(language.Name); listOfPayments.ForEach(p => { String paymentCode = p[0]; String paymentName = p[1]; //Adjust as per your needs this.AddPaymentMethodIfItDoesNotExist(paymentName, paymentName, paymentCode, language, typeof(GenericPaymentGateway), typeof(OtherPayment), marketIds, paymentMethodDto); }); } } private void AddPaymentMethodIfItDoesNotExist(string name, string description, string systemKeyword, CultureInfo language, Type providerType, Type paymentClass, IEnumerable<MarketId> marketIds, PaymentMethodDto paymentMethodDto) { bool found = paymentMethodDto.PaymentMethod .Any(c => c.SystemKeyword.Equals(systemKeyword, StringComparison.OrdinalIgnoreCase)); //Skip if (found) { PaymentMethodDto.PaymentMethodRow paymentMethodRow = paymentMethodDto.PaymentMethod.First(p => p.IsActive && p.SystemKeyword.Equals(systemKeyword, StringComparison.OrdinalIgnoreCase)); PaymentMethod method = new PaymentMethod(paymentMethodRow); marketIds.ForEach(m => { if(!method.MarketId.Contains(m)) method.MarketId.Add(m); }); method.SaveChanges(); return; } PaymentMethodDto.PaymentMethodRow row = paymentMethodDto.PaymentMethod.AddPaymentMethodRow(Guid.NewGuid(), name, description, language.Name, systemKeyword, true, false, String.Format("{0}, {1}", providerType.FullName, providerType.Assembly.GetName().Name), String.Format("{0}, {1}", paymentClass.FullName, paymentClass.Assembly.GetName().Name), false, 0, DateTime.Now, DateTime.Now, Mediachase.Commerce.Core.AppContext.Current.ApplicationId); PaymentMethod paymentMethod = new PaymentMethod(row); paymentMethod.MarketId.AddRange(marketIds); paymentMethod.SaveChanges(); } public void Uninitialize(InitializationEngine context) { } private IList<String[]> GetPaymentMethodList(string languageName) { //You can separate by language if needed. return new List<String[]>() { new[] {"30D", "Net 30 Days Credit"}, new[] {"60D", "Net 60 Days Credit"}, new[] {"90D", "Net 90 Days Credit"} }; } }
Shipping Options
[InitializableModule] [ModuleDependency(typeof(CommerceInitialization))] public class SetupShippingMethodsInitialization : IInitializableModule { public void Initialize(InitializationEngine context) { IMarketService marketService = context.Locate.Advanced.GetInstance<IMarketService>(); //Get all enabled markets IEnumerable<IMarket> allMarkets = marketService.GetAllMarkets(); //Get all the languages foreach (CultureInfo language in allMarkets.SelectMany(m => m.Languages).Distinct()) { //Get all shipping methods for the language ShippingMethodDto shippingMethodDto = ShippingManager.GetShippingMethods(language.Name, true); //Create a shipping provider, as needed. //You can also choose to use one of the build in provider classes ShippingMethodDto.ShippingOptionRow shippingOption = this.CreateOrGetShippingOption("Calculated Shipping", "Calculated Shipping", "CalculatedRate", typeof(CalculatedShippingPlugin), shippingMethodDto); //Get the first market for our language Currency defaultCurrency = allMarkets.First(m => m.Languages.Contains(language)).DefaultCurrency; //Get the market's to enable the method for IList<MarketId> marketIds = allMarkets.Where(m => m.Languages.Contains(language)).Select(m => m.MarketId).ToList(); this.AddShippingMethodIfItDoesNotExist("Calculated Fee Royal Mail", "Royal Mail", "RM", language, defaultCurrency, 0m, marketIds, shippingMethodDto, shippingOption); //Save shipping options and methods if (shippingMethodDto.HasChanges()) ShippingManager.SaveShipping(shippingMethodDto); } } private ShippingMethodDto.ShippingOptionRow CreateOrGetShippingOption(string shippingOptionName, string shippingOptionDescription, string systemKeyword, Type shippingProviderType, ShippingMethodDto shippingMethodDto) { //Compose class name string providerClassName = String.Format("{0}, {1}", shippingProviderType.FullName, shippingProviderType.Assembly.GetName().Name); //Try to locate the shipping provider we are trying to create ShippingMethodDto.ShippingOptionRow shippingOptionRow = shippingMethodDto.ShippingOption.FirstOrDefault( option => option.ClassName == providerClassName && option.SystemKeyword == systemKeyword && option.ApplicationId == Mediachase.Commerce.Core.AppContext.Current.ApplicationId); //Check if it already exist if (shippingOptionRow == null) { //Add shipping provider shippingOptionRow = shippingMethodDto.ShippingOption.AddShippingOptionRow(Guid.NewGuid(), shippingOptionName, shippingOptionDescription, systemKeyword, providerClassName, DateTime.Now, DateTime.Now, Mediachase.Commerce.Core.AppContext.Current.ApplicationId); } return shippingOptionRow; } private void AddShippingMethodIfItDoesNotExist(string shippingMethodDisplayName, string shippingMethodDescription, string shippingMethodName, CultureInfo language, Currency currency, decimal basePrice, IList<MarketId> marketIds, ShippingMethodDto shippingMethodDto, ShippingMethodDto.ShippingOptionRow shippingOption) { //Find for the shipping method that is to be added ShippingMethodDto.ShippingMethodRow shippingMethod = shippingMethodDto.ShippingMethod .FirstOrDefault(method => method.Name == shippingMethodName && method.ApplicationId == Mediachase.Commerce.Core.AppContext.Current.ApplicationId && method.ShippingOptionRow != null && method.ShippingOptionRow.ClassName == shippingOption.ClassName); //Check if it already exist if (shippingMethod == null) { //Add shipping method shippingMethod = shippingMethodDto.ShippingMethod.AddShippingMethodRow(Guid.NewGuid(), shippingOption, Mediachase.Commerce.Core.AppContext.Current.ApplicationId, language.Name, true, shippingMethodName, shippingMethodDescription, basePrice, currency, shippingMethodDisplayName, false, 0, DateTime.Now, DateTime.Now); } else if(shippingMethod.ShippingOptionId != shippingOption.ShippingOptionId) { shippingMethod.ShippingOptionId = shippingOption.ShippingOptionId; } IList<string> existingMarkets = shippingMethodDto.MarketShippingMethods .Where(m => m.ShippingMethodId == shippingMethod.ShippingMethodId) .Select(m => m.MarketId).ToList(); //Add markets for shipping methods marketIds.ForEach(marketId => { if(!existingMarkets.Contains(marketId.Value)) shippingMethodDto.MarketShippingMethods.AddMarketShippingMethodsRow(marketId.Value, shippingMethod); }); } public void Uninitialize(InitializationEngine context) { } }
Markets
[InitializableModule] [ModuleDependency(typeof(CommerceInitialization))] public class SetupMarketInitialization : IInitializableModule { public void Initialize(InitializationEngine context) { IMarketService marketService = context.Locate.Advanced.GetInstance<IMarketService>(); //Create markets to hold pricing data this.CreateMarketIfDoesNotExist(marketService, Currency.USD, CultureInfo.GetCultureInfo("en-US"), "US"); } public void Uninitialize(InitializationEngine context) { } /// <summary> /// Create market if it does not exist already /// </summary> /// <param name="marketConventionManager"></param> /// <param name="marketId">MarketId</param> /// <param name="marketService">Market Service</param> private void CreateMarket(IMarketService marketService, MarketId marketId, Currency defaultCurrency, CultureInfo defaultLanguage, string marketName) { //Create market implementation from marketid MarketImpl market = new MarketImpl(marketId); //Set default currency as USD market.DefaultCurrency = defaultCurrency; //Set default language same as preferred culture market.DefaultLanguage = defaultLanguage; market.MarketName = marketName; //Activate the market by setting value to true market.IsEnabled = true; //Add country codes to market //market.CountriesCollection.Add(c); //Add languages to market //market.LanguagesCollection.Add(l); //Add currencies to market //market.CurrenciesCollection.Add(c); market.MarketDescription = market.MarketName; //Create market marketService.CreateMarket(market); } private void CreateMarketIfDoesNotExist(IMarketService marketService, Currency defaultCurrency, CultureInfo defaultLanguage, string marketName) { MarketId marketId = new MarketId(marketName); //Check if market already exist if (marketService.GetMarket(marketId) == null) { //Create market this.CreateMarket(marketService, marketId, defaultCurrency, defaultLanguage, marketName); } } }
This article was originally published on Optimizely Fellow Blog: Optimizely Commerce Data Enrichment Cheat Sheet