November Happy Hour will be moved to Thursday December 5th.

Interface IPriceDetailService

Accesses pricing information for catalog entries.

Namespace: Mediachase.Commerce.Pricing
Assembly: Mediachase.Commerce.dll
Version: 13.30.0
Syntax
public interface IPriceDetailService
Examples
using EPiServer;
using EPiServer.Commerce.Catalog.ContentTypes;
using EPiServer.Core;
using EPiServer.ServiceLocation;
using Mediachase.Commerce;
using Mediachase.Commerce.Catalog;
using Mediachase.Commerce.Core;
using Mediachase.Commerce.Pricing;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;

namespace CodeSamples.Mediachase.Commerce.Pricing
{
/// <summary>
/// This sample shows how to implement a custom pricing provider for managing prices in-memory.
/// </summary>
[ServiceConfiguration(typeof(CustomPricingServiceSample), Lifecycle = ServiceInstanceScope.Singleton)]
public class CustomPricingServiceSample : IPriceService, IPriceDetailService
{
private const string _ReadOnlyExceptionMessage = "Saving prices is not allowed when the Service is readonly.";
private bool _IsReadOnly = false;
private HashSet<IPriceDetailValue> _PriceValues;

/// <summary>
/// Gets the repository for holding prices used by the service.
/// </summary>
protected HashSet<IPriceDetailValue> Prices
{
get
{
    return _PriceValues ?? (_PriceValues = new HashSet<IPriceDetailValue>());
}
}

/// <summary>
/// Gets a value indicating whether or not the price service can modify prices.
/// </summary>
public bool IsReadOnly
{
get
{
    return _IsReadOnly;
}
}

#region  IPriceService implementation

/// <summary>
/// Gets a default price for a catalog entry.  The default price for a market, currency, and catalog entry is the price available
/// to all customers at a minimum quantity of 0.
/// </summary>
/// <param name="market">The market ID for the price.</param>
/// <param name="validOn">The date and time, in UTC, that the price is valid on.</param>
/// <param name="catalogKey">The catalog entry to get pricing for.</param>
/// <param name="currency">The currency for the price.</param>
/// <returns>The default price, or null if a default price is not found.</returns>
public IPriceValue GetDefaultPrice(MarketId market, DateTime validOn, CatalogKey catalogKey, Currency currency)
{
return GetPrices(market, validOn, new[] { catalogKey }, new PriceFilter { Currencies = new[] { currency } })
        .OrderBy(x => x.UnitPrice)
        .FirstOrDefault();
}

/// <summary>
/// Gets filtered pricing for a single catalog entry.
/// </summary>
/// <param name="market">The market for the prices.</param>
/// <param name="validOn">The valid on date for the prices.</param>
/// <param name="catalogKey">The catalog entry.</param>
/// <param name="filter">The price filter.</param>
/// <returns>All price values that match the parameters.</returns>
public IEnumerable<IPriceValue> GetPrices(MarketId market, DateTime validOn, CatalogKey catalogKey, PriceFilter filter)
{
return GetPrices(market, validOn, new[] { catalogKey }, filter);
}

/// <summary>
/// Gets filtered pricing for multiple catalog entries.
/// </summary>
/// <param name="market">The market for the prices.</param>
/// <param name="validOn">The valid on date for the prices.</param>
/// <param name="catalogKeys">The catalog entries.  The enumeration will only be evaluated once.</param>
/// <param name="filter">The price filter.</param>
/// <returns>All price values that match the parameters.</returns>
public IEnumerable<IPriceValue> GetPrices(MarketId market, DateTime validOn, IEnumerable<CatalogKey> catalogKeys, PriceFilter filter)
{
foreach (var catalogKey in catalogKeys)
{
    if (catalogKey == null)
    {
        continue;
    }

    var validModels = GetPriceValues(market, validOn, catalogKey, filter);
    foreach (var validModel in validModels)
    {
        yield return validModel;
    }
}
}

/// <summary>
/// Gets filtered pricing for multiple catalog entries, in varying quantities.
/// </summary>
/// <param name="market">The market for the prices.</param>
/// <param name="validOn">The valid on date for the prices.</param>
/// <param name="catalogKeysAndQuantities">The catalog entries and quantities.  The enumeration will only be evaluated once.</param>
/// <param name="filter">The price filter.  The quantity value in the filter will be ignored, using the quantity in catalogEntriesAndQuantities instead.</param>
/// <returns>All price values that match the parameters.</returns>
public IEnumerable<IPriceValue> GetPrices(MarketId market, DateTime validOn, IEnumerable<CatalogKeyAndQuantity> catalogKeysAndQuantities, PriceFilter filter)
{
foreach (var catalogKeyWithQuantity in catalogKeysAndQuantities)
{
    if (catalogKeyWithQuantity == null || catalogKeyWithQuantity.CatalogKey == null)
    {
        continue;
    }

    filter.Quantity = catalogKeyWithQuantity.Quantity;

    var validModels = GetPriceValues(market, validOn, catalogKeyWithQuantity.CatalogKey, filter);
    foreach (var validModel in validModels)
    {
        yield return validModel;
    }
}
}

/// <summary>
/// Gets all price values for a single catalog entry.
/// </summary>
/// <param name="catalogKey">The catalog entry to fetch prices for.</param>
/// <returns>An enumeration of IPriceValues describing the pricing for the catalog entry.</returns>
public IEnumerable<IPriceValue> GetCatalogEntryPrices(CatalogKey catalogKey)
{
return GetPrices(null, DateTime.UtcNow, new[] { catalogKey }, null);
}

/// <summary>
/// Gets all price values for one or more catalog entries.
/// </summary>
/// <param name="catalogKeys">The catalog entries to fetch prices for.</param>
/// <returns>An enumeration of IPriceValues describing the pricing for the catalog entry.</returns>
public IEnumerable<IPriceValue> GetCatalogEntryPrices(IEnumerable<CatalogKey> catalogKeys)
{
return GetPrices(null, FrameworkContext.Current.CurrentDateTime, catalogKeys, null);
}

/// <summary>
/// Sets the price values for a catalog entry.
/// </summary>
/// <param name="catalogKey">The catalog entry to set prices for.</param>
/// <param name="priceValues">The complete set of price values for the catalog entry.</param>
/// <remarks>
/// <para>To delete pricing for a catalog entry, pass in an empty enumeration of price values.</para>
/// <para>Providers may modify the exact price values stored, so long as the final values represent an identical pricing scheme (for example, to eliminate overlapping values).</para>
/// <para>Will not be implemented when provider is read only.</para>
/// </remarks>
public void SetCatalogEntryPrices(CatalogKey catalogKey, IEnumerable<IPriceValue> priceValues)
{
SetCatalogEntryPrices(new CatalogKey[] { catalogKey }, priceValues);
}

/// <summary>
/// Sets the price values for multiple catalog entries;
/// </summary>
/// <param name="catalogKeys">The catalog entries to set prices for.</param>
/// <param name="priceValues">The complete set of price values for all catalog entries referenced in catalogKeys.</param>
/// <remarks>
/// <para>Any catalog entry listed in catalogKeys but not represented in priceValues will have its pricing data deleted.</para>
/// <para>Providers may modify the exact price values stored, so long as the final values represent an identical pricing scheme (for example, to eliminate overlapping values).</para>
/// <para>Will not be implemented when provider is read only.</para>
/// </remarks>       
public void SetCatalogEntryPrices(IEnumerable<CatalogKey> catalogKeys, IEnumerable<IPriceValue> priceValues)
{
if (IsReadOnly)
{
    throw new InvalidOperationException(_ReadOnlyExceptionMessage);
}

// Iterate through all the provided CatalogKeys.
foreach (var key in catalogKeys)
{
    // Fetch any price values from the provided IPriceValues related to the
    // current CatalogKey.
    var values = from v in priceValues
                 where v.CatalogKey.Equals(key)
                 select v;

    // Always remove the existing prices for the current CatalogKey.
    Prices.RemoveWhere(x => x.CatalogKey == key);

    // Only if there are PriceValues associated to the CatalogKey, the
    // update the prices by adding the key again to the repository.
    if (values != null)
    {
        foreach (var value in values)
        {
            Prices.Add(new PriceDetailValue
            {
                CatalogKey = value.CatalogKey,
                MarketId = value.MarketId,
                CustomerPricing = value.CustomerPricing,
                ValidFrom = value.ValidFrom,
                ValidUntil = value.ValidUntil,
                MinQuantity = value.MinQuantity,
                UnitPrice = value.UnitPrice
            });
        }
    }
}
}

/// <summary>
/// Gets a collection of price values.
/// </summary>
/// <param name="marketId">The id of the market used for getting prices.</param>
/// <param name="validOn">The date prices should be valid for. If null, all prices are returned.</param>
/// <param name="catalogKey">The CatalogKey the prices should be associated with.</param>
/// <param name="filter">Any additional filtering parameters such as currencies, quantity etc.</param>
/// <returns></returns>
private IEnumerable<IPriceDetailValue> GetPriceValues(MarketId marketId, DateTime? validOn, CatalogKey catalogKey, PriceFilter filter)
{
return Prices.Where(model =>
    MatchCatalogKey(catalogKey, model) &&
    MatchDateTime(validOn, model) &&
    MatchMarket(marketId, model) &&  (filter == null ||
    MatchCurrency(filter.Currencies, model) &&
    MatchCustomerPricing(filter.CustomerPricing, model) &&
    MatchQuantity(filter.Quantity, model)));
}

/// <summary>
/// Determines whether the CatalogKey matches the one stated in the provided IPriceValue object.
/// </summary>
/// <param name="catalogKey">The CatalogKey to match against the IPriceValue.</param>
/// <param name="priceValue">The IPriceValue to compare against the CatalogKey.</param>
/// <returns></returns>
private static bool MatchCatalogKey(CatalogKey catalogKey, IPriceValue priceValue)
{
return priceValue.CatalogKey.Equals(catalogKey);
}

/// <summary>
/// Determines whether an IPriceValue is valid wihtin a certain date or not.
/// </summary>
/// <param name="validOn">The date the price should be valid on. If null then the IPriceValue are considered valid per default.</param>
/// <param name="priceValue">The IPriceValue to determine whether its valid or not.</param>
/// <returns>True if the IPriceValue is valid at the specific date.</returns>
private static bool MatchDateTime(DateTime? validOn, IPriceValue priceValue)
{
if (validOn.HasValue)
{
    return priceValue.ValidFrom <= validOn && (priceValue.ValidUntil == null || priceValue.ValidUntil >= validOn);
}

return true;
}

/// <summary>
/// Determines whether a IPriceValue belong to a Market.
/// </summary>
/// <param name="marketId">The id of the market to evaluate.</param>
/// <param name="priceValue">The PriceValue to determine whether it belongs to the market or not.</param>
/// <returns>True if the MarketId of the PriceValue matches the provided MarketId.</returns>
private static bool MatchMarket(MarketId marketId, IPriceValue priceValue)
{
return priceValue.MarketId == null || marketId == null || priceValue.MarketId == marketId;
}

/// <summary>
/// Determines whether an IPriceValue's currency is within a collection of currencies.
/// </summary>
/// <param name="currencies">A collection of currencies that should act as criteria when evaluating the PriceValue.</param>
/// <param name="priceValue">The PriceValue that should be checked against the currency collection.</param>
/// <returns>True if any of the currencies in the collection matches the currency of the PriceValue or if the 
/// currency collection is null or empty. Else returns false.</returns>
private static bool MatchCurrency(IEnumerable<Currency> currencies, IPriceValue priceValue)
{
return priceValue.UnitPrice.Currency == null || currencies == null || !currencies.Any() || currencies.Contains(priceValue.UnitPrice.Currency);
}

/// <summary>
/// Determines whether a IPriceValue has CustomerPricing details and if so if its a member of the CustomerPricing collection criteria.
/// </summary>
/// <param name="customerPricings">A collection of CustomerPricings that are considered valid.</param>
/// <param name="priceValue">The IPriceValue to be evaluated against the CustomerPricing collection.</param>
/// <returns>True if the IPriceValue's CustomerPricing is either null or part of the CustomerPricing collection.</returns>
private static bool MatchCustomerPricing(IEnumerable<CustomerPricing> customerPricings, IPriceValue priceValue)
{
if (priceValue.CustomerPricing == null || customerPricings != null && !customerPricings.Any())
{
    return true;
}

var customerPricingsArray = customerPricings.ToArray();

return (from pricings in customerPricings
        where pricings.PriceCode == priceValue.CustomerPricing.PriceCode
        && pricings.PriceTypeId == priceValue.CustomerPricing.PriceTypeId
        select pricings).Any();
}

/// <summary>
/// Determines whether an IPriceValue's quantity value matches a certain amount.
/// </summary>
/// <param name="quantity">The number of items used as criteria.</param>
/// <param name="priceValue">The IPriceValue to evaluate.</param>
/// <returns>True if the quantity matches the IPriceValue's quantity property, or if qunatity is null. Else returns false.</returns>
private static bool MatchQuantity(decimal? quantity, IPriceValue priceValue)
{
return quantity == null || priceValue.MinQuantity == quantity;
}


#endregion

#region IPriceDetailService

private Injected<IContentLoader> _contentLoader;

/// <summary>
/// Gets or sets the ContentLoader used for getting the the catalog entry code
/// for a perticular ContentReference.
/// </summary>
public Injected<IContentLoader> ContentLoader
{
get { return _contentLoader; }
set { _contentLoader = value; }
}

public IPriceDetailValue Get(long priceValueId)
{
return Prices.SingleOrDefault(x => x.PriceValueId == priceValueId);
}

/// <summary>
/// Gets all price details for the <paramref name="catalogContentReference"/>.
/// </summary>
/// <param name="catalogContentReference">
/// A reference to the catalog entity containing the desired prices.
/// </param>
/// <returns>
/// A collection of <see cref="IPriceDetailValue"/> objects associated with the referenced 
/// catalog entity.
/// </returns>
/// <remarks>
/// The prices returned will vary on the type on entity referenced by the
/// <paramref name="catalogContentReference"/> value.
/// <list type="bullet">
/// <item><description>
/// If the value is a category, then prices will be returned for all products and variants 
/// in the category, 
/// and all variants associated with products in the category.
/// </description></item>
/// <item><description>
/// If the value is a product, then prices will be returned for the product and for all 
/// variants associated with the product.
/// </description></item>
/// <item><description>
/// If the value is a variant, then prices will be returned only for the variant.
/// </description></item>
/// <item><description>
/// If the value is a bundle, package, or dynamic package; then prices will be returned for 
/// the specified entity, all products and variants in the specified entity, and all variants
/// associated with products in the specified entity.
/// </description></item>
/// </list>
/// </remarks>
public IList<IPriceDetailValue> List(ContentReference catalogContentReference)
{
int total;
return List(catalogContentReference, 0, Int32.MaxValue, out total);
}

/// <summary>
/// Gets all price details for the <paramref name="catalogContentReference"/> with paging 
/// support.
/// </summary>
/// <param name="catalogContentReference">
/// A reference to the catalog entity containing the desired prices.
/// </param>
/// <param name="offset">The offset within result set to start at.</param>
/// <param name="count">
/// The number of items to return, starting at <paramref name="offset"/>.
/// </param>
/// <param name="totalCount">
/// The total number of price detail values that exist for <paramref name="catalogContentReference"/>.
/// </param>
/// <returns>
/// A collection of <see cref="IPriceDetailValue"/> objects associated with the referenced
/// catalog entity.
/// </returns>
/// <remarks>
/// Prices returned will vary by the type of item referenced by 
/// <paramref name="catalogContentReference"/>. 
/// </remarks>
public IList<IPriceDetailValue> List(ContentReference catalogContentReference, int offset, int count, out int totalCount)
{
return List(catalogContentReference, null, null, offset, count, out totalCount);
}

/// <summary>
/// Gets all price details for the <paramref name="catalogContentReference"/> with paging 
/// support and filter for market, currencies and customer pricings.
/// </summary>
/// <param name="catalogContentReference">
/// A reference to the catalog entity containing the desired prices.
/// </param>
/// <param name="marketId">Market Id to filter by.</param>
/// <param name="filter">Currencies and CustomerPricing to filter by.</param>
/// <param name="offset">The offset within result set to start at.</param>
/// <param name="count">
/// The number of items to return, starting at <paramref name="offset"/>.
/// </param>
/// <param name="totalCount">
/// The total number of price detail values that exist for <paramref name="catalogContentReference"/>.
/// </param>
/// <returns>
/// A collection of <see cref="IPriceDetailValue"/> objects associated with the referenced
/// catalog entity.
/// </returns>
/// <remarks>
/// Prices returned will vary by the type of item referenced by 
/// <paramref name="catalogContentReference"/>.
/// </remarks>
public IList<IPriceDetailValue> List(ContentReference catalogContentReference, MarketId marketId, PriceFilter filter, int offset, int count, out int totalCount)
{
var entryCode = GetCode(catalogContentReference);
if (String.IsNullOrEmpty(entryCode))
{
    totalCount = 0;
    return new List<IPriceDetailValue>();
}

var prices = GetPriceValues(marketId, null, new CatalogKey(entryCode), filter).ToList();
totalCount = prices.Count;

return prices.Skip(offset).Take(count).ToList();
}

/// <summary>
/// Gets the CatalogEntryCode for a ContentReference.
/// </summary>
/// <param name="catalogContentReference">The ContentLink to get the CatalogEntryCode for.</param>
/// <returns>The CatalogEntryCode for the provided ContentLink.</returns>
private string GetCode(ContentReference catalogContentReference)
{
var entryContent = ContentLoader.Service.Get<EntryContentBase>(catalogContentReference);

return entryContent != null ? entryContent.Code : null;
}

/// <summary>
/// Updates or inserts the specified price values.
/// </summary>
/// <param name="priceValues">The price values to save in the backing store.</param>
/// <returns>The saved values.</returns>
public IList<IPriceDetailValue> Save(IEnumerable<IPriceDetailValue> priceValues)
{
if (IsReadOnly)
{
    throw new InvalidOperationException(_ReadOnlyExceptionMessage);
}

var priceValueList = priceValues.ToList();
foreach (var priceDetailValue in priceValueList)
{
    var existingPrice = Prices.SingleOrDefault(x => x.PriceValueId == priceDetailValue.PriceValueId);
    if (existingPrice != null)
    {
        Prices.Remove(existingPrice);
    }

    // Make sure the new price gets a unique id in the price collection.
    priceDetailValue.PriceValueId = this.Prices.Any() ? this.Prices.Max(x => x.PriceValueId) + 1 : 1;

    Prices.Add(priceDetailValue);
}

return priceValueList;
}

/// <summary>
/// Deletes the specified price values.
/// </summary>
/// <param name="priceValueIds">The identifiers of the price values to delete.</param>
public void Delete(IEnumerable<long> priceValueIds)
{
if (IsReadOnly)
{
    throw new InvalidOperationException(_ReadOnlyExceptionMessage);
}

foreach (var priceValueId in priceValueIds)
{
    var priceValue = Prices.SingleOrDefault(x => x.PriceValueId == priceValueId);
    if (priceValue != null)
    {
        Prices.Remove(priceValue);
    }
}
}

#endregion

#region Ignored methods

/// <summary>
/// Sets the price detail values to reflect changes in <see cref="IPriceService"/>.
/// This should ONLY be used by implementations of <see cref="IPriceService"/>.
/// </summary>
/// <param name="catalogKeySet"></param>
/// <param name="priceValuesList"></param>
public void ReplicatePriceServiceChanges(IEnumerable<CatalogKey> catalogKeySet, IEnumerable<IPriceValue> priceValuesList)
{
// This method is omitted since both IPriceDetailService and IPriceService are implemented by the same class
// and therefor there is no longer a need for keeping two such objects in sync.
throw new NotImplementedException();
}

/// <summary>
/// Sets the price values for catalog entries to reflect changes in <see cref="IPriceDetailService"/>.
/// This should ONLY be used by implementations of <see cref="IPriceDetailService"/>.
/// </summary>
/// <param name="catalogKeys">catalog entries to set prices for.</param>
/// <param name="priceValues">The complete set of price values for all catalog entries referenced in catalogKeys.</param>
public void ReplicatePriceDetailChanges(IEnumerable<CatalogKey> catalogKeys, IEnumerable<IPriceValue> priceValues)
{
// This method is omitted since both IPriceDetailService and IPriceService are implemented by the same class
// and therefor there is no longer a need for keeping two such objects in sync.
throw new NotImplementedException();
}

#endregion

}
}

Properties

IsReadOnly

Gets a value indicating whether or not the price service can modify prices.

Declaration
bool IsReadOnly { get; }
Property Value
Type Description
System.Boolean

Methods

Delete(IEnumerable<Int64>)

Deletes the specified price values.

Declaration
void Delete(IEnumerable<long> priceValueIds)
Parameters
Type Name Description
System.Collections.Generic.IEnumerable<System.Int64> priceValueIds

The identifiers of the price values to delete.

Get(Int64)

Gets the price value identified by priceValueId.

Declaration
IPriceDetailValue Get(long priceValueId)
Parameters
Type Name Description
System.Int64 priceValueId

The idenfitier for the price value.

Returns
Type Description
IPriceDetailValue

List(ContentReference)

Gets all price details for the catalogContentReference.

Declaration
IList<IPriceDetailValue> List(ContentReference catalogContentReference)
Parameters
Type Name Description
EPiServer.Core.ContentReference catalogContentReference

A reference to the catalog entity containing the desired prices.

Returns
Type Description
System.Collections.Generic.IList<IPriceDetailValue>

A collection of IPriceDetailValue objects associated with the referenced catalog entity.

Remarks

The prices returned will vary on the type on entity referenced by the catalogContentReference value.

  • If the value is a category, then prices will be returned for all products and variants in the category, and all variants associated with products in the category.
  • If the value is a product, then prices will be returned for the product and for all variants associated with the product.
  • If the value is a variant, then prices will be returned only for the variant.
  • If the value is a bundle, package, or dynamic package; then prices will be returned for the specified entity, all products and variants in the specified entity, and all variants associated with products in the specified entity.

List(ContentReference, MarketId, PriceFilter, Int32, Int32, out Int32)

Gets all price details for the catalogContentReference with paging support and filter for market, currencies and customer pricings.

Declaration
IList<IPriceDetailValue> List(ContentReference catalogContentReference, MarketId marketId, PriceFilter filter, int offset, int count, out int totalCount)
Parameters
Type Name Description
EPiServer.Core.ContentReference catalogContentReference

A reference to the catalog entity containing the desired prices.

MarketId marketId

Market Id to filter by.

PriceFilter filter

Currencies and CustomerPricing to filter by.

System.Int32 offset

The offset within result set to start at.

System.Int32 count

The number of items to return, starting at offset.

System.Int32 totalCount

The total number of price detail values that exist for catalogContentReference.

Returns
Type Description
System.Collections.Generic.IList<IPriceDetailValue>

A collection of IPriceDetailValue objects associated with the referenced catalog entity.

Remarks

Prices returned will vary by the type of item referenced by catalogContentReference.

List(ContentReference, Int32, Int32, out Int32)

Gets all price details for the catalogContentReference with paging support.

Declaration
IList<IPriceDetailValue> List(ContentReference catalogContentReference, int offset, int count, out int totalCount)
Parameters
Type Name Description
EPiServer.Core.ContentReference catalogContentReference

A reference to the catalog entity containing the desired prices.

System.Int32 offset

The offset within result set to start at.

System.Int32 count

The number of items to return, starting at offset.

System.Int32 totalCount

The total number of price detail values that exist for catalogContentReference.

Returns
Type Description
System.Collections.Generic.IList<IPriceDetailValue>

A collection of IPriceDetailValue objects associated with the referenced catalog entity.

Remarks

Prices returned will vary by the type of item referenced by catalogContentReference.

ReplicatePriceServiceChanges(IEnumerable<CatalogKey>, IEnumerable<IPriceValue>)

Sets the price detail values to reflect changes in IPriceService. This should ONLY be used by implementations of IPriceService.

Declaration
void ReplicatePriceServiceChanges(IEnumerable<CatalogKey> catalogKeySet, IEnumerable<IPriceValue> priceValuesList)
Parameters
Type Name Description
System.Collections.Generic.IEnumerable<CatalogKey> catalogKeySet
System.Collections.Generic.IEnumerable<IPriceValue> priceValuesList

Save(IEnumerable<IPriceDetailValue>)

Updates or inserts the specified price values.

Declaration
IList<IPriceDetailValue> Save(IEnumerable<IPriceDetailValue> priceValues)
Parameters
Type Name Description
System.Collections.Generic.IEnumerable<IPriceDetailValue> priceValues

The price values to save in the backing store.

Returns
Type Description
System.Collections.Generic.IList<IPriceDetailValue>

The saved values.

Extension Methods