Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
namespace OrchardCore.Commerce.Abstractions;

public class HeadersDisplayNamesOptions
{
public string Price { get; set; } = "Price";
public string NetPrice { get; set; } = "Net Price";
public string GrossPrice { get; set; } = "Gross Price";
public string OldPrice { get; set; } = "Old Price";
public string Product { get; set; } = "Product";
public string Quantity { get; set; } = "Quantity";
public string Action { get; set; } = "Action";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
using Microsoft.AspNetCore.Mvc.Localization;
using OrchardCore.Commerce.Abstractions.Abstractions;
using System.Collections.Generic;

namespace OrchardCore.Commerce.Abstractions.Helpers;

public static class LocalizationHelpers
{
public static IList<LocalizedHtmlString> GetLocalizedShoppingCartHeaders(IHtmlLocalizer<HeadersDisplayNamesOptions> htmlLocalizer, HeadersDisplayNamesOptions options) =>
[
htmlLocalizer[options.Product],
htmlLocalizer[options.Quantity],
htmlLocalizer[options.Price],
htmlLocalizer[options.Action],
];

/// <summary>
/// Gets the localized string for Net Price.
/// </summary>
public static LocalizedHtmlString GetLocalizedNetPriceName(
IHtmlLocalizer<HeadersDisplayNamesOptions> htmlLocalizer,
HeadersDisplayNamesOptions options) =>
htmlLocalizer[options.NetPrice];

/// <summary>
/// Gets the localized string for Gross Price.
/// </summary>
public static LocalizedHtmlString GetLocalizedGrossPriceName(
IHtmlLocalizer<HeadersDisplayNamesOptions> htmlLocalizer,
HeadersDisplayNamesOptions options) =>
htmlLocalizer[options.GrossPrice];

/// <summary>
/// Formats a localized price label alongside an amount/total.
/// (e.g., "Net Price : $10.00")
/// </summary>
public static LocalizedHtmlString FormatPriceWithLabel(
IHtmlLocalizer<HeadersDisplayNamesOptions> htmlLocalizer,
string priceName,
object amount) =>
htmlLocalizer["{0} : {1}", htmlLocalizer[priceName], amount];
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,10 @@
@using Microsoft.Extensions.Options;
@using Microsoft.AspNetCore.Mvc.Localization
@using OrchardCore.Commerce.Abstractions
@using OrchardCore.Commerce.Abstractions.Helpers
@using OrchardCore.Commerce.MoneyDataType
@inject IOptions<HeadersDisplayNamesOptions> options;
@inject IHtmlLocalizer<HeadersDisplayNamesOptions> HtmlLocalizer;

@{
if (Model.DefaultTotal is not Amount defaultTotal)
Expand All @@ -19,12 +25,12 @@

@* Net Price is always applicable, while Gross Price can only be shown if it's specified. *@
<div>
<strong>@T["Net Price: {0}", Model.NetTotal]</strong>
<strong>@LocalizationHelpers.FormatPriceWithLabel(HtmlLocalizer, options.Value.NetPrice, Model.NetTotal)</strong>
</div>
@if (Model.GrossTotal != null && Model.GrossTotal.Value != 0)
{
<div>
<strong>@T["Gross Price: {0}", Model.GrossTotal]</strong>
<strong>@LocalizationHelpers.FormatPriceWithLabel(HtmlLocalizer, options.Value.GrossPrice, Model.GrossTotal)</strong>
</div>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The options.Value.NetPrice and options.Value.GrossPrice are not localized here. Add a helper to TableHeaders for these, because they recur in other places as well.

}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
using Microsoft.Extensions.Options;
using OrchardCore.Commerce.Abstractions;
using OrchardCore.Commerce.ContentFields.Settings;
using OrchardCore.Commerce.Tax.Models;
using OrchardCore.ContentFields.Settings;
Expand All @@ -13,10 +15,14 @@ namespace OrchardCore.Commerce.Tax.Migrations;
/// </summary>
public class TaxPartMigrations : DataMigration
{
private readonly HeadersDisplayNamesOptions _options;
private readonly IContentDefinitionManager _contentDefinitionManager;

public TaxPartMigrations(IContentDefinitionManager contentDefinitionManager) =>
public TaxPartMigrations(IContentDefinitionManager contentDefinitionManager, IOptions<HeadersDisplayNamesOptions> priceDisplayNameOptions)
{
_contentDefinitionManager = contentDefinitionManager;
_options = priceDisplayNameOptions.Value;
}

public async Task<int> CreateAsync()
{
Expand All @@ -33,7 +39,7 @@ await _contentDefinitionManager
Hint = "The ID that identifies the product of product category for taxing purposes.",
}))
.WithField(part => part.GrossPrice, part => part
.WithDisplayName("Gross Price")
.WithDisplayName(_options.GrossPrice)
.WithSettings(new PriceFieldSettings
{
Hint = "The price with tax. If specified along with the Tax Rate, then Price content part is " +
Expand All @@ -43,7 +49,7 @@ await _contentDefinitionManager
.WithDisplayName("Tax Rate")
.WithSettings(new NumericFieldSettings
{
Hint = "The tax percentage of the net price, which is added to get the gross price.",
Hint = $"The tax percentage of the {_options.NetPrice}, which is added to get the {_options.GrossPrice}.",
Minimum = 0,
Maximum = 100,
}))
Expand Down
1 change: 1 addition & 0 deletions src/Modules/OrchardCore.Commerce/CommerceConstants.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ public static class Features
public const string CurrencySettingsSelector = $"{Core}.{nameof(CurrencySettingsSelector)}";
public const string Subscription = $"{Core}.{nameof(Subscription)}";
public const string SkuGeneratorGuid = $"{SkuGenerator}.Guid";
public const string HeadersDisplayNames = $"{Core}.{nameof(HeadersDisplayNames)}";
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.Options;
using OrchardCore.Commerce.Abstractions;
using OrchardCore.Commerce.Abstractions.ViewModels;
using OrchardCore.Commerce.Models;
Expand All @@ -17,18 +18,21 @@ public class PromotionShoppingCartEvents : ShoppingCartEventsBase
private readonly IClock _clock;
private readonly IHtmlLocalizer<PromotionShoppingCartEvents> H;
private readonly IPromotionService _promotionService;
private readonly HeadersDisplayNamesOptions _priceDisplayNameOptions;

// Promotions should be applied after taxes.
public override int Order => int.MaxValue;

public PromotionShoppingCartEvents(
IClock clock,
IHtmlLocalizer<PromotionShoppingCartEvents> htmlLocalizer,
IOptions<HeadersDisplayNamesOptions> priceDisplayNameOptions,
IPromotionService promotionService)
{
_clock = clock;
H = htmlLocalizer;
_promotionService = promotionService;
_priceDisplayNameOptions = priceDisplayNameOptions.Value;
}

public override async Task<(IList<LocalizedHtmlString> Headers, IList<ShoppingCartLineViewModel> Lines)> DisplayingAsync(
Expand All @@ -50,12 +54,12 @@ public PromotionShoppingCartEvents(

var newHeaders = headers.ToList();

var netPriceExists = newHeaders.Exists(header => header.Name == "Net Price");
var netPriceExists = newHeaders.Exists(header => header.Name == _priceDisplayNameOptions.NetPrice);
var insertIndex = netPriceExists
? newHeaders.FindIndex(header => header.Name == "Net Price")
: newHeaders.FindIndex(header => header.Name is "Price" or "Gross Price");
? newHeaders.FindIndex(header => header.Name == _priceDisplayNameOptions.NetPrice)
: newHeaders.FindIndex(header => header.Name == _priceDisplayNameOptions.Price || header.Name == _priceDisplayNameOptions.GrossPrice);

newHeaders.Insert(insertIndex, H["Old Price"]);
newHeaders.Insert(insertIndex, H[_priceDisplayNameOptions.OldPrice]);

foreach (var (price, index) in lines.Select((item, index) => (item.UnitPrice, index)))
{
Expand Down
10 changes: 7 additions & 3 deletions src/Modules/OrchardCore.Commerce/Events/TaxShoppingCartEvents.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.Options;
using OrchardCore.Commerce.Abstractions;
using OrchardCore.Commerce.Abstractions.ViewModels;
using OrchardCore.Commerce.Models;
Expand All @@ -12,17 +13,20 @@ namespace OrchardCore.Commerce.Events;

public class TaxShoppingCartEvents : ShoppingCartEventsBase
{
private readonly HeadersDisplayNamesOptions _options;
private readonly IHtmlLocalizer<TaxShoppingCartEvents> H;
private readonly IEnumerable<ITaxProvider> _taxProviders;
private readonly ISiteService _siteService;

public override int Order => 0;

public TaxShoppingCartEvents(
IOptions<HeadersDisplayNamesOptions> priceDisplayNameOptions,
IHtmlLocalizer<TaxShoppingCartEvents> htmlLocalizer,
IEnumerable<ITaxProvider> taxProviders,
ISiteService siteService)
{
_options = priceDisplayNameOptions.Value;
H = htmlLocalizer;
_taxProviders = taxProviders;
_siteService = siteService;
Expand Down Expand Up @@ -60,15 +64,15 @@ public TaxShoppingCartEvents(
}

var newHeaders = headers
.Select(header => header.Name == "Price" ? H["Gross Price"] : header)
.Select(header => header.Name == _options.Price ? H[_options.GrossPrice] : header)
.ToList();

// When taxes are specified, Gross Price is always applicable, while Net Price is optional.
var priceDisplaySettings = (await _siteService.GetSiteSettingsAsync()).GetOrCreate<PriceDisplaySettings>();
if (priceDisplaySettings.UseNetPriceDisplay)
{
var grossIndex = newHeaders.FindIndex(header => header.Name == "Gross Price");
newHeaders.Insert(grossIndex, H["Net Price"]);
var grossIndex = newHeaders.FindIndex(header => header.Name == _options.GrossPrice);
newHeaders.Insert(grossIndex, H[_options.NetPrice]);
}

return (newHeaders, lines);
Expand Down
8 changes: 8 additions & 0 deletions src/Modules/OrchardCore.Commerce/Manifest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"OrchardCore.Templates",
"OrchardCore.Commerce.ContentFields",
"OrchardCore.Commerce.Payment",
CommerceConstants.Features.HeadersDisplayNames,
]
)]

Expand Down Expand Up @@ -70,3 +71,10 @@
CommerceConstants.Features.Core,
]
)]

[assembly: Feature(
Id = CommerceConstants.Features.HeadersDisplayNames,
Name = "Orchard Core Commerce - Display Names",
Category = "Commerce",
Description = "Allows configuring display names."
)]
19 changes: 11 additions & 8 deletions src/Modules/OrchardCore.Commerce/Services/ShoppingCartHelpers.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using Lombiq.HelpfulLibraries.AspNetCore.Exceptions;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.Localization;
using Microsoft.Extensions.Options;
using OrchardCore.Commerce.Abstractions;
using OrchardCore.Commerce.Abstractions.Abstractions;
using OrchardCore.Commerce.Abstractions.Helpers;
using OrchardCore.Commerce.Abstractions.Models;
using OrchardCore.Commerce.Abstractions.ViewModels;
using OrchardCore.Commerce.AddressDataType;
Expand All @@ -20,6 +22,7 @@ namespace OrchardCore.Commerce.Services;

public class ShoppingCartHelpers : IShoppingCartHelpers
{
private readonly HeadersDisplayNamesOptions _options;
private readonly IHttpContextAccessor _hca;
private readonly IPriceSelectionStrategy _priceSelectionStrategy;
private readonly IPriceService _priceService;
Expand All @@ -30,6 +33,7 @@ public class ShoppingCartHelpers : IShoppingCartHelpers
private readonly IShoppingCartPersistence _shoppingCartPersistence;
private readonly IShoppingCartSerializer _shoppingCartSerializer;
private readonly IHtmlLocalizer<ShoppingCartHelpers> H;
private readonly IHtmlLocalizer<HeadersDisplayNamesOptions> _htmlLocalizer;

[SuppressMessage(
"Major Code Smell",
Expand All @@ -45,7 +49,9 @@ public ShoppingCartHelpers(
IEnumerable<IShoppingCartEvents> shoppingCartEvents,
IShoppingCartPersistence shoppingCartPersistence,
IShoppingCartSerializer shoppingCartSerializer,
IHtmlLocalizer<ShoppingCartHelpers> localizer)
IOptions<HeadersDisplayNamesOptions> priceDisplayNameOptions,
IHtmlLocalizer<ShoppingCartHelpers> localizer,
IHtmlLocalizer<HeadersDisplayNamesOptions> htmlLocalizer)
{
_hca = hca;
_priceSelectionStrategy = priceSelectionStrategy;
Expand All @@ -56,7 +62,9 @@ public ShoppingCartHelpers(
_shoppingCartEvents = shoppingCartEvents;
_shoppingCartPersistence = shoppingCartPersistence;
_shoppingCartSerializer = shoppingCartSerializer;
_options = priceDisplayNameOptions.Value;
H = localizer;
_htmlLocalizer = htmlLocalizer;
}

public async Task<ShoppingCartViewModel> CreateShoppingCartViewModelAsync(
Expand Down Expand Up @@ -99,13 +107,8 @@ private async Task<ShoppingCartViewModel> CreateShoppingCartViewModelAsync(

if (lines.Count == 0) return null;

IList<LocalizedHtmlString> headers =
[
H["Quantity"],
H["Product"],
H["Price"],
H["Action"],
];
var headers = LocalizationHelpers.GetLocalizedShoppingCartHeaders(_htmlLocalizer, _options);

IList<Amount> totals = [.. (await CalculateMultipleCurrencyTotalsAsync(cart)).Values];

(shipping, billing) = await _hca.GetUserAddressIfNullAsync(shipping, billing);
Expand Down
17 changes: 17 additions & 0 deletions src/Modules/OrchardCore.Commerce/Startup.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Options;
Expand Down Expand Up @@ -44,6 +45,7 @@
using OrchardCore.Data.Migration;
using OrchardCore.Deployment;
using OrchardCore.DisplayManagement.Handlers;
using OrchardCore.Environment.Shell.Configuration;
using OrchardCore.Liquid;
using OrchardCore.Modules;
using OrchardCore.Mvc.Core.Utilities;
Expand Down Expand Up @@ -436,3 +438,18 @@ public class SkuGeneratorGuidStartup : StartupBase
public override void ConfigureServices(IServiceCollection services) =>
services.AddScoped<ISkuGenerator, GuidSkuGenerator>();
}

[RequireFeatures(CommerceConstants.Features.HeadersDisplayNames)]
public class PriceDisplayNamesStartup : StartupBase
{
private readonly IShellConfiguration _shellConfiguration;
public PriceDisplayNamesStartup(IShellConfiguration shellConfiguration) => _shellConfiguration = shellConfiguration;
public override void ConfigureServices(IServiceCollection services)
{
var section = _shellConfiguration.GetSection("OrchardCoreCommerce_HeadersDisplayNames");
if (section.Exists())
{
services.Configure<HeadersDisplayNamesOptions>(section);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
@using Microsoft.Extensions.Options
@using OrchardCore.Commerce.Abstractions
@model PriceDisplaySettingsViewModel

@inject IOptions<HeadersDisplayNamesOptions> options;
<div class="mb-3">
<div class="custom-control custom-checkbox">
<input asp-for="UseNetPriceDisplay" type="checkbox" class="custom-control-input">
<label class="custom-control-label" asp-for="UseNetPriceDisplay">@T["Use Net Price Display"]</label>
<label class="custom-control-label" asp-for="UseNetPriceDisplay">@T[$"Use {options.Value.NetPrice} Display"]</label>
</div>
<div class="row col-md">
<span class="hint">@T["Turn on to display prices with taxes excluded."]</span>
</div>
<div class="custom-control custom-checkbox">
<input asp-for="UseGrossPriceDisplay" type="checkbox" class="custom-control-input">
<label class="custom-control-label" asp-for="UseGrossPriceDisplay">@T["Use Gross Price Display"]</label>
<label class="custom-control-label" asp-for="UseGrossPriceDisplay">@T[$"Use {options.Value.GrossPrice} Display"]</label>
</div>
<div class="row col-md">
<span class="hint">@T["Turn on to display prices with taxes included."]</span>
Expand Down
10 changes: 8 additions & 2 deletions src/Modules/OrchardCore.Commerce/Views/ShoppingCartTable.cshtml
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
@using Microsoft.AspNetCore.Mvc.Localization
@using Microsoft.Extensions.Options
@using OrchardCore.Commerce.Abstractions
@using OrchardCore.Commerce.Abstractions.Helpers
@using OrchardCore.Commerce.Abstractions.ViewModels
@using OrchardCore.Commerce.Models
@using OrchardCore.Commerce.MoneyDataType
@using OrchardCore.Commerce.Tax.Extensions
@using OrchardCore.Settings
@inject ISiteService SiteService
@inject IOptions<HeadersDisplayNamesOptions> options;
@inject IHtmlLocalizer<HeadersDisplayNamesOptions> HtmlLocalizer;

@{
var viewModel = (ShoppingCartViewModel)Model.ShoppingCartViewModel;
Expand Down Expand Up @@ -70,15 +76,15 @@
<p>
@if (priceDisplaySettings.UseNetPriceDisplay)
{
@T["Net Price: {0}", netTotal]
@LocalizationHelpers.FormatPriceWithLabel(HtmlLocalizer, options.Value.NetPrice, netTotal)
}
@if (priceDisplaySettings.UseNetPriceDisplay && priceDisplaySettings.UseGrossPriceDisplay)
{
<text>|</text>
}
@if (priceDisplaySettings.UseGrossPriceDisplay)
{
@T["Gross Price: {0}", grossTotal]
@LocalizationHelpers.FormatPriceWithLabel(HtmlLocalizer, options.Value.GrossPrice, grossTotal)
}
</p>
}
Expand Down
Loading