diff --git a/src/Modules/OrchardCore.Commerce/Indexes/ProductPartIndex.cs b/src/Modules/OrchardCore.Commerce/Indexes/ProductPartIndex.cs
index f54e89b57..19d9486b5 100644
--- a/src/Modules/OrchardCore.Commerce/Indexes/ProductPartIndex.cs
+++ b/src/Modules/OrchardCore.Commerce/Indexes/ProductPartIndex.cs
@@ -1,6 +1,7 @@
using OrchardCore.Commerce.Abstractions.Abstractions;
using OrchardCore.Commerce.Models;
using OrchardCore.ContentManagement;
+using System;
using YesSql.Indexes;
namespace OrchardCore.Commerce.Indexes;
@@ -9,6 +10,8 @@ public class ProductPartIndex : MapIndex, ISkuHolder
{
public string ContentItemId { get; set; }
public string Sku { get; set; }
+ public DateTime? StartTimeUtc { get; set; }
+ public DateTime? EndTimeUtc { get; set; }
}
///
@@ -36,6 +39,8 @@ public override void Describe(DescribeContext context) =>
{
Sku = productPart.Sku.ToUpperInvariant(),
ContentItemId = contentItem.ContentItemId,
+ StartTimeUtc = productPart.StartTimeUtc?.Value,
+ EndTimeUtc = productPart.EndTimeUtc?.Value,
};
});
}
diff --git a/src/Modules/OrchardCore.Commerce/Migrations/ProductMigrations.cs b/src/Modules/OrchardCore.Commerce/Migrations/ProductMigrations.cs
index 247f55552..351bd25c3 100644
--- a/src/Modules/OrchardCore.Commerce/Migrations/ProductMigrations.cs
+++ b/src/Modules/OrchardCore.Commerce/Migrations/ProductMigrations.cs
@@ -2,6 +2,7 @@
using OrchardCore.Commerce.Models;
using OrchardCore.ContentManagement.Metadata;
using OrchardCore.ContentManagement.Metadata.Settings;
+using OrchardCore.ContentFields.Settings;
using OrchardCore.Data.Migration;
using OrchardCore.Media.Fields;
using OrchardCore.Media.Settings;
@@ -54,4 +55,46 @@ await _contentDefinitionManager
.WithSettings(new MediaFieldSettings { Multiple = false })));
return 2;
}
+
+ public async Task UpdateFrom2Async()
+ {
+ await SchemaBuilder
+ .AlterTableAsync(nameof(ProductPartIndex), table => {
+ table.AddColumn(nameof(ProductPartIndex.StartTimeUtc));
+ table.AddColumn(nameof(ProductPartIndex.EndTimeUtc));
+ });
+
+ await SchemaBuilder
+ .AlterTableAsync(nameof(ProductPartIndex), table => table
+ .CreateIndex(
+ $"IDX_{nameof(ProductPartIndex)}_TimeBased",
+ new[]
+ {
+ nameof(ProductPartIndex.StartTimeUtc),
+ nameof(ProductPartIndex.EndTimeUtc)
+ }));
+
+ return 3;
+ }
+
+ public async Task UpdateFrom3Async()
+ {
+ // Add DateTimeField fields to ProductPart for UI editing
+ await _contentDefinitionManager
+ .AlterPartDefinitionAsync(builder => builder
+ .WithField(part => part.StartTimeUtc, field => field
+ .WithDisplayName("Product Start Time")
+ .WithSettings(new DateTimeFieldSettings
+ {
+ Hint = "The date and time (UTC) from which the product becomes visible and available for purchase. Leave empty for immediate availability.",
+ }))
+ .WithField(part => part.EndTimeUtc, field => field
+ .WithDisplayName("Product End Time")
+ .WithSettings(new DateTimeFieldSettings
+ {
+ Hint = "The date and time (UTC) until which the product remains visible and available for purchase. Leave empty for indefinite availability.",
+ })));
+
+ return 4;
+ }
}
diff --git a/src/Modules/OrchardCore.Commerce/Models/ProductPart.cs b/src/Modules/OrchardCore.Commerce/Models/ProductPart.cs
index 4275af296..d1a0ac7de 100644
--- a/src/Modules/OrchardCore.Commerce/Models/ProductPart.cs
+++ b/src/Modules/OrchardCore.Commerce/Models/ProductPart.cs
@@ -1,6 +1,7 @@
using OrchardCore.Commerce.Abstractions.Abstractions;
using OrchardCore.Commerce.Inventory.Models;
using OrchardCore.ContentManagement;
+using OrchardCore.ContentFields.Fields;
using OrchardCore.Media.Fields;
using System.Collections.Generic;
@@ -26,4 +27,16 @@ public class ProductPart : ContentPart, ISkuHolderContent
/// Gets or sets the image associated with this product, which will be displayed on the product's page.
///
public MediaField ProductImage { get; set; }
+
+ ///
+ /// Gets or sets the date and time (UTC) from which the product becomes visible and available for purchase.
+ /// If null, the product is visible immediately.
+ ///
+ public DateTimeField StartTimeUtc { get; set; } = new();
+
+ ///
+ /// Gets or sets the date and time (UTC) until which the product remains visible and available for purchase.
+ /// If null, the product remains visible indefinitely.
+ ///
+ public DateTimeField EndTimeUtc { get; set; } = new();
}
diff --git a/src/Modules/OrchardCore.Commerce/Services/ProductListService.cs b/src/Modules/OrchardCore.Commerce/Services/ProductListService.cs
index 7c3ba3e6b..a72bc6a43 100644
--- a/src/Modules/OrchardCore.Commerce/Services/ProductListService.cs
+++ b/src/Modules/OrchardCore.Commerce/Services/ProductListService.cs
@@ -1,6 +1,8 @@
+using OrchardCore.Commerce.Indexes;
using OrchardCore.Commerce.Models;
using OrchardCore.ContentManagement;
using OrchardCore.ContentManagement.Metadata;
+using OrchardCore.ContentManagement.Metadata.Records;
using OrchardCore.ContentManagement.Records;
using System;
using System.Collections.Generic;
@@ -41,6 +43,12 @@ public async Task GetProductsAsync(ProductListPart productList, Pro
var query = _session.Query();
query = query.With(index => index.ContentType.IsIn(productTypes) && index.Published);
+
+ // Filter products by start and end time
+ var utcNow = DateTime.UtcNow;
+ query = query.With(index =>
+ (index.StartTimeUtc == null || index.StartTimeUtc <= utcNow) &&
+ (index.EndTimeUtc == null || index.EndTimeUtc >= utcNow));
var context = new ProductListFilterContext
{
diff --git a/src/Modules/OrchardCore.Commerce/Services/ProductService.cs b/src/Modules/OrchardCore.Commerce/Services/ProductService.cs
index 2006fc5d4..ba39989f7 100644
--- a/src/Modules/OrchardCore.Commerce/Services/ProductService.cs
+++ b/src/Modules/OrchardCore.Commerce/Services/ProductService.cs
@@ -40,8 +40,12 @@ public virtual async Task> GetProductsAsync(IEnumerable
{
var trimmedSkus = skus.Select(sku => sku.Split('-')[0]);
+ var utcNow = DateTime.UtcNow;
var contentItemIds = (await _session
- .QueryIndex(index => index.Sku.IsIn(trimmedSkus))
+ .QueryIndex(index =>
+ index.Sku.IsIn(trimmedSkus) &&
+ (index.StartTimeUtc == null || index.StartTimeUtc <= utcNow) &&
+ (index.EndTimeUtc == null || index.EndTimeUtc >= utcNow))
.ListAsync())
.Select(idx => idx.ContentItemId)
.Distinct();