Skip to content

Commit 4e6cc3c

Browse files
committed
Resolves smartstore#732 Product list: Option to display the pre-selected price instead of the lowest price
1 parent d7cb1cb commit 4e6cc3c

12 files changed

Lines changed: 255 additions & 308 deletions

File tree

src/Libraries/SmartStore.Core/Domain/Catalog/CatalogSettings.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -353,6 +353,11 @@ public CatalogSettings()
353353
/// </summary>
354354
public string DefaultPageSizeOptions { get; set; }
355355

356+
/// <summary>
357+
/// Gets or sets the price display type for prices in product lists
358+
/// </summary>
359+
public PriceDisplayType PriceDisplayType { get; set; }
360+
356361
/// <summary>
357362
/// Gets or sets a value indicating whether to include "Short description" in compare products
358363
/// </summary>
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+

2+
namespace SmartStore.Core.Domain.Catalog
3+
{
4+
/// <summary>
5+
/// Represents types of product prices to display
6+
/// </summary>
7+
public enum PriceDisplayType
8+
{
9+
/// <summary>
10+
/// The lowest possible price of a product (default)
11+
/// </summary>
12+
LowestPrice = 0,
13+
14+
/// <summary>
15+
/// The product price initially displayed on the product detail page
16+
/// </summary>
17+
PreSelectedPrice = 10,
18+
19+
/// <summary>
20+
/// The product price without associated data like discounts, tier prices, attributes or attribute combinations
21+
/// </summary>
22+
PriceWithoutDiscountsAndAttributes = 20,
23+
24+
/// <summary>
25+
/// Do not display a product price
26+
/// </summary>
27+
Hide = 30
28+
}
29+
}

src/Libraries/SmartStore.Core/SmartStore.Core.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@
157157
<Compile Include="Collections\QuerystringBuilder.cs" />
158158
<Compile Include="Collections\TopologicalSorter.cs" />
159159
<Compile Include="Data\ITransaction.cs" />
160+
<Compile Include="Domain\Catalog\PriceDisplayType.cs" />
160161
<Compile Include="Domain\Directory\QuantityUnit.cs" />
161162
<Compile Include="Domain\Common\RestrictionContextTypes.cs" />
162163
<Compile Include="Domain\Messages\EmailAttachmentStorageLocation.cs" />

src/Libraries/SmartStore.Data/Migrations/201507210952098_PaymentMethodDescription.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,28 @@ public void MigrateLocaleResources(LocaleResourcesBuilder builder)
3939
"Langtext",
4040
"Specifies a full description of the payment method. It appears in the payment list in checkout.",
4141
"Legt eine vollständige Beschreibung der Zahlungsmethode fest. Sie erscheint in der Zahlungsliste im Kassenbereich.");
42+
43+
builder.AddOrUpdate("Admin.Configuration.Settings.Catalog.PriceDisplayType",
44+
"Price display",
45+
"Preisanzeige",
46+
"Specifies whether or what type of price to be displayed in product lists.",
47+
"Legt fest, ob bzw. welcher Typ von Preis in Produktlisten angezeigt werden soll.");
48+
49+
builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.PriceDisplayType.LowestPrice",
50+
"Minimum feasible price",
51+
"Minimal realisierbarer Preis");
52+
53+
builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.PriceDisplayType.PreSelectedPrice",
54+
"Preselected price",
55+
"Vorgewählter Preis");
56+
57+
builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.PriceDisplayType.PriceWithoutDiscountsAndAttributes",
58+
"Price without discounts and attributes",
59+
"Preis ohne Rabatte und Attribute");
60+
61+
builder.AddOrUpdate("Enums.SmartStore.Core.Domain.Catalog.PriceDisplayType.Hide",
62+
"No price indication",
63+
"Keine Preisanzeige");
4264
}
4365
}
4466
}

src/Libraries/SmartStore.Services/Catalog/IPriceCalculationService.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,9 +92,10 @@ decimal GetFinalPrice(Product product, IEnumerable<ProductBundleItemData> bundle
9292
/// Get the lowest possible price for a product.
9393
/// </summary>
9494
/// <param name="product">Product</param>
95+
/// <param name="context">Object with cargo data for better performance</param>
9596
/// <param name="displayFromMessage">Whether to display the from message.</param>
9697
/// <returns>The lowest price.</returns>
97-
decimal GetLowestPrice(Product product, out bool displayFromMessage);
98+
decimal GetLowestPrice(Product product, PriceCalculationContext context, out bool displayFromMessage);
9899

99100
/// <summary>
100101
/// Get the lowest price of a grouped product.

src/Libraries/SmartStore.Services/Catalog/PriceCalculationService.cs

Lines changed: 40 additions & 144 deletions
Original file line numberDiff line numberDiff line change
@@ -243,104 +243,13 @@ protected virtual decimal GetPreselectedPrice(Product product, PriceCalculationC
243243
return result;
244244
}
245245

246-
//protected virtual decimal GetPreselectedPriceOld(Product product, ProductBundleItemData bundleItem, IList<ProductBundleItemData> bundleItems)
247-
//{
248-
// var taxRate = decimal.Zero;
249-
// var attributesTotalPriceBase = decimal.Zero;
250-
// var preSelectedPriceAdjustmentBase = decimal.Zero;
251-
// var isBundle = (product.ProductType == ProductType.BundledProduct);
252-
// var isBundleItemPricing = (bundleItem != null && bundleItem.Item.BundleProduct.BundlePerItemPricing);
253-
// var isBundlePricing = (bundleItem != null && !bundleItem.Item.BundleProduct.BundlePerItemPricing);
254-
// var bundleItemId = (bundleItem == null ? 0 : bundleItem.Item.Id);
255-
// var attributes = (isBundle ? new List<ProductVariantAttribute>() : _productAttributeService.GetProductVariantAttributesByProductId(product.Id));
256-
// var selectedAttributes = new NameValueCollection();
257-
// List<ProductVariantAttributeValue> selectedAttributeValues = null;
258-
259-
// foreach (var attribute in attributes)
260-
// {
261-
// int preSelectedValueId = 0;
262-
// ProductVariantAttributeValue defaultValue = null;
263-
264-
// if (attribute.ShouldHaveValues())
265-
// {
266-
// var pvaValues = _productAttributeService.GetProductVariantAttributeValues(attribute.Id);
267-
// if (pvaValues.Count == 0)
268-
// continue;
269-
270-
// foreach (var pvaValue in pvaValues)
271-
// {
272-
// ProductBundleItemAttributeFilter attributeFilter = null;
273-
274-
// if (bundleItem.FilterOut(pvaValue, out attributeFilter))
275-
// continue;
276-
277-
// if (preSelectedValueId == 0 && attributeFilter != null && attributeFilter.IsPreSelected)
278-
// preSelectedValueId = attributeFilter.AttributeValueId;
279-
280-
// if (!isBundlePricing && pvaValue.IsPreSelected)
281-
// {
282-
// decimal attributeValuePriceAdjustment = GetProductVariantAttributeValuePriceAdjustment(pvaValue);
283-
// decimal priceAdjustmentBase = _taxService.GetProductPrice(product, attributeValuePriceAdjustment, out taxRate);
284-
285-
// preSelectedPriceAdjustmentBase = decimal.Add(preSelectedPriceAdjustmentBase, priceAdjustmentBase);
286-
// }
287-
// }
288-
289-
// // value pre-selected by a bundle item filter discards the default pre-selection
290-
// if (preSelectedValueId != 0 && (defaultValue = pvaValues.FirstOrDefault(x => x.Id == preSelectedValueId)) != null)
291-
// defaultValue.IsPreSelected = true;
292-
293-
// if (defaultValue == null)
294-
// defaultValue = pvaValues.FirstOrDefault(x => x.IsPreSelected);
295-
296-
// if (defaultValue == null && attribute.IsRequired)
297-
// defaultValue = pvaValues.First();
298-
299-
// if (defaultValue != null)
300-
// selectedAttributes.AddProductAttribute(attribute.ProductAttributeId, attribute.Id, defaultValue.Id, product.Id, bundleItemId);
301-
// }
302-
// }
303-
304-
// if (!isBundle && selectedAttributes.Count > 0)
305-
// {
306-
// string attributeXml = selectedAttributes.CreateSelectedAttributesXml(product.Id, attributes, _productAttributeParser, _services.Localization,
307-
// _downloadService, _catalogSettings, _httpRequestBase, new List<string>(), true, bundleItemId);
308-
309-
// selectedAttributeValues = _productAttributeParser.ParseProductVariantAttributeValues(attributeXml).ToList();
310-
311-
// var combinations = _productAttributeService.GetAllProductVariantAttributeCombinations(product.Id);
312-
313-
// var selectedCombination = combinations.FirstOrDefault(x => _productAttributeParser.AreProductAttributesEqual(x.AttributesXml, attributeXml));
314-
315-
// if (selectedCombination != null && selectedCombination.IsActive)
316-
// product.MergeWithCombination(selectedCombination);
317-
// }
318-
319-
// if (_catalogSettings.EnableDynamicPriceUpdate && !isBundlePricing)
320-
// {
321-
// if (selectedAttributeValues != null)
322-
// {
323-
// selectedAttributeValues.Each(x => attributesTotalPriceBase += GetProductVariantAttributeValuePriceAdjustment(x));
324-
// }
325-
// else
326-
// {
327-
// attributesTotalPriceBase = preSelectedPriceAdjustmentBase;
328-
// }
329-
// }
330-
331-
// if (bundleItem != null)
332-
// {
333-
// bundleItem.AdditionalCharge = attributesTotalPriceBase;
334-
// }
335-
336-
// var result = GetFinalPrice(product, bundleItems, _services.WorkContext.CurrentCustomer, attributesTotalPriceBase, true, 1, bundleItem);
337-
// return result;
338-
//}
339-
340246
public virtual PriceCalculationContext CreatePriceCalculationContext(IEnumerable<int> productIds = null)
341247
{
342248
var context = new PriceCalculationContext();
343249

250+
if (_catalogSettings.PriceDisplayType == PriceDisplayType.Hide || _catalogSettings.PriceDisplayType == PriceDisplayType.PriceWithoutDiscountsAndAttributes)
251+
return context;
252+
344253
context.Attributes = new LazyMultimap<ProductVariantAttribute>(x =>
345254
{
346255
"load {0}".FormatInvariant(string.Join(";", x)).Dump();
@@ -349,11 +258,14 @@ public virtual PriceCalculationContext CreatePriceCalculationContext(IEnumerable
349258
return result;
350259
}, productIds);
351260

352-
context.AttributeCombinations = new LazyMultimap<ProductVariantAttributeCombination>(x =>
261+
if (_catalogSettings.PriceDisplayType == PriceDisplayType.PreSelectedPrice)
353262
{
354-
var result = _productAttributeService.GetProductVariantAttributeCombinations(x);
355-
return result;
356-
}, productIds);
263+
context.AttributeCombinations = new LazyMultimap<ProductVariantAttributeCombination>(x =>
264+
{
265+
var result = _productAttributeService.GetProductVariantAttributeCombinations(x);
266+
return result;
267+
}, productIds);
268+
}
357269

358270
return context;
359271
}
@@ -527,48 +439,27 @@ public virtual decimal GetFinalPrice(
527439
/// Get the lowest possible price for a product.
528440
/// </summary>
529441
/// <param name="product">Product</param>
442+
/// <param name="context">Object with cargo data for better performance</param>
530443
/// <param name="displayFromMessage">Whether to display the from message.</param>
531444
/// <returns>The lowest price.</returns>
532-
public virtual decimal GetLowestPrice(Product product, out bool displayFromMessage)
445+
public virtual decimal GetLowestPrice(Product product, PriceCalculationContext context, out bool displayFromMessage)
533446
{
534447
if (product == null)
535448
throw new ArgumentNullException("product");
536449

537450
if (product.ProductType == ProductType.GroupedProduct)
538451
throw Error.InvalidOperation("Choose the other override for products of type grouped product.");
539452

540-
displayFromMessage = false;
453+
if (context == null)
454+
context = CreatePriceCalculationContext(null);
541455

542-
IList<ProductBundleItemData> bundleItems = null;
543456
bool isBundlePerItemPricing = (product.ProductType == ProductType.BundledProduct && product.BundlePerItemPricing);
544457

545-
if (isBundlePerItemPricing)
546-
{
547-
bundleItems = _productService.GetBundleItems(product.Id);
458+
displayFromMessage = isBundlePerItemPricing;
548459

549-
// sepcial case: one bundle item with one attribute and one attribute value and one attribute combination
550-
if (bundleItems.Count == 1)
551-
{
552-
var firstBundleItem = bundleItems.First();
553-
if (firstBundleItem.Item.Product.ProductVariantAttributes.Count == 1)
554-
{
555-
var firstAttribute = firstBundleItem.Item.Product.ProductVariantAttributes.First();
556-
if (firstAttribute.ProductVariantAttributeValues.Count == 1)
557-
{
558-
var firstAttributeValue = firstAttribute.ProductVariantAttributeValues.First();
559-
firstBundleItem.AdditionalCharge = firstAttributeValue.PriceAdjustment;
560-
561-
var combinations = _productAttributeService.GetAllProductVariantAttributeCombinations(firstBundleItem.Item.ProductId);
562-
if (combinations.Count == 1)
563-
{
564-
firstBundleItem.Item.Product.MergeWithCombination(combinations.First());
565-
}
566-
}
567-
}
568-
}
569-
}
460+
// TODO(?): attribute price adjustments were not regarded here cause of performance
570461

571-
decimal lowestPrice = GetFinalPrice(product, bundleItems, _services.WorkContext.CurrentCustomer, decimal.Zero, true, int.MaxValue);
462+
decimal lowestPrice = GetFinalPrice(product, null, _services.WorkContext.CurrentCustomer, decimal.Zero, true, int.MaxValue);
572463

573464
if (product.LowestAttributeCombinationPrice.HasValue && product.LowestAttributeCombinationPrice.Value < lowestPrice)
574465
{
@@ -581,6 +472,24 @@ public virtual decimal GetLowestPrice(Product product, out bool displayFromMessa
581472
lowestPrice = product.LowestAttributeCombinationPrice ?? decimal.Zero;
582473
}
583474

475+
if (!displayFromMessage && product.ProductType != ProductType.BundledProduct)
476+
{
477+
var attributes = context.Attributes.Ensure(product.Id);
478+
displayFromMessage = attributes.Any(x => x.ProductVariantAttributeValues.Any(y => y.PriceAdjustment != decimal.Zero));
479+
}
480+
481+
if (!displayFromMessage && product.HasTierPrices && !isBundlePerItemPricing)
482+
{
483+
var tierPrices = product.TierPrices
484+
.OrderBy(tp => tp.Quantity)
485+
.FilterByStore(_services.StoreContext.CurrentStore.Id)
486+
.FilterForCustomer(_services.WorkContext.CurrentCustomer)
487+
.ToList()
488+
.RemoveDuplicatedQuantities();
489+
490+
displayFromMessage = (tierPrices.Count > 0 && !(tierPrices.Count == 1 && tierPrices.First().Quantity <= 1));
491+
}
492+
584493
return lowestPrice;
585494
}
586495

@@ -602,7 +511,7 @@ public virtual decimal GetLowestPrice(Product product, out bool displayFromMessa
602511
if (product.ProductType != ProductType.GroupedProduct)
603512
throw Error.InvalidOperation("Choose the other override for products not of type grouped product.");
604513

605-
lowestPriceProduct = product;
514+
lowestPriceProduct = null;
606515
decimal? lowestPrice = null;
607516

608517
foreach (var associatedProduct in associatedProducts)
@@ -620,6 +529,10 @@ public virtual decimal GetLowestPrice(Product product, out bool displayFromMessa
620529
lowestPriceProduct = associatedProduct;
621530
}
622531
}
532+
533+
if (lowestPriceProduct == null)
534+
lowestPriceProduct = associatedProducts.FirstOrDefault();
535+
623536
return lowestPrice;
624537
}
625538

@@ -660,23 +573,6 @@ public virtual decimal GetPreselectedPrice(Product product, PriceCalculationCont
660573
result = GetPreselectedPrice(product, context, null, null);
661574
}
662575

663-
//if (product.ProductType == ProductType.BundledProduct)
664-
//{
665-
// var bundleItems = _productService.GetBundleItems(product.Id);
666-
667-
// foreach (var bundleItem in bundleItems.Where(x => x.Item.Product.CanBeBundleItem()))
668-
// {
669-
// // fetch bundleItems.AdditionalCharge for all bundle items
670-
// var unused = GetPreselectedPriceOld(bundleItem.Item.Product, bundleItem, bundleItems);
671-
// }
672-
673-
// result = GetPreselectedPriceOld(product, null, bundleItems);
674-
//}
675-
//else
676-
//{
677-
// result = GetPreselectedPriceOld(product, null, null);
678-
//}
679-
680576
return result;
681577
}
682578

src/Plugins/SmartStore.WebApi/Controllers/OData/ProductsController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -266,7 +266,7 @@ public IQueryable<ProductBundleItem> GetProductBundleItems(int key)
266266
else
267267
{
268268
bool displayFromMessage;
269-
result = _priceCalculationService.Value.GetLowestPrice(entity, out displayFromMessage);
269+
result = _priceCalculationService.Value.GetLowestPrice(entity, null, out displayFromMessage);
270270
}
271271
}
272272
else

src/Presentation/SmartStore.Web/Administration/Controllers/SettingController.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -558,6 +558,7 @@ public ActionResult Catalog()
558558
StoreDependingSettings.GetOverrideKeys(catalogSettings, model, storeScope, _services.Settings);
559559

560560
model.AvailableSubCategoryDisplayTypes = catalogSettings.SubCategoryDisplayType.ToSelectList();
561+
model.AvailablePriceDisplayTypes = catalogSettings.PriceDisplayType.ToSelectList();
561562

562563
model.AvailableDefaultViewModes.Add(
563564
new SelectListItem { Value = "grid", Text = _services.Localization.GetResource("Common.Grid"), Selected = model.DefaultViewMode.IsCaseInsensitiveEqual("grid") }

src/Presentation/SmartStore.Web/Administration/Infrastructure/AutoMapperStartupTask.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -618,6 +618,7 @@ public void Execute()
618618
.ForMember(dest => dest.ReturnValidOptionsIfThereAreAny, mo => mo.Ignore());
619619
Mapper.CreateMap<CatalogSettings, CatalogSettingsModel>()
620620
.ForMember(dest => dest.AvailableSubCategoryDisplayTypes, mo => mo.Ignore())
621+
.ForMember(dest => dest.AvailablePriceDisplayTypes, mo => mo.Ignore())
621622
.ForMember(dest => dest.AvailableDefaultViewModes, mo => mo.Ignore())
622623
.ForMember(dest => dest.AvailableDeliveryTimes, mo => mo.Ignore());
623624
Mapper.CreateMap<CatalogSettingsModel, CatalogSettings>()

src/Presentation/SmartStore.Web/Administration/Models/Settings/CatalogSettingsModel.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,10 @@ public CatalogSettingsModel()
115115
[SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.DefaultPageSizeOptions")]
116116
public string DefaultPageSizeOptions { get; set; }
117117

118+
[SmartResourceDisplayName("Admin.Configuration.Settings.Catalog.PriceDisplayType")]
119+
public PriceDisplayType PriceDisplayType { get; set; }
120+
public SelectList AvailablePriceDisplayTypes { get; set; }
121+
118122
#endregion
119123

120124
#region Products

0 commit comments

Comments
 (0)