create new Table in Databses in sana and map hibernate

 kavo Project.

create table in DB , add script to database update folder

D:\development\Florette_935_2\DatabaseUpdates

create new hbm or update existing.

<hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" default-lazy="false" namespace="Sana.Commerce.Catalog" assembly="Sana.Commerce.Sdk">
  <class name="RelatedProductItems" table="RelatedProductItems">
    <id name="Id" type="Guid">
      <generator class="guid" />
    </id>
    <property name="WebsiteId" type="string" length="50" not-null="true" />
    <property name="CategoryId" type="string" length="50" not-null="false" />
    <property name="ProductSetId" type="Guid" length="50" not-null="true" />
  </class>


need to set correct name space . else get an error.

eh:Catalog.hbm.xml

namespace="Sana.Commerce.Catalog" <-----

RelatedProductitem.cs

namespace Sana.Commerce.Catalog <---------
{
    [Serializable]
    [DataContract(Name = "RelatedProductItem", Namespace = "")]
    public class RelatedProductItem : IRelatedProductItem
    {
        /// <summary>
        /// Gets or sets the stock range identifier.
        /// </summary>
        [IgnoreDataMember]
        public Guid Id { getset; }
 
        /// <summary>
        /// Gets or sets the website identifier.
        /// </summary>
        [IgnoreDataMember]
        public string WebsiteId { getset; }

        /// <summary>
        /// Gets or sets the category identifier.
        /// </summary>
        [DataMember]
        public string CategoryId { getset; }

        /// <summary>
        /// Gets or sets the out of stock value.
        /// </summary>
        [DataMember]
        public Guid ProductSetId { getset; }
 
    }


namespace Sana.Commerce.Catalog
{
    /// Ticket 88704: [Kavo] 3.9. PDP – RELATED ITEMS
    /// <summary>
    /// Describes related product item.
    /// </summary>
    public interface IRelatedProductSet : IIdentityObject
    {
        /// <summary>
        /// Gets or sets the website identifier.
        /// </summary>
        string WebsiteId { getset; }
 
        /// <summary>
        /// Gets or sets the category identifier.
        /// </summary>
        string CategoryId { getset; }
 
        /// <summary>
        /// Gets or sets the Product set id.
        /// </summary>
        Guid ProductSetId { getset; }
    }
}


Top media Ticket 100006: [Topmedia] 3.1. Search – Product Finder

create table on DB 

CREATE TABLE [dbo].[ProductTypes](
[Id] [uniqueidentifier] NOT NULL,
[WebsiteId] [nvarchar](50) NOT NULL,
[Code] [nvarchar](50) NULL,
[Name] [nvarchar](100) NULL,
CONSTRAINT [PK_ProductTypes] PRIMARY KEY CLUSTERED
(
[Id] ASC
)WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY]
) ON [PRIMARY]
GO
 
ALTER TABLE [dbo].[ProductTypes]  WITH CHECK ADD  CONSTRAINT [FK_ProductTypes_ProductTypes] FOREIGN KEY([Id])
REFERENCES [dbo].[ProductTypes] ([Id])
GO
 
ALTER TABLE [dbo].[ProductTypes] CHECK CONSTRAINT [FK_ProductTypes_ProductTypes]
GO

common.hbm.xml

<class name="ProductType" table="ProductTypes" polymorphism="explicit">
    <id name="Id" type="Guid">
      <generator class="guid" />
    </id>
    <property name="WebsiteId" type="string" length="50" not-null="true" />
    <property name="Code" type="string" length="50" not-null="false" />
    <property name="Name" type="string" length="100" not-null="false" />
  </class>


When u want to get datafrom DB use OfflineCommonProvider

from ERP use ExtendedCommonProvider


How to call these methods . This is calling on General import runing

public class ExtendedSyncTaskSyncTask
    {
        protected override void Synchronize()
        {
            base.Synchronize();
 
            var offlineCommonProvider = Providers.Offline.Common;
            var onlineCommonProvider = Providers.Common;
 
            // Ticket 100006: [Topmedia] 3.1. Search – Product Finder
            // Get Prodcuct type data from ERP.
            var pickupLocations = ((ExtendedCommonProvider)onlineCommonProvider).GetProductTypes();
            ((ExtendedOfflineCommonProvider)offlineCommonProvider).DeleteAllProductTypes();
            ((ExtendedOfflineCommonProvider)offlineCommonProvider).SaveProductTypes(pickupLocations);
            taskLog.Add(this"Saved product types.");
 
        }
 
    }


Get data from ERP  (online)

public class ExtendedCommonProvider : CommonProviderIExtendedCommonProvider
    {
 
        public virtual IList<IProductType> GetProductTypes()
        {
            var request = new List<XElement>(); 
            var result = ExecuteRequest("GetProductTypes", request); 
             var productTypes = result.Elements("ProductType").Select(e => ((ExtendedCommonXmlParser)Parser).ParseProductType(e)).ToList();
             
            foreach (var item in productTypes)
            {
                item.WebsiteId = Context.WebsiteId;
            }
            return productTypes;
        }
         
    }


get data from DB offline

public class ExtendedOfflineCommonProvider : OfflineCommonProviderIExtendedCommonProvider
    {
        /// Ticket 100006: [Topmedia] 3.1. Search – Product Finder
        /// <summary>
        /// Delete all product types.
        /// </summary>
        public virtual void DeleteAllProductTypes()
        {
            WrapDataAccessException(() =>
            {
                using (var sm = CreateSessionManager(false))
                {
                    var productTypes = GetProductTypes();
                    foreach (var productType in productTypes)
                    {
                        sm.Session.Delete(productType);
                    }
                    sm.Commit();
                }
            }, "Cannot delete pickup locations for {0} website".FormatWith(Context.WebsiteId));
        }
 
        /// Ticket 100006: [Topmedia] 3.1. Search – Product Finder
        /// <summary>
        ///  Get product types.
        /// </summary>
        /// <returns></returns>
        public virtual IList<IProductType> GetProductTypes()
        {
            using (var sm = CreateSessionManager(true))
            {
                var productTypes = sm.Session.CreateCriteria(GetRegisteredType<IProductType>())
                    .Add(NHibernate.Criterion.Restrictions.Eq("WebsiteId"CommerceFrameworkBase.Context.WebsiteId))
                    .List<IProductType>();               
                return productTypes;
            }
        }
 
        /// Ticket 100006: [Topmedia] 3.1. Search – Product Finder
        /// <summary>
        /// Save product types.
        /// </summary>
        /// <param name="productTypes"></param>
        public virtual void SaveProductTypes(IList<IProductType> productTypes)
        {
            WrapDataAccessException(() =>
            {
                using (var sm = CreateSessionManager(false))
                {
                    productTypes.Each(c => sm.Session.SaveOrUpdate(c));
                    sm.Commit();
                }
            }, "Cannot save pickup location");
        }
 
 
    }




order confirmation e-mail not working. check list

  Send order confirmation e-mail : always send order conf.

1. Check db > PaymentTransactionInfos table > ConfirmationEmailSent set to true :

2. If true : hosting issue

If false sana issue . Check db have any multiple data for same order  

 protected virtual bool ShouldSendConfirmationEmail(IPaymentTransactionInfo transaction)
{
var checkoutProcess = transaction.CheckoutProcessName;
if (checkoutProcess == CheckoutProcesses.EditOrder || checkoutProcess == CheckoutProcesses.EditQuote)
return true;
if (transaction.ConfirmationEmailSent)
return false;
var transactions = CommerceFrameworkBase.PaymentData.GetPaymentTransactionsInfo(transaction.OrderId, transaction.WebsiteId);
return !transactions.Any(t => t.ConfirmationEmailSent);
}

decimal value error with different culture./lanuage

 

  

step="@rangeValue.Step.ToInvariantString()">

Holzpichler
/// Ticket 98400: Wishlist does not adopt/show the attributes and range values
//Convert to decimal with different culters.
range.Value = Decimal.Parse(rangeValue, System.Globalization.CultureInfo.InvariantCulture);







How to get product URL

 @*Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes.*@

var productUrl = Url.Sana.Product(Product, CommerceFrameworkBase.Context.LanguageId);

Add new Section to Admin Product settings / Navigation search new tab/

 Eg : medux, Kavo 


Update AdminMenuInitializer

layoutTabs.Add(AdminMenuConstants.LayoutTabs.ProductSettings, new TabInfoCollection()
{
    CreateLayoutTab("productsets"T.ProductSets, urlHelper.Action("Index""ProductSets"),
        PermissionOn.ProductSets),
    CreateLayoutTab("price"T.Price, urlHelper.Action("Price""ProductSettings"),
        PermissionOn.ProductSettings),
    CreateLayoutTab("specs"T.ProductSpecifications, urlHelper.Action("Index""ProductSpecs"),
        PermissionOn.ProductSettings),
    CreateLayoutTab("stock"T.StockAndAssortment, urlHelper.Action("StockAndAssortment""ProductSettings"),
        PermissionOn.ProductSettings),
    CreateLayoutTab("stockranges"T.StockLevels, urlHelper.Action("Index""StockRanges"),
        PermissionOn.ProductSettings),
    CreateLayoutTab("uoms"T.UnitsOfMeasure, urlHelper.Action("UnitsOfMeasure""ProductSettings"),
        PermissionOn.ProductSettings),
    CreateLayoutTab("relatedproducts"Admin.ResourceManager.GetString(Sana.Commerce.Constants.RelatedProductSettingsMenu), urlHelper.Action("RelatedProductSetting""ProductSettings"),
        PermissionOn.ProductSettings), <------
});


update  ExtendedProductSettingsController

[HttpGet]
        [ErpConnectionRequired]
        public virtual ActionResult RelatedProductSetting()
        {
            var model = ObjectFactory.Create<RelatedProductModel>();
            model.Initialize(Shop.Settings);
            return View(model);
        }
 
       /// <summary>
        /// A POST action to save the stock and assortment settings.
        /// </summary>
        /// <param name="model">The model.</param>
        /// <returns>Returns a view result.</returns>
        [HttpPost]
        [ValidateInput(false)]
        [SanaValidateAntiForgeryToken]
        public virtual ActionResult RelatedProductSetting(RelatedProductModel model)
        {
            if (ModelState.IsValid)
            {
                if (DataManager.ChangeWebsiteSettings(model.ApplyChanges))
                    RefreshSettingsCache();
            }
            return View(model);
        }






// Ticket 100006: [Topmedia] 3.1. Search – Product Finde
AdminMenuInitializer

protected virtual MenuGroupCollection CreateMainMenu(AdminUrlHelper urlHelper, string erpName)
        {
            var mainMenuItems = ObjectFactory.Create<MenuGroupCollection>();

            mainMenuItems.Add(
                  ............
                        CreateAdminMenuLink(T.SearchAndResults, urlHelper.Action("SearchSettings""ProductIndex"),
                            PermissionOn.SearchSettings),

                    CreateAdminMenuLink(Admin.ResourceManager.GetString(Sana.Commerce.Constants.ProductFinder), urlHelper.Action("ProductFinder""ProductIndex"),
                            PermissionOn.SearchSettings)),

AdminMenuInitializer
layoutTabs.Add(AdminMenuConstants.LayoutTabs.NavigationAndSearch, new TabInfoCollection()
            {
                CreateLayoutTab("menus", T.Menus, urlHelper.Action("Index""Menus"), PermissionOn.NavigationSettings),
                CreateLayoutTab("facets", T.FacettedFilters, urlHelper.Action("FacetsSettings""ProductIndex"),
                    PermissionOn.FacettedFilters),
                CreateLayoutTab("search", T.SearchAndResults, urlHelper.Action("SearchSettings""ProductIndex"),
                    PermissionOn.SearchSettings),
                // Ticket 100006: [Topmedia] 3.1. Search – Product Finder
                CreateLayoutTab("productfinder", T.ResourceManager.GetString("ProductFinder"), urlHelper.Action("ProductFinderSettings""ProductIndex"),
                    PermissionOn.FacettedFilters),
            });




How to compair two lines in SANA / Linq list

 

Project Bosig,

 Jimlorance. : https://sanacommerce.visualstudio.com/Sana%20Projects/_git/JimLawrence_932?path=%2FSana.Commerce.Sdk%2FCustomization%2FShop%2FExtendedSalesLineComparer.cs&_a=contents&version=GBmaster

HolzPicher

// Ticket 93818: [Holz Pichler] 3.3.Item variants – “range value” attributes.
public class ExtendedSalesLineComparer : SalesLineComparer
    {
        public override bool MatchSku(ISalesLine line1ISalesLine line2)
        {
            var baseResult = base.MatchSku(line1line2);
            var result =  baseResult && CompareRangeValues(line1line2);
            return result;
        }
 
        protected virtual bool CompareRangeValues(ISalesLine line1ISalesLine line2)
        {
            // Ticket 93818: [Holz Pichler] 3.3.Item variants – “range value” attributes.
 
            var excictingLineRangeValues = ((BasketLine)line1).RangeValueList;
            var newLineRangeValues = ((BasketLine)line2).RangeValueList;
 
            var machingRangeValueFields = excictingLineRangeValues.Where(x => newLineRangeValues.Any(y => x.Name == y.Name && x.Id == y.Id && x.Value == y.Value)).ToList();
            var result  =  (machingRangeValueFields.Count == newLineRangeValues.Count);
            return result;
 
        }
 
 
         
 
 
 
    }



                latestMissmatchList = latestJsonList.Where(s => !oldCJsonList.Any(s2 => s2.Value.Contains(s.Value) && s2.Key.Contains(s.Key))).ToList();

                oldMissmatchList = oldCJsonList.Where(s => !latestJsonList.Any(s2 => s2.Value.Contains(s.Value) && s2.Key.Contains(s.Key))).ToList();


var l1 = currentList.Where(m => !oldList.Contains(m)).ToList();

            var l2 = oldList.Where(m => !currentList.Contains(m)).ToList();


            var diff1 = currentList.Except(oldList).ToList();

            var diff2 = oldList.Except(currentList).ToList();









Quick order / knowkout add variable to quick order

 Project Holzpitcher

@* Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes. *@

control.quickorder.js

//Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes.

        self.isVarient = ko.observable(false); //Product contain varients or not        


function QuickOrderViewModel() {        

        var self = this;
 
           .....
 
        //Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes.
        self.isVarient = ko.observable(); //Product contain varients or not
 
        .....




var _buildComponentGroups = function (data) {
           var componentsCollection = data.VariantComponents,
               variantsCollection = data.Variants;
 
           if (componentsCollection.length) {
 
               //Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes.
               if (componentsCollection.length > 0) {
                   self.isVarient(true)                   
               }
               else {
                   self.isVarient(false)                     
               }
 
               // Product with variants
               self.componentGroups = $.map(componentsCollection, function (vali) {

on view 


get false value 

data-bind="visible: (isVarient() == false)">

get True Value : 

data-bind="visible:isVarient
@* Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes. *@
<div class="" data-bind="visible:isVarient">
    <a data-bind="attr: { href: product().url }" data-product-url class="btn"> @Sana.SimpleText("QuiclOrder_SelectVariants""Select Variants"</a>
</div> 
 
@* Ticket 93818: [Holz Pichler] 3.3. Item variants – “range value” attributes. *@
<div class="qo-quantity-box" data-bind="visible: (isVarient() == false)">
    <!-- ko if: !product().isProductConfigurable -->
    @if (Shop.UserAbilities.Has(AbilityTo.ViewUnitOfMeasure))
    {
        if (Shop.Settings.AllowUnitOfMeasureSelection)


Ravensburg 



control.Quickorder.js



// Ticket 113689: [Ravensburger - Phase 2] 3.1. Display stock availability in the shopping cart. 
        self.availabilityHtmlTags = ko.observable();       


function QuickOrderViewModel() {
       var self = this;
 
       ...
 
       self.product = ko.observable();
       self.components = ko.observable();
       self.quantity = ko.observable();
       self.selectedUom = ko.observable();
       self.defaultUomTitle = ko.observable();
       self.quantityStep = ko.observable(1);
       self.minimumQuantity = ko.observable();
       self.maximumQuantity = ko.observable();
        
       // Ticket 113689: [Ravensburger - Phase 2] 3.1. Display stock availability in the shopping cart. 
       self.availabilityHtmlTags = ko.observable();       


var _buildComponentGroups = function (data) {
            var componentsCollection = data.VariantComponents,
                variantsCollection = data.Variants;
 
            //Ticket 113689: [Ravensburger - Phase 2] 3.1. Display stock availability in the shopping cart
            self.availabilityHtmlTags = data.AvailabilityHtmlTags;
             
            if (componentsCollection.length) {


_quickorder.js

<form data-bind="form: 'quickOrderForm', submit: quickOrderSubmitForm, visible: product()" class="choose-product" style="displaynone;">
        <div data-bind="attr: { 'data-tracking-data': JSON.stringify(product().trackingData) }" class="qo-product-title-box">
            <a data-bind="text: product().title, attr: { href: product().url }" data-product-url class="hyp-qo-title font-darkest" tabindex="-1" target="_blank"></a>
        </div>
 
        <div class="label">
            <label>@Sana.SimpleText("Availability")</label>            
 
            <div>
                @* //Ticket 113689: [Ravensburger - Phase 2] 3.1. Display stock availability in the shopping cart *@
                <div data-bind="html:availabilityHtmlTags"></div>
            </div>
 
 
        </div>


using Sana.Commerce.Catalog;
using Sana.Commerce.Customization.Catalog;
using Sana.Commerce.Shop;
using Sana.Commerce.Stock;
using Sana.Commerce.Web;
using Sana.Commerce.Web.Frontend;
using Sana.Commerce.Web.Frontend.Models.Product;
using Sana.Commerce.Web.Frontend.Models.QuickOrder;
using Sana.Commerce.Web.Mvc.Extensions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Web;
using System.Web.Mvc;
 
namespace Sana.Commerce.Customization.ExtendedModels
{
    public class ExtendedProductInfoModel : ProductInfoModel
    {
        public StockModel Stock { getset; }
 
        public bool IsInStock { getset; }
 
        public string AvailabilityHref { getset; }
 
        public int MaxStock { getset; }
 
        public string AvailabilityHtmlTags { getset; }
 
 
        public override void Initialize(IProduct product, IWebsiteSettings settingsstring detailsUrlbool isProductConfigurable = false)
        {
            base.Initialize(product, settings, detailsUrl, isProductConfigurable);
            Stock = ((ExtendedProductManager)CommerceFramework.Products).CreateProductStockModel(product);
 
 
            var isInStock = Stock.Inventory > Stock.StockRange.OutOfStock;
            IsInStock = isInStock;
 
            var href = "http://schema.org/" + (isInStock ? "InStock" : "OutOfStock");
           // AvailabilityHref = href;
 
 
            var Model = Stock;
 
 
           var maxStock = Model.StockRange.MaxStockNumber.HasValue ? Model.StockRange.MaxStockNumber.Value : -1;
            MaxStock = maxStock;
 
            //HtmlString htmlString = new HtmlString("");
            StringBuilder buildHtml = new StringBuilder();
 
 
            var ranges = new[]
            {
                new { Min = Int32.MinValue, Max = Model.StockRange.OutOfStock },
                new { Min = Model.StockRange.OutOfStock, Max = Model.StockRange.LowStock },
                new { Min = Model.StockRange.LowStock, Max = Int32.MaxValue },
            };
            foreach (var range in ranges)
            {
                var show = Model.Inventory > range.Min && Model.Inventory <= range.Max;
 
                // var htmlString = new HtmlString();
 
                var stockText_MaxStockNumber = CommerceFrameworkBase.SanaTexts.GetSanaText("StockText_MaxStockNumber").FormatWith(maxStock.ToString());
 
                var isHideClass = (!show) ? "hide" : "";
 
                var strHead = "<span class='stock-row "+ isHideClass  + "'" + 
                 " data-min=" + range.Min.ToInvariantString() + " " +
                 " data-max=" + range.Max.ToInvariantString() + " " +
                 " data-max-stock=" + maxStock + " " +
                 " data-custom-stock-message=" + "'"+  stockText_MaxStockNumber + "'" + " "+
                 ">"; 
                
 
                var strBody = StockAmountExtention(Model.StockRange, show ? Model.Inventory : range.Max);
 
                //Sana.StockAmount(Model.StockRange, show ? Model.Inventory : range.Max)
                //ProductExtensions
                var strFooter = "</span>";
 
                var combinedString = strHead + strBody + strFooter;
                buildHtml.Append(combinedString);
                 
            }
            string sHtml = buildHtml.ToString();
 
             sHtml = sHtml.Replace("\"","'"); // REmove double qu
 
            //var htmlString = new HtmlString(sHtml);
            //AvailabilityHtmlTags = htmlString;
            AvailabilityHtmlTags = sHtml;
            AvailabilityHref = "<p>Trrrrrrr</p>";
 
        }
 
        private string StockAmountExtention(IStockRange stockRangedecimal inventorystring textKeySuffix = null)
        {            
            var val = "";
 
            var textKey = "StockText_";
            if (inventory <= stockRange.OutOfStock)
                textKey += "OutOfStock";
            else if (inventory > stockRange.LowStock)
                textKey += "InStock";
            else
                textKey += "LowStock";
 
            if (ShopContext.GetCurrent().IsB2cCustomer)
                textKey += "_B2C";
            else
                textKey += "_B2B";
 
            if (textKeySuffix != null && !textKeySuffix.StartsWith("_"))
                textKeySuffix = '_' + textKeySuffix;
 
            var inventoryElement = new TagBuilder("span");
            inventoryElement.AddCssClass("stock-amount");
 
            if (stockRange.MaxStockNumber.HasValue && stockRange.MaxStockNumber.Value < inventory)
            {
                var maxStockNumberText = CommerceFrameworkBase.SanaTexts.GetSanaText("StockText_MaxStockNumber").FormatWith(stockRange.MaxStockNumber.Value.ToString());
                inventoryElement.SetInnerText(HttpUtility.HtmlDecode(maxStockNumberText.ToString()));
            }
            else
            {
                inventoryElement.SetInnerText(inventory.ToString());
            }
 
            var txtKeyString = CommerceFrameworkBase.SanaTexts.GetSanaText(textKey + textKeySuffix); //.iReplace("[STOCKAMOUNT]", inventoryElement.AsHtml());
            var htmlKeyString = new HtmlString(txtKeyString);
            var stockText = htmlKeyString.iReplace("[STOCKAMOUNT]", inventoryElement.AsHtml());
 
            val =  StockAmountHelper(stockRange , inventory, stockText);
 
 
            //var stockText = viewHelper.RichText(textKey + textKeySuffix).iReplace("[STOCKAMOUNT]", inventoryElement.AsHtml());
            //return CompiledHelpers.Themed(ProductHelpers.StockAmount, stockRange, inventory, stockText);
 
            //--------
            return val;
        }              
 
 
 
        public string StockAmountHelper(Sana.Commerce.Stock.IStockRange stockRangedecimal inventory, IHtmlString stockText)
        {
            // StockAmount(Sana.Commerce.Stock.IStockRange stockRange, decimal inventory, IHtmlString stockText)
 
            var cssStockState = "low-stock";
            if (inventory <= stockRange.OutOfStock)
            {
                cssStockState = "out-stock";
            }
            else if (inventory > stockRange.LowStock)
            {
                cssStockState = "in-stock";
            }
            var value = "<span class='lbl-stock " + cssStockState + "'> " + stockText + " </span>";
            return value;
 
        }
    }
 
 
 
 
}