Skip to content

Commit 17cb1dc

Browse files
committed
Merge remote-tracking branch 'origin/main' into renovate/all-dependencies
2 parents 5abc71f + 1dc86ce commit 17cb1dc

15 files changed

Lines changed: 203 additions & 25 deletions

File tree

4.0.0.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,7 @@ The `Retrieve`, `Store` and `Remove` methods have been replaced with `RetrieveAs
1111
## Change Logs
1212

1313
Please check the GitHub release entry [here](https://github.com/OrchardCMS/OrchardCore.Commerce/releases/tag/v4.0.0).
14+
15+
### SKU Generators
16+
17+
Now it's possible to automatically generate the product SKU. Manual entry remains the default for backwards compatibility, but if the SKU doesn't have to be a specific value you can enable the "Orchard Core Commerce - SKU Generator - GUID" feature. This makes the SKU field read-only even during product creation. Instead, when you publish the product a new GUID string is automatically generated and filled as the SKU. You can also implement your own SKU generator by implementing the `ISkuGenerator` service.

Directory.Packages.props

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,22 +6,22 @@
66
<!-- The Orchard Core version should always be x.y.0 of the latest minor version for maximum compatibility when
77
distributed as NuGet packages. On the other hand, the consuming projects (including OrchardCore.Commerce.Web)
88
should use Orchard Core references for the latest patch version to pull all versions up in the final app. -->
9-
<OrchardCoreVersion>3.0.0-preview-18937</OrchardCoreVersion>
9+
<OrchardCoreVersion>3.0.0-preview-18960</OrchardCoreVersion>
1010
</PropertyGroup>
1111

1212
<ItemGroup>
1313
<PackageVersion Include="GraphQL.MicrosoftDI" Version="8.8.4" />
1414
<PackageVersion Include="GraphQL.SystemTextJson" Version="8.8.4" />
1515
<PackageVersion Include="HtmlSanitizer" Version="9.1.893-beta" />
1616
<PackageVersion Include="Lombiq.Analyzers.OrchardCore" Version="5.2.1-alpha.3.osoe-925" />
17-
<PackageVersion Include="Lombiq.HelpfulLibraries.OrchardCore" Version="12.6.1-alpha.5.osoe-925" />
18-
<PackageVersion Include="Lombiq.HelpfulLibraries.AspNetCore" Version="12.6.1-alpha.5.osoe-925" />
19-
<PackageVersion Include="Lombiq.HelpfulLibraries.Refit" Version="12.6.1-alpha.5.osoe-925" />
20-
<PackageVersion Include="Lombiq.Tests" Version="5.0.1-alpha.0.osoe-925" />
21-
<PackageVersion Include="Lombiq.Tests.UI" Version="14.2.2-alpha.6.osoe-925" />
22-
<PackageVersion Include="Lombiq.Tests.UI.AppExtensions" Version="14.2.2-alpha.6.osoe-925" />
23-
<PackageVersion Include="Lombiq.Tests.UI.Shortcuts" Version="14.2.2-alpha.6.osoe-925" />
24-
<PackageVersion Include="Microsoft.Identity.Web" Version="4.4.0" />
17+
<PackageVersion Include="Lombiq.HelpfulLibraries.OrchardCore" Version="12.6.1-alpha.6.osoe-925" />
18+
<PackageVersion Include="Lombiq.HelpfulLibraries.AspNetCore" Version="12.6.1-alpha.6.osoe-925" />
19+
<PackageVersion Include="Lombiq.HelpfulLibraries.Common" Version="12.6.1-alpha.6.osoe-925" />
20+
<PackageVersion Include="Lombiq.HelpfulLibraries.Refit" Version="12.6.1-alpha.6.osoe-925" />
21+
<PackageVersion Include="Lombiq.Tests" Version="5.0.1-alpha.1.osoe-925" />
22+
<PackageVersion Include="Lombiq.Tests.UI" Version="14.2.2-alpha.7.osoe-925" />
23+
<PackageVersion Include="Lombiq.Tests.UI.AppExtensions" Version="14.2.2-alpha.7.osoe-925" />
24+
<PackageVersion Include="Lombiq.Tests.UI.Shortcuts" Version="14.2.2-alpha.7.osoe-925" />
2525
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="18.3.0" />
2626
<PackageVersion Include="Newtonsoft.Json" Version="13.0.4" />
2727
<PackageVersion Include="OrchardCore.Application.Cms.Targets" Version="$(OrchardCoreVersion)" />
@@ -45,7 +45,7 @@
4545
<PackageVersion Include="OrchardCore.Workflows.Abstractions" Version="$(OrchardCoreVersion)" />
4646
<PackageVersion Include="Shouldly" Version="4.3.0" />
4747
<PackageVersion Include="SixLabors.ImageSharp" Version="3.1.12" />
48-
<PackageVersion Include="Stripe.net" Version="50.4.0" />
48+
<PackageVersion Include="Stripe.net" Version="50.4.1" />
4949
<PackageVersion Include="xunit.v3" Version="3.2.2" />
5050
<PackageVersion Include="xunit.runner.visualstudio" Version="3.1.5" />
5151
</ItemGroup>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
#nullable enable
2+
3+
using OrchardCore.ContentManagement;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
using System.Threading.Tasks;
7+
8+
namespace OrchardCore.Commerce.Abstractions.Abstractions;
9+
10+
/// <summary>
11+
/// A service that can provide an alternative to manually filling out the product part's SKU field.
12+
/// </summary>
13+
public interface ISkuGenerator
14+
{
15+
/// <summary>
16+
/// Gets the sorting order when selecting SKU generator. If multiple generators are registered, the one with the
17+
/// highest priority is used.
18+
/// </summary>
19+
public int Priority => 0;
20+
21+
/// <summary>
22+
/// Gets a value indicating whether this generator allows user fill-in. If <see langword="false"/>, the SKU field is
23+
/// hidden in the product editor. If <see langword="true"/>, the generator is only used when the product is
24+
/// published with an empty SKU.
25+
/// </summary>
26+
public bool IsManualAllowed => false;
27+
28+
/// <summary>
29+
/// Returns the SKU for the provided <paramref name="contentItem"/>.
30+
/// </summary>
31+
/// <param name="contentItem">
32+
/// The content item whose product SKU is to be generated. Must not be modified by this method.
33+
/// </param>
34+
public Task<string> GenerateSkuAsync(ContentItem contentItem);
35+
}
36+
37+
public static class SkuGeneratorExtensions
38+
{
39+
/// <summary>
40+
/// Returns the highest <see cref="ISkuGenerator.Priority"/> generator, or <see langword="null"/>.
41+
/// </summary>
42+
public static ISkuGenerator? HighestPriority(this IEnumerable<ISkuGenerator> generators) =>
43+
generators.OrderByDescending(generator => generator.Priority).FirstOrDefault();
44+
}

src/Modules/OrchardCore.Commerce.Payment.Exactly/Services/ExactlyApiHandler.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.Extensions.Options;
22
using OrchardCore.Commerce.Payment.Exactly.Models;
3+
using OrchardCore.Infrastructure;
34
using System;
45
using System.Net.Http;
56
using System.Net.Http.Headers;
@@ -25,7 +26,7 @@ protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage reques
2526
const BindingFlags privateFieldFlags = BindingFlags.Instance | BindingFlags.NonPublic;
2627
var allowedHeaderTypes = typeof(HttpHeaders).GetField("_allowedHeaderTypes", privateFieldFlags);
2728
allowedHeaderTypes!.SetValue(request.Headers, Enum.Parse(allowedHeaderTypes.FieldType, "All"));
28-
request.Headers.Add("Content-Type", "application/vnd.api+json");
29+
request.Headers.Add("Content-Type", MediaTypeNamesExtended.Application.JsonVendeorPrefix);
2930
#pragma warning restore S3011
3031

3132
return base.SendAsync(request, cancellationToken);

src/Modules/OrchardCore.Commerce.Payment.Stripe/Migrations/StripeMigrations.cs

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,11 @@
11
using Lombiq.HelpfulLibraries.OrchardCore.Data;
2-
using Microsoft.Data.SqlClient;
32
using Microsoft.Extensions.Logging;
43
using OrchardCore.Commerce.Payment.Stripe.Indexes;
54
using OrchardCore.Commerce.Payment.Stripe.Models;
65
using OrchardCore.ContentManagement.Metadata;
76
using OrchardCore.ContentManagement.Metadata.Settings;
87
using OrchardCore.Data.Migration;
9-
using System;
8+
using System.Data.Common;
109
using System.Threading.Tasks;
1110
using YesSql.Sql;
1211
using static OrchardCore.Commerce.Abstractions.Constants.ContentTypes;
@@ -40,7 +39,7 @@ await _contentDefinitionManager
4039
{
4140
await SchemaBuilder.DropMapIndexTableAsync(typeof(OrderPaymentIndex));
4241
}
43-
catch (Exception exception) when (exception is SqlException)
42+
catch (DbException exception)
4443
{
4544
// This is fine, it just means that the table didn't exist.
4645
_logger.LogInformation(

src/Modules/OrchardCore.Commerce/CommerceConstants.cs

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@ public static class CommerceConstants
55
public static class Features
66
{
77
public const string Core = "OrchardCore.Commerce";
8-
public const string SessionCartStorage = "OrchardCore.Commerce.SessionCartStorage";
9-
public const string CurrencySettingsSelector = "OrchardCore.Commerce.CurrencySettingsSelector";
10-
public const string Subscription = "OrchardCore.Commerce.Subscription";
8+
public const string SkuGenerator = $"{Core}.{nameof(SkuGenerator)}";
9+
public const string SessionCartStorage = $"{Core}.{nameof(SessionCartStorage)}";
10+
public const string CurrencySettingsSelector = $"{Core}.{nameof(CurrencySettingsSelector)}";
11+
public const string Subscription = $"{Core}.{nameof(Subscription)}";
12+
public const string SkuGeneratorGuid = $"{SkuGenerator}.Guid";
1113
}
1214
}

src/Modules/OrchardCore.Commerce/Drivers/ProductPartDisplayDriver.cs

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using Microsoft.Extensions.Localization;
22
using OrchardCore.Commerce.Abstractions;
3+
using OrchardCore.Commerce.Abstractions.Abstractions;
34
using OrchardCore.Commerce.Inventory;
45
using OrchardCore.Commerce.Inventory.Models;
56
using OrchardCore.Commerce.Models;
@@ -17,13 +18,18 @@ namespace OrchardCore.Commerce.Drivers;
1718
public class ProductPartDisplayDriver : ContentPartDisplayDriver<ProductPart>
1819
{
1920
private readonly IProductAttributeService _productAttributeService;
21+
private readonly ISkuGenerator _skuGenerator;
2022
private readonly IStringLocalizer T;
2123

24+
private bool IsSkuReadOnly => _skuGenerator?.IsManualAllowed == false;
25+
2226
public ProductPartDisplayDriver(
2327
IProductAttributeService productAttributeService,
28+
IEnumerable<ISkuGenerator> skuGenerators,
2429
IStringLocalizer<ProductPartDisplayDriver> stringLocalizer)
2530
{
2631
_productAttributeService = productAttributeService;
32+
_skuGenerator = skuGenerators.HighestPriority();
2733
T = stringLocalizer;
2834
}
2935

@@ -39,17 +45,19 @@ public override async Task<IDisplayResult> UpdateAsync(
3945
ProductPart part,
4046
UpdatePartEditorContext context)
4147
{
42-
var skuBefore = part.Sku;
48+
var skuBefore = part.Sku ?? string.Empty;
4349

4450
await context.Updater.TryUpdateModelAsync(part, Prefix);
4551

52+
part.Sku ??= string.Empty;
4653
if (part.Sku.Contains('-'))
4754
{
4855
context.AddModelError(nameof(ProductPart.Sku), T["SKU may not contain the dash character."]);
4956
return await EditAsync(part, context);
5057
}
5158

52-
part.Sku = part.Sku.ToUpperInvariant();
59+
// If the SKU is read-only then editing should not be possible, but here we undo any POST trickery just in case.
60+
part.Sku = IsSkuReadOnly ? skuBefore : part.Sku.ToUpperInvariant();
5361

5462
if (part.ContentItem.As<InventoryPart>() is { } inventoryPart)
5563
{
@@ -94,6 +102,7 @@ private async Task BuildViewModelAsync(ProductPartViewModel viewModel, ProductPa
94102
{
95103
viewModel.ContentItem = part.ContentItem;
96104
viewModel.Sku = part.Sku;
105+
viewModel.IsSkuReadOnly = IsSkuReadOnly;
97106
viewModel.ProductPart = part;
98107

99108
if (part.ContentItem.As<InventoryPart>() is { } inventoryPart)

src/Modules/OrchardCore.Commerce/Handlers/SkuValidationHandler.cs

Lines changed: 26 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using Microsoft.Extensions.Localization;
2+
using OrchardCore.Commerce.Abstractions.Abstractions;
23
using OrchardCore.Commerce.Indexes;
34
using OrchardCore.Commerce.Models;
45
using OrchardCore.Commerce.Services;
56
using OrchardCore.ContentManagement;
7+
using OrchardCore.ContentManagement.Handlers;
68
using OrchardCore.DisplayManagement.ModelBinding;
79
using System;
810
using System.Collections.Generic;
@@ -12,26 +14,46 @@
1214

1315
namespace OrchardCore.Commerce.Handlers;
1416

15-
public class SkuValidationHandler : CreatingOrUpdatingPartHandler<ProductPart>
17+
public class SkuValidationHandler : ContentPartHandler<ProductPart>
1618
{
1719
private readonly ISession _session;
1820
private readonly IUpdateModelAccessor _updateModelAccessor;
1921
private readonly IEnumerable<IDuplicateSkuResolver> _duplicateSkuResolvers;
22+
private readonly IEnumerable<ISkuGenerator> _skuGenerators;
2023
private readonly IStringLocalizer<SkuValidationHandler> T;
2124

2225
public SkuValidationHandler(
2326
ISession session,
2427
IUpdateModelAccessor updateModelAccessor,
2528
IEnumerable<IDuplicateSkuResolver> duplicateSkuResolvers,
29+
IEnumerable<ISkuGenerator> skuGenerators,
2630
IStringLocalizer<SkuValidationHandler> stringLocalizer)
2731
{
2832
_session = session;
2933
_updateModelAccessor = updateModelAccessor;
3034
_duplicateSkuResolvers = duplicateSkuResolvers;
35+
_skuGenerators = skuGenerators;
3136
T = stringLocalizer;
3237
}
3338

34-
protected override async Task CreatingOrUpdatingAsync(ProductPart part)
39+
public override async Task CreatingAsync(CreateContentContext context, ProductPart part)
40+
{
41+
// If we have an SKU generator and the SKU is either empty or it must not be manually filled, then overwrite it
42+
// with the generated value.
43+
if (_skuGenerators.HighestPriority() is { } generator &&
44+
(string.IsNullOrWhiteSpace(part.Sku) || !generator.IsManualAllowed))
45+
{
46+
part.Sku = await generator.GenerateSkuAsync(part.ContentItem);
47+
part.ContentItem.Apply(part);
48+
}
49+
50+
await CreatingOrUpdatingAsync(part);
51+
}
52+
53+
public override Task UpdatingAsync(UpdateContentContext context, ProductPart part) =>
54+
CreatingOrUpdatingAsync(part);
55+
56+
private async Task CreatingOrUpdatingAsync(ProductPart part)
3557
{
3658
if (string.IsNullOrWhiteSpace(part.Sku))
3759
{
@@ -47,14 +69,14 @@ protected override async Task CreatingOrUpdatingAsync(ProductPart part)
4769
.AsList();
4870

4971
var resolvers = _duplicateSkuResolvers.AsList();
50-
for (var i = 0; i < resolvers.Count && alreadyExisting?.Any() == true; i++)
72+
for (var i = 0; i < resolvers.Count && alreadyExisting.Any(); i++)
5173
{
5274
alreadyExisting =
5375
await resolvers[i].UpdateDuplicatesListAsync(part.ContentItem, alreadyExisting) ??
5476
Array.Empty<ContentItem>();
5577
}
5678

57-
if (alreadyExisting?.Any() == true)
79+
if (alreadyExisting.Any())
5880
{
5981
_updateModelAccessor.ModelUpdater.ModelState.AddModelError(
6082
nameof(part.Sku),

src/Modules/OrchardCore.Commerce/Manifest.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,3 +59,14 @@
5959
CommerceConstants.Features.Core,
6060
]
6161
)]
62+
63+
[assembly: Feature(
64+
Id = CommerceConstants.Features.SkuGeneratorGuid,
65+
Name = "Orchard Core Commerce - SKU Generator - GUID",
66+
Category = "Commerce",
67+
Description = "Replaces manual SKU entry in the content item editor with a GUID-based generator.",
68+
Dependencies =
69+
[
70+
CommerceConstants.Features.Core,
71+
]
72+
)]
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using OrchardCore.Commerce.Abstractions.Abstractions;
2+
using OrchardCore.ContentManagement;
3+
using System;
4+
using System.Threading.Tasks;
5+
6+
namespace OrchardCore.Commerce.Services;
7+
8+
public class GuidSkuGenerator : ISkuGenerator
9+
{
10+
public int Priority => int.MinValue;
11+
12+
public Task<string> GenerateSkuAsync(ContentItem contentItem) =>
13+
Task.FromResult(Guid.NewGuid().ToString("N"));
14+
}

0 commit comments

Comments
 (0)