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
5 changes: 5 additions & 0 deletions src/Modules/OrchardCore.Commerce/Indexes/ProductPartIndex.cs
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.

This is not necessary. Content fields like DateTimeField already create an index on their own (in this case DateTimeFieldIndex) which you can join in your query. Please revert the changes in ProductPartIndex and ProductMigrations

Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -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; }
}

/// <summary>
Expand Down Expand Up @@ -36,6 +39,8 @@ public override void Describe(DescribeContext<ContentItem> context) =>
{
Sku = productPart.Sku.ToUpperInvariant(),
ContentItemId = contentItem.ContentItemId,
StartTimeUtc = productPart.StartTimeUtc?.Value,
EndTimeUtc = productPart.EndTimeUtc?.Value,
};
});
}
43 changes: 43 additions & 0 deletions src/Modules/OrchardCore.Commerce/Migrations/ProductMigrations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -54,4 +55,46 @@ await _contentDefinitionManager
.WithSettings(new MediaFieldSettings { Multiple = false })));
return 2;
}

public async Task<int> UpdateFrom2Async()
{
await SchemaBuilder
.AlterTableAsync(nameof(ProductPartIndex), table => {
table.AddColumn<System.DateTime?>(nameof(ProductPartIndex.StartTimeUtc));
table.AddColumn<System.DateTime?>(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<int> UpdateFrom3Async()
{
// Add DateTimeField fields to ProductPart for UI editing
await _contentDefinitionManager
.AlterPartDefinitionAsync<ProductPart>(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;
}
}
13 changes: 13 additions & 0 deletions src/Modules/OrchardCore.Commerce/Models/ProductPart.cs
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.

Interesting concept, but this is not a core product feature, so it shouldn't be in ProductPart

Move these fields into a new content part. As this is related to availability, it should be in OrchardCore.Commerce.Inventory. Also create a documentation file docs/features/*-part.md and link it in the mkdocs.yml.

Original file line number Diff line number Diff line change
@@ -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;

Expand All @@ -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.
/// </summary>
public MediaField ProductImage { get; set; }

/// <summary>
/// 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.
/// </summary>
public DateTimeField StartTimeUtc { get; set; } = new();

/// <summary>
/// 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.
/// </summary>
public DateTimeField EndTimeUtc { get; set; } = new();
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -41,6 +43,12 @@ public async Task<ProductList> GetProductsAsync(ProductListPart productList, Pro

var query = _session.Query<ContentItem>();
query = query.With<ContentItemIndex>(index => index.ContentType.IsIn(productTypes) && index.Published);

// Filter products by start and end time
var utcNow = DateTime.UtcNow;
query = query.With<ProductPartIndex>(index =>
(index.StartTimeUtc == null || index.StartTimeUtc <= utcNow) &&
(index.EndTimeUtc == null || index.EndTimeUtc >= utcNow));
Comment on lines +47 to +51
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.

Create a new IProductListFilterProvider implementation and move this code into its BuildQueryAsync method.


var context = new ProductListFilterContext
{
Expand Down
6 changes: 5 additions & 1 deletion src/Modules/OrchardCore.Commerce/Services/ProductService.cs
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,12 @@ public virtual async Task<IEnumerable<ProductPart>> GetProductsAsync(IEnumerable
{
var trimmedSkus = skus.Select(sku => sku.Split('-')[0]);

var utcNow = DateTime.UtcNow;
var contentItemIds = (await _session
.QueryIndex<ProductPartIndex>(index => index.Sku.IsIn(trimmedSkus))
.QueryIndex<ProductPartIndex>(index =>
index.Sku.IsIn(trimmedSkus) &&
(index.StartTimeUtc == null || index.StartTimeUtc <= utcNow) &&
(index.EndTimeUtc == null || index.EndTimeUtc >= utcNow))
Comment on lines +43 to +48
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.

GetProductsAsync is used in a lot of places, in some cases fetching an expired product is valid.
I think you just want to disable expired products, right?

.ListAsync())
.Select(idx => idx.ContentItemId)
.Distinct();
Expand Down